Dear Robert,

I have been using the ply plugin to read files created by bundler and pmvs2 (http://grail.cs.washington.edu/software/pmvs/). This program generates models in the form of vertices only. However, the existing ply reader implementation was not able to handle the models generated in a satisfactory manner for two reasons:

1. It did not support normals applied to individual vertices.
2. It would only support red / green / blue colour triples, but the pmvs models are generated with diffuse colours. (The PLY format, http://local.wasp.uwa.edu.au/~pbourke/dataformats/ply/, lists specular and ambient colour forms as well.)

To partially overcome these limitations, please find attached modified versions of

src/osgPlugins/ply/vertexData.cpp
src/osgPlugins/ply/vertexData.h

The changes I've made are:

1. I have changed the boolean hasColor flag to a vertexField (which is a boolean operation on an enum) to indicate what fields are present in the ply file. (This is required because Turk's ply reader spits out warnings for every line where you try to read fields which do not exist.) 2. I have modified the code to apply valid normals to either triangles or vertices. 3. I have kludged in "support" for the various colour variants. Specifically, all the colour specified can be read from the file. However, they are all applied in the same way (namely as a colour array, bound to each vertex).

I've tested the code under Linux using a few data sets from the Stanford PLY repository and some pmvs2-generated data.

Cheers,

Simon
/*  
    vertexData.h
    Copyright (c) 2007, Tobias Wolf <[email protected]>
    All rights reserved.  
    
    Header file of the VertexData class.
*/

/** note, derived from Equalizer LGPL source.*/


#ifndef MESH_VERTEXDATA_H
#define MESH_VERTEXDATA_H


#include <osg/Node>
#include <osg/PrimitiveSet>

#include <vector>

///////////////////////////////////////////////////////////////////////////////
//!
//! \class VertexData
//! \brief helps to read ply file and converts in to osg::Node format
//!
///////////////////////////////////////////////////////////////////////////////

// defined elsewhere
struct PlyFile;

namespace ply 
{
    /*  Holds the flat data and offers routines to read, scale and sort it.  */
    class VertexData
    {
    public:
        // Default constructor
        VertexData();

        
        // Reads ply file and convert in to osg::Node and returns the same
        osg::Node* readPlyFile( const char* file, const bool ignoreColors = false );
        
        // to set the flag for using inverted face
        void useInvertedFaces() { _invertFaces = true; }
        
    private:

	enum VertexFields
	{
	  NONE = 0,
	  XYZ = 1,
	  NORMALS = 2,
	  RGB = 4,
	  AMBIENT = 8,
	  DIFFUSE = 16,
	  SPECULAR = 32
	};

        // Function which reads all the vertices and colors if color info is
        // given and also if the user wants that information
        void readVertices( PlyFile* file, const int nVertices, 
                           const int vertexFields );

        // Reads the triangle indices from the ply file
        void readTriangles( PlyFile* file, const int nFaces );

        // Calculates the normals according to passed flag
        // if vertexNormals is true then computes normal per vertices
        // otherwise per triangle means per face
        void _calculateNormals( const bool vertexNormals = true );
        
        bool        _invertFaces;

        // Vertex array in osg format
        osg::ref_ptr<osg::Vec3Array>   _vertices;
        // Color array in osg format
        osg::ref_ptr<osg::Vec4Array>   _colors;
        osg::ref_ptr<osg::Vec4Array>   _ambient;
        osg::ref_ptr<osg::Vec4Array>   _diffuse;
        osg::ref_ptr<osg::Vec4Array>   _specular;

        // Normals in osg format
        osg::ref_ptr<osg::Vec3Array> _normals;
        // The indices of the faces in premitive set
        osg::ref_ptr<osg::DrawElementsUInt> _triangles;
    };
}


#endif // MESH_VERTEXDATA_H
/*  
    vertexData.cpp
    Copyright (c) 2007, Tobias Wolf <[email protected]>
    All rights reserved.  
    
    Implementation of the VertexData class.
*/

/** note, derived from Equalizer LGPL source.*/

#include "typedefs.h"
#include "vertexData.h"
#include "ply.h"

#include <cstdlib>
#include <algorithm>
#include <osg/Geometry>
#include <osg/Geode>
#include <osg/io_utils>

using namespace std;
using namespace ply;


struct Normal{
    osg::Vec3 triNormal;
    void normal(osg::Vec3 v1, osg::Vec3 v2, osg::Vec3 v3)
    {
        osg::Vec3 u,v;

        // right hand system, CCW triangle
        u = v2 - v1;
        v = v3 - v1;
        triNormal = u^v;
        triNormal.normalize();
    }
};

/*  Contructor.  */
VertexData::VertexData()
    : _invertFaces( false )
{
    // Initialize the members
    _vertices = NULL;
    _colors = NULL;
    _normals = NULL;
    _triangles = NULL;
    _diffuse = NULL;
    _ambient = NULL;
    _specular = NULL;
}


/*  Read the vertex and (if available/wanted) color data from the open file.  */
void VertexData::readVertices( PlyFile* file, const int nVertices, 
                               const int fields )
{
    // temporary vertex structure for ply loading
    struct _Vertex
    {
        float           x;
        float           y;
        float           z;
        float           nx;
        float           ny;
        float           nz;
        unsigned char   red;
        unsigned char   green;
        unsigned char   blue;
        unsigned char   ambient_red;
        unsigned char   ambient_green;
        unsigned char   ambient_blue;
        unsigned char   diffuse_red;
        unsigned char   diffuse_green;
        unsigned char   diffuse_blue;
        unsigned char   specular_red;
        unsigned char   specular_green;
        unsigned char   specular_blue;
        float           specular_coeff;
        float           specular_power;
    } vertex;

    PlyProperty vertexProps[] = 
    {
        { "x", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, x ), 0, 0, 0, 0 },
        { "y", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, y ), 0, 0, 0, 0 },
        { "z", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, z ), 0, 0, 0, 0 },
        { "nx", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, nx ), 0, 0, 0, 0 },
        { "ny", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, ny ), 0, 0, 0, 0 },
        { "nz", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, nz ), 0, 0, 0, 0 },
        { "red", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, red ), 0, 0, 0, 0 },
        { "green", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, green ), 0, 0, 0, 0 },
        { "blue", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, blue ), 0, 0, 0, 0 },
        { "ambient_red", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, ambient_red ), 0, 0, 0, 0 },
        { "ambient_green", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, ambient_green ), 0, 0, 0, 0 },
        { "ambient_blue", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, ambient_blue ), 0, 0, 0, 0 },
        { "diffuse_red", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, diffuse_red ), 0, 0, 0, 0 },
        { "diffuse_green", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, diffuse_green ), 0, 0, 0, 0 },
        { "diffuse_blue", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, diffuse_blue ), 0, 0, 0, 0 },
        { "specular_red", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, specular_red ), 0, 0, 0, 0 },
        { "specular_green", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, specular_green ), 0, 0, 0, 0 },
        { "specular_blue", PLY_UCHAR, PLY_UCHAR, offsetof( _Vertex, specular_blue ), 0, 0, 0, 0 },
        { "specular_coeff", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, specular_coeff ), 0, 0, 0, 0 },
        { "specular_power", PLY_FLOAT, PLY_FLOAT, offsetof( _Vertex, specular_power ), 0, 0, 0, 0 },
    };
    
    // use all 6 properties when reading colors, only the first 3 otherwise
    for( int i = 0; i < 3; ++i ) 
        ply_get_property( file, "vertex", &vertexProps[i] );
    
    if (fields & NORMALS)
      for( int i = 3; i < 6; ++i ) 
        ply_get_property( file, "vertex", &vertexProps[i] );

    if (fields & RGB)
      for( int i = 6; i < 9; ++i ) 
        ply_get_property( file, "vertex", &vertexProps[i] );

    if (fields & AMBIENT)
      for( int i = 9; i < 12; ++i ) 
        ply_get_property( file, "vertex", &vertexProps[i] );

    if (fields & DIFFUSE)
      for( int i = 12; i < 15; ++i ) 
        ply_get_property( file, "vertex", &vertexProps[i] );

    if (fields & SPECULAR)
      for( int i = 15; i < 20; ++i ) 
        ply_get_property( file, "vertex", &vertexProps[i] );

    // check whether array is valid otherwise allocate the space
    if(!_vertices.valid())
        _vertices = new osg::Vec3Array; 

    if( fields & NORMALS )
    {
        if(!_normals.valid())
            _normals = new osg::Vec3Array;
    }
    
    // If read colors allocate space for color array
    if( fields & RGB )
    {
        if(!_colors.valid())
            _colors = new osg::Vec4Array;
    }

    if( fields & AMBIENT )
    {
        if(!_ambient.valid())
            _ambient = new osg::Vec4Array;
    }

    if( fields & DIFFUSE )
    {
        if(!_diffuse.valid())
            _diffuse = new osg::Vec4Array;
    }
    
    if( fields & SPECULAR )
    {
        if(!_specular.valid())
            _specular = new osg::Vec4Array;
    }

    // read in the vertices
    for( int i = 0; i < nVertices; ++i )
    {
        ply_get_element( file, static_cast< void* >( &vertex ) );
        _vertices->push_back( osg::Vec3( vertex.x, vertex.y, vertex.z ) );
	if (fields & NORMALS)
	  _normals->push_back( osg::Vec3( vertex.nx, vertex.ny, vertex.nz ) );
        if( fields & RGB )
            _colors->push_back( osg::Vec4( (unsigned int) vertex.red / 256.0,
					   (unsigned int) vertex.green / 256.0 ,
					   (unsigned int) vertex.blue / 256.0, 0.0 ) );
        if( fields & AMBIENT )
	  _ambient->push_back( osg::Vec4( (unsigned int) vertex.ambient_red / 256.0,
					   (unsigned int) vertex.ambient_green / 256.0 ,
					   (unsigned int) vertex.ambient_blue / 256.0, 0.0 ) );

        if( fields & DIFFUSE )
	  _diffuse->push_back( osg::Vec4( (unsigned int) vertex.diffuse_red / 256.0,
					   (unsigned int) vertex.diffuse_green / 256.0 ,
					   (unsigned int) vertex.diffuse_blue / 256.0, 0.0 ) );

        if( fields & SPECULAR )
	  _specular->push_back( osg::Vec4( (unsigned int) vertex.specular_red / 256.0,
					   (unsigned int) vertex.specular_green / 256.0 ,
					   (unsigned int) vertex.specular_blue / 256.0, 0.0 ) );
    }
}


/*  Read the index data from the open file.  */
void VertexData::readTriangles( PlyFile* file, const int nFaces )
{
    // temporary face structure for ply loading
    struct _Face
    {
        unsigned char   nVertices;
        int*            vertices;
    } face;

    PlyProperty faceProps[] = 
    {
        { "vertex_indices", PLY_INT, PLY_INT, offsetof( _Face, vertices ), 
          1, PLY_UCHAR, PLY_UCHAR, offsetof( _Face, nVertices ) }
    };
    
    ply_get_property( file, "face", &faceProps[0] );
    
    //triangles.clear();
    //triangles.reserve( nFaces );
    if(!_triangles.valid())
        _triangles = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);

    
    // read in the faces, asserting that they are only triangles
    int ind1 = _invertFaces ? 2 : 0;
    int ind3 = _invertFaces ? 0 : 2;
    for( int i = 0; i < nFaces; ++i )
    {
        ply_get_element( file, static_cast< void* >( &face ) );
        MESHASSERT( face.vertices != 0 );
        if( (unsigned int)(face.nVertices) != 3 )
        {
            free( face.vertices );
            throw MeshException( "Error reading PLY file. Encountered a "
                                 "face which does not have three vertices." );
        }
        // Add the face indices in the premitive set
        _triangles->push_back( face.vertices[ind1]);
        _triangles->push_back( face.vertices[1]);
        _triangles->push_back( face.vertices[ind3] );
        
        // free the memory that was allocated by ply_get_element
        free( face.vertices );
    }
}


/*  Open a PLY file and read vertex, color and index data. and returns the node  */
osg::Node* VertexData::readPlyFile( const char* filename, const bool ignoreColors )
{
    int     nPlyElems;
    char**  elemNames;
    int     fileType;
    float   version;
    bool    result = false;
    int     nComments;
    char**  comments;
    
    PlyFile* file = NULL;

    // Try to open ply file as for reading
    try{
            file  = ply_open_for_reading( const_cast< char* >( filename ), 
                                          &nPlyElems, &elemNames, 
                                          &fileType, &version );
    }
    // Catch the if any exception thrown
    catch( exception& e )
    {
        MESHERROR << "Unable to read PLY file, an exception occured:  " 
                    << e.what() << endl;
    }

    if( !file )
    {
        MESHERROR << "Unable to open PLY file " << filename 
                  << " for reading." << endl;
        return NULL;
    }

    MESHASSERT( elemNames != 0 );
    

    nComments = file->num_comments;
    comments = file->comments;
    
    
    #ifndef NDEBUG
    MESHINFO << filename << ": " << nPlyElems << " elements, file type = " 
             << fileType << ", version = " << version << endl;
    #endif

    for( int i = 0; i < nComments; i++ )
    {
        if( equal_strings( comments[i], "modified by flipply" ) )
        {
            _invertFaces = true;
        }

    }
    for( int i = 0; i < nPlyElems; ++i )
    {
        int nElems;
        int nProps;
        
        PlyProperty** props = NULL;
        try{
                props = ply_get_element_description( file, elemNames[i], 
                                                     &nElems, &nProps );
        }
        catch( exception& e )
        {
            MESHERROR << "Unable to get PLY file description, an exception occured:  " 
                        << e.what() << endl;
        }
        MESHASSERT( props != 0 );
        
        #ifndef NDEBUG
        MESHINFO << "element " << i << ": name = " << elemNames[i] << ", "
                 << nProps << " properties, " << nElems << " elements" << endl;
        for( int j = 0; j < nProps; ++j )
        {
            MESHINFO << "element " << i << ", property " << j << ": "
                     << "name = " << props[j]->name << endl;
        }
        #endif
        
        // if the string is vertex means vertex data is started
        if( equal_strings( elemNames[i], "vertex" ) )
        {
 	    int fields = NONE;
            // determine if the file stores vertex colors
            for( int j = 0; j < nProps; ++j )
	      {
                // if the string have the red means color info is there
                if( equal_strings( props[j]->name, "x" ) )
                    fields |= XYZ;
                if( equal_strings( props[j]->name, "nx" ) )
                    fields |= NORMALS;
                if( equal_strings( props[j]->name, "red" ) )
                    fields |= RGB;
                if( equal_strings( props[j]->name, "ambient" ) )
                    fields |= AMBIENT;
                if( equal_strings( props[j]->name, "diffuse_red" ) )
                    fields |= DIFFUSE;
                if( equal_strings( props[j]->name, "specular_red" ) )
                    fields |= SPECULAR;
	      }

            if( ignoreColors )
	      {
		fields &= ~(XYZ | NORMALS);
                MESHINFO << "Colors in PLY file ignored per request." << endl;
	      }

            try {   
                // Read vertices and store in a std::vector array
                readVertices( file, nElems, fields );
                // Check whether all vertices are loaded or not
                MESHASSERT( _vertices->size() == static_cast< size_t >( nElems ) );

		// Check if all the optional elements were read or not
                if( fields & NORMALS )
                {
                    MESHASSERT( _normals->size() == static_cast< size_t >( nElems ) );
                }
                if( fields & RGB )
                {
                    MESHASSERT( _colors->size() == static_cast< size_t >( nElems ) );
                }
                if( fields & AMBIENT )
                {
                    MESHASSERT( _ambient->size() == static_cast< size_t >( nElems ) );
                }
                if( fields & DIFFUSE )
                {
                    MESHASSERT( _diffuse->size() == static_cast< size_t >( nElems ) );
                }
                if( fields & SPECULAR )
                {
                    MESHASSERT( _specular->size() == static_cast< size_t >( nElems ) );
                }

                result = true;
            }
            catch( exception& e )
            {
                MESHERROR << "Unable to read vertex in PLY file, an exception occured:  " 
                            << e.what() << endl;
                // stop for loop by setting the loop variable to break condition
                // this way resources still get released even on error cases
                i = nPlyElems;
                
            }
        }
        // If the string is face means triangle info started
        else if( equal_strings( elemNames[i], "face" ) )
        try
        {
            // Read Triangles
            readTriangles( file, nElems );
            // Check whether all face elements read or not
            MESHASSERT( _triangles->size()/3  == static_cast< size_t >( nElems ) );
            result = true;
        }
        catch( exception& e )
        {
            MESHERROR << "Unable to read PLY file, an exception occured:  " 
                      << e.what() << endl;
            // stop for loop by setting the loop variable to break condition
            // this way resources still get released even on error cases
            i = nPlyElems;
        }
        
        // free the memory that was allocated by ply_get_element_description
        for( int j = 0; j < nProps; ++j )
            free( props[j] );
        free( props );
    }
    
    ply_close( file );
    
    // free the memory that was allocated by ply_open_for_reading
    for( int i = 0; i < nPlyElems; ++i )
        free( elemNames[i] );
    free( elemNames );

   // If the result is true means the ply file is successfully read
   if(result)
   {
        // Create geometry node
        osg::Geometry* geom  =  new osg::Geometry;

        // set the vertex array
        geom->setVertexArray(_vertices.get());

        // If the normals are not calculated calculate the normals for faces
        if(_triangles.valid())
        {
            if(!_normals.valid())
                _calculateNormals();
	}

        // Set the normals
	if (_normals.valid())
	{
            geom->setNormalArray(_normals.get());
            geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
        }
        
        // Add the primitive set
        if (_triangles.valid() && _triangles->size() > 0 )
            geom->addPrimitiveSet(_triangles.get());
        else
            geom->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, _vertices->size()));


	// Apply the colours to the model; at the moment this is a
	// kludge because we only use one kind and apply them all the
	// same way. Also, the priority order is completely arbitrary

        if(_colors.valid())
        {
            geom->setColorArray(_colors.get());
            geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
        }
        else if(_ambient.valid())
        {
            geom->setColorArray(_ambient.get());
            geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
        }
        else if(_diffuse.valid())
        {
            geom->setColorArray(_diffuse.get());
            geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
        }
	else if(_specular.valid())
        {
            geom->setColorArray(_specular.get());
            geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
        }

        // set flage true to activate the vertex buffer object of drawable
        geom->setUseVertexBufferObjects(true);
        

        osg::Geode* geode = new osg::Geode;
        geode->addDrawable(geom);
        return geode;
   }
    
    return NULL;
}


/*  Calculate the face or vertex normals of the current vertex data.  */
void VertexData::_calculateNormals( const bool vertexNormals )
{

    if(_normals.valid())
        return;

    #ifndef NDEBUG
    int wrongNormals = 0;
    #endif

    if(!_normals.valid())
    {
        _normals = new osg::Vec3Array; 
    }
    
    //normals.clear();
    if( vertexNormals )
    {
        // initialize all normals to zero
        for( size_t i = 0; i < _vertices->size(); ++i )
        {
            _normals->push_back( osg::Vec3( 0, 0, 0 ) );
        }
    }

    
    for( size_t i = 0; i < ((_triangles->size()));  i += 3 )
    {
        // iterate over all triangles and add their normals to adjacent vertices
        Normal  triangleNormal;
        unsigned int i0, i1, i2;
        i0 = (*_triangles)[i+0];
        i1 = (*_triangles)[i+1];
        i2 = (*_triangles)[i+2];
        triangleNormal.normal((*_vertices)[i0],
                               (*_vertices)[i1],
                               (*_vertices)[i2] );
        
        // count emtpy normals in debug mode
        #ifndef NDEBUG
        if( triangleNormal.triNormal.length() == 0.0f )
            ++wrongNormals;
        #endif
         
        if( vertexNormals )
        {
            (*_normals)[i0] += triangleNormal.triNormal; 
            (*_normals)[i1] += triangleNormal.triNormal; 
            (*_normals)[i2] += triangleNormal.triNormal;
        }
        else
            _normals->push_back( triangleNormal.triNormal ); 
    }
    
    // normalize all the normals
    if( vertexNormals )
        for( size_t i = 0; i < _normals->size(); ++i )
            (*_normals)[i].normalize();
    
    #ifndef NDEBUG
    if( wrongNormals > 0 )
        MESHINFO << wrongNormals << " faces had no valid normal." << endl;
    #endif 
}

_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to