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