From bfbc73785a9b2ea1e81e8e2e2c82cfdac01011bf Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Wed, 26 Sep 2007 10:58:29 +0000 Subject: [PATCH] From Stephan Huber, "attached you'll find a first implementation for the obj-plugin to write obj-files. It is not feature complete but usable. Known issues: * not all materials are handled correctly (especially when using osg::StateAttribute::OVERRIDE), not all properties are supported * could not test point and lines, all of my programs which are capable to read obj-files only import triangle-meshes. * only simple texture-handling" --- src/osgPlugins/obj/CMakeLists.txt | 12 +- src/osgPlugins/obj/OBJWriterNodeVisitor.cpp | 568 ++++++++++++++++++++ src/osgPlugins/obj/OBJWriterNodeVisitor.h | 160 ++++++ src/osgPlugins/obj/ReaderWriterOBJ.cpp | 54 ++ 4 files changed, 792 insertions(+), 2 deletions(-) create mode 100644 src/osgPlugins/obj/OBJWriterNodeVisitor.cpp create mode 100644 src/osgPlugins/obj/OBJWriterNodeVisitor.h diff --git a/src/osgPlugins/obj/CMakeLists.txt b/src/osgPlugins/obj/CMakeLists.txt index 4a5648bd5..6f32ca91e 100644 --- a/src/osgPlugins/obj/CMakeLists.txt +++ b/src/osgPlugins/obj/CMakeLists.txt @@ -1,7 +1,15 @@ #this file is automatically generated -SET(TARGET_SRC ReaderWriterOBJ.cpp obj.cpp ) -SET(TARGET_H obj.h ) +SET(TARGET_SRC + OBJWriterNodeVisitor.cpp + ReaderWriterOBJ.cpp + obj.cpp +) + +SET(TARGET_H + OBJWriterNodeVisitor.h + obj.h +) #### end var setup ### SETUP_PLUGIN(obj) diff --git a/src/osgPlugins/obj/OBJWriterNodeVisitor.cpp b/src/osgPlugins/obj/OBJWriterNodeVisitor.cpp new file mode 100644 index 000000000..3becbb8f5 --- /dev/null +++ b/src/osgPlugins/obj/OBJWriterNodeVisitor.cpp @@ -0,0 +1,568 @@ +// -*-c++-*- + +/* + * Wavefront OBJ loader for Open Scene Graph + * + * Copyright (C) 2001 Ulrich Hertlein + * + * Modified by Robert Osfield to support per Drawable coord, normal and + * texture coord arrays, bug fixes, and support for texture mapping. + * + * Writing support added 2007 by Stephan Huber, http://digitalmind.de, + * some ideas taken from the dae-plugin + * + * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for + * real-time rendering of large 3D photo-realistic models. + * The OSG homepage is http://www.openscenegraph.org/ + */ + +#include +#include "OBJWriterNodeVisitor.h" + + + +/** writes all values of an array out to a stream, applies a matrix beforehand if necessary */ +class ValueVisitor : public osg::ValueVisitor { + public: + ValueVisitor(std::ostream& fout, const osg::Matrix& m = osg::Matrix::identity(), bool isNormal = false) : + osg::ValueVisitor(), + _fout(fout), + _m(m), + _isNormal(isNormal) + { + _applyMatrix = (_m != osg::Matrix::identity()); + if (_isNormal) _origin = osg::Vec3(0,0,0) * _m; + } + + virtual void apply (osg::Vec2 & inv) + { + _fout << inv[0] << ' ' << inv[1]; + } + + virtual void apply (osg::Vec3 & inv) + { + osg::Vec3 v(inv); + if (_applyMatrix) v = (_isNormal) ? (v * _m) - _origin : v * _m; + _fout << v[0] << ' ' << v[1] << ' ' << v[2]; + } + + virtual void apply (osg::Vec2b & inv) + { + _fout << inv[0] << ' ' << inv[1]; + } + + virtual void apply (osg::Vec3b & inv) + { + osg::Vec3 v(inv[0], inv[1], inv[2]); + if (_applyMatrix) v = (_isNormal) ? (v * _m) - _origin : v * _m; + _fout << v[0] << ' ' << v[1] << ' ' << v[2]; + } + + virtual void apply (osg::Vec2s & inv) + { + _fout << inv[0] << ' ' << inv[1]; + } + + virtual void apply (osg::Vec3s & inv) + { + osg::Vec3 v(inv[0], inv[1], inv[2]); + if (_applyMatrix) v = (_isNormal) ? (v * _m) - _origin : v * _m; + _fout << v[0] << ' ' << v[1] << ' ' << v[2]; + } + private: + std::ostream& _fout; + osg::Matrix _m; + bool _applyMatrix, _isNormal; + osg::Vec3 _origin; +}; + +/** writes all primitives of a primitive-set out to a stream, decomposes quads to triangles, line-strips to lines etc */ +class PrimitiveIndexWriter : public osg::PrimitiveIndexFunctor { + + public: + PrimitiveIndexWriter(std::ostream& fout,osg::Geometry* geo, unsigned int normalIndex, unsigned int lastVertexIndex, unsigned int lastNormalIndex, unsigned int lastTexIndex) : + osg::PrimitiveIndexFunctor(), + _fout(fout), + _lastVertexIndex(lastVertexIndex), + _lastNormalIndex(lastNormalIndex), + _lastTexIndex(lastTexIndex), + _hasNormalCoords(geo->getNormalArray() != NULL), + _hasTexCoords(geo->getTexCoordArray(0) != NULL), + _geo(geo), + _normalIndex(normalIndex) + { + } + + virtual void setVertexArray(unsigned int,const osg::Vec2*) {} + + virtual void setVertexArray(unsigned int ,const osg::Vec3* ) {} + + virtual void setVertexArray(unsigned int,const osg::Vec4* ) {} + + void write(unsigned int i) + { + _fout << (i + _lastVertexIndex) << "/"; + + if (_hasTexCoords || _hasNormalCoords) + { + if (_hasTexCoords) + _fout << (i + _lastTexIndex); + _fout << "/"; + if (_hasNormalCoords) + { + if (_geo->getNormalBinding() == osg::Geometry::BIND_PER_VERTEX) + _fout << (i+_lastNormalIndex); + else + _fout << (_normalIndex + _lastNormalIndex); + } + } + _fout << " "; + } + + // operator for triangles + void writeTriangle(unsigned int i1, unsigned int i2, unsigned int i3) + { + _fout << "f "; + write(i1); + write(i2); + write(i3); + _fout << std::endl; + // not sure if this is correct? + if(_geo->getNormalBinding() && _geo->getNormalBinding() == osg::Geometry::BIND_PER_PRIMITIVE) ++_normalIndex; + } + + // operator for lines + void writeLine(unsigned int i1, unsigned int i2) + { + _fout << "l "; + write(i1); + write(i2); + _fout << std::endl; + // not sure if this is correct? + if(_geo->getNormalBinding() && _geo->getNormalBinding() == osg::Geometry::BIND_PER_PRIMITIVE) ++_normalIndex; + } + + // operator for points + void writePoint(unsigned int i1) + { + _fout << "p "; + write(i1); + _fout << std::endl; + // not sure if this is correct? + if(_geo->getNormalBinding() && _geo->getNormalBinding() == osg::Geometry::BIND_PER_PRIMITIVE) ++_normalIndex; + } + + virtual void begin(GLenum mode) + { + _modeCache = mode; + _indexCache.clear(); + } + + virtual void vertex(unsigned int vert) + { + _indexCache.push_back(vert); + } + + virtual void end() + { + if (!_indexCache.empty()) + { + drawElements(_modeCache,_indexCache.size(),&_indexCache.front()); + } + } + + virtual void drawArrays(GLenum mode,GLint first,GLsizei count); + + virtual void drawElements(GLenum mode,GLsizei count,const GLubyte* indices) + { + drawElementsImplementation(mode, count, indices); + } + virtual void drawElements(GLenum mode,GLsizei count,const GLushort* indices) + { + drawElementsImplementation(mode, count, indices); + } + + virtual void drawElements(GLenum mode,GLsizei count,const GLuint* indices) + { + drawElementsImplementation(mode, count, indices); + } + + protected: + + templatevoid drawElementsImplementation(GLenum mode, GLsizei count, const T* indices) + { + if (indices==0 || count==0) return; + + typedef const T* IndexPointer; + + switch(mode) + { + case(GL_TRIANGLES): + { + IndexPointer ilast = &indices[count]; + for(IndexPointer iptr=indices;iptr _indexCache; + unsigned int _lastVertexIndex, _lastNormalIndex, _lastTexIndex; + bool _hasNormalCoords, _hasTexCoords; + osg::Geometry* _geo; + unsigned int _normalIndex; + + +}; + + +void PrimitiveIndexWriter::drawArrays(GLenum mode,GLint first,GLsizei count) +{ + switch(mode) + { + case(GL_TRIANGLES): + { + unsigned int pos=first; + for(GLsizei i=2;igetDiffuse(osg::Material::FRONT); + ambient = mat->getAmbient(osg::Material::FRONT); + specular = mat->getSpecular(osg::Material::FRONT); + } + + if (tex) { + osg::Image* img = tex->getImage(0); + if ((img) && (!img->getFileName().empty())) + image = img->getFileName(); + + } + +} + +std::ostream& operator<<(std::ostream& fout, const OBJWriterNodeVisitor::OBJMaterial& mat) { + + fout << "newmtl " << mat.name << std::endl; + fout << " " << "Ka " << mat.ambient << std::endl; + fout << " " << "Kd " << mat.diffuse << std::endl; + fout << " " << "Ks " << mat.specular << std::endl; + + if(!mat.image.empty()) + fout << " " << "map_Kd " << mat.image << std::endl; + + return fout; + +} + +void OBJWriterNodeVisitor::writeMaterials(std::ostream& fout) +{ + for(MaterialMap::iterator i = _materialMap.begin(); i != _materialMap.end(); ++i) + { + fout << (*i).second << std::endl; + } +} + + +std::string OBJWriterNodeVisitor::getUniqueName(const std::string& defaultvalue) { + + std::string name = ""; + for(std::list::iterator i = _nameStack.begin(); i != _nameStack.end(); ++i) { + if (!name.empty()) name+="_"; + name += (*i); + } + + if (!defaultvalue.empty()) + name += "_" +defaultvalue; + + if (_nameMap.find(name) == _nameMap.end()) + _nameMap.insert(std::make_pair(name, 0)); + + std::stringstream ss; + ss << name << "_" << _nameMap[name]; + ++(_nameMap[name]); + return ss.str(); + +} + +void OBJWriterNodeVisitor::processArray(const std::string& key, osg::Array* array, const osg::Matrix& m, bool isNormal) +{ + if (array == NULL) + return; + + ValueVisitor vv(_fout, m, isNormal); + _fout << std::endl; + for(unsigned int i = 0; i < array->getNumElements(); ++i) { + _fout << key << ' '; + array->accept(i, vv); + _fout << std::endl; + } + + _fout << "# " << array->getNumElements() << " elements written" << std::endl; + +} + +void OBJWriterNodeVisitor::processStateSet(osg::StateSet* ss) +{ + if (_materialMap.find(ss) != _materialMap.end()) { + _fout << "usemtl " << _materialMap[ss].name << std::endl; + return; + } + + osg::Material* mat = dynamic_cast(ss->getAttribute(osg::StateAttribute::MATERIAL)); + osg::Texture* tex = dynamic_cast(ss->getTextureAttribute(0, osg::StateAttribute::TEXTURE)); + + if (mat || tex) + { + _materialMap.insert(std::make_pair(ss, OBJMaterial(mat, tex))); + _fout << "usemtl " << _materialMap[ss].name << std::endl; + } + +} + + +void OBJWriterNodeVisitor::processGeometry(osg::Geometry* geo, osg::Matrix& m) { + _fout << std::endl; + _fout << "o " << getUniqueName( geo->getName().empty() ? geo->className() : geo->getName() ) << std::endl; + + processStateSet(_currentStateSet.get()); + + processArray("v", geo->getVertexArray(), m, false); + processArray("vn", geo->getNormalArray(), m, true); + processArray("vt", geo->getTexCoordArray(0)); // we support only tex-unit 0 + unsigned int normalIndex = 0; + for(unsigned int i = 0; i < geo->getNumPrimitiveSets(); ++i) + { + osg::PrimitiveSet* ps = geo->getPrimitiveSet(i); + + PrimitiveIndexWriter pif(_fout, geo, normalIndex, _lastVertexIndex, _lastNormalIndex, _lastTexIndex); + ps->accept(pif); + + if(geo->getNormalArray() && geo->getNormalBinding() == osg::Geometry::BIND_PER_PRIMITIVE_SET) + ++normalIndex; + } + if (geo->getVertexArray()) + _lastVertexIndex += geo->getVertexArray()->getNumElements(); + if (geo->getNormalArray()) + _lastNormalIndex += geo->getNormalArray()->getNumElements(); + if(geo->getTexCoordArray(0)) + _lastTexIndex += geo->getTexCoordArray(0)->getNumElements(); + +} + +void OBJWriterNodeVisitor::apply( osg::Geode &node ) +{ + + pushStateSet(node.getStateSet()); + _nameStack.push_back(node.getName()); + osg::Matrix m = osg::computeLocalToWorld(getNodePath()); + unsigned int count = node.getNumDrawables(); + for ( unsigned int i = 0; i < count; i++ ) + { + osg::Geometry *g = node.getDrawable( i )->asGeometry(); + if ( g != NULL ) + { + pushStateSet(g->getStateSet()); + + processGeometry(g,m); + + popStateSet(g->getStateSet()); + } + } + + + popStateSet(node.getStateSet()); + _nameStack.pop_back(); +} + + + diff --git a/src/osgPlugins/obj/OBJWriterNodeVisitor.h b/src/osgPlugins/obj/OBJWriterNodeVisitor.h new file mode 100644 index 000000000..7cf9648fd --- /dev/null +++ b/src/osgPlugins/obj/OBJWriterNodeVisitor.h @@ -0,0 +1,160 @@ +// -*-c++-*- + +/* + * Wavefront OBJ loader for Open Scene Graph + * + * Copyright (C) 2001 Ulrich Hertlein + * + * Modified by Robert Osfield to support per Drawable coord, normal and + * texture coord arrays, bug fixes, and support for texture mapping. + * + * Writing support added 2007 by Stephan Huber, http://digitalmind.de, + * some ideas taken from the dae-plugin + * + * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for + * real-time rendering of large 3D photo-realistic models. + * The OSG homepage is http://www.openscenegraph.org/ + */ + + #ifndef OBJ_WRITER_NODE_VISITOR_HEADER__ + #define OBJ_WRITER_NODE_VISITOR_HEADER__ + + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + + + +#include +#include + +class OBJWriterNodeVisitor: public osg::NodeVisitor { + + public: + OBJWriterNodeVisitor(std::ostream& fout, const std::string materialFileName = "") : + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _fout(fout), + _currentStateSet(new osg::StateSet()), + _lastVertexIndex(1), + _lastNormalIndex(1), + _lastTexIndex(1) + { + _fout << "# file written by OpenSceneGraph" << std::endl << std::endl; + + if (!materialFileName.empty()) { + _fout << "mtllib " << materialFileName << std::endl << std::endl; + } + } + + virtual void apply(osg::Geode &node); + + virtual void apply(osg::Group &node) + { + _nameStack.push_back( node.getName().empty() ? node.className() : node.getName() ); + _fout << std::endl; + _fout << "g " << getUniqueName() << std::endl; + + osg::NodeVisitor::traverse( node ); + _nameStack.pop_back(); + } + + void traverse (osg::Node &node) + { + pushStateSet(node.getStateSet()); + + osg::NodeVisitor::traverse( node ); + + popStateSet(node.getStateSet()); + } + + void pushStateSet(osg::StateSet* ss) + { + if (NULL!=ss) { + // Save our current stateset + _stateSetStack.push(_currentStateSet.get()); + + // merge with node stateset + _currentStateSet = static_cast(_currentStateSet->clone(osg::CopyOp::SHALLOW_COPY)); + _currentStateSet->merge(*ss); + } + } + + + void popStateSet(osg::StateSet* ss) + { + if (NULL!=ss) { + // restore the previous stateset + _currentStateSet = _stateSetStack.top(); + _stateSetStack.pop(); + } + } + + + void writeMaterials(std::ostream& fout); + + + + class OBJMaterial { + public: + OBJMaterial() {} + OBJMaterial(osg::Material* mat, osg::Texture* tex); + + osg::Vec4 diffuse, ambient, specular; + std::string image; + std::string name; + }; + + protected: + struct CompareStateSet + { + bool operator()(const osg::ref_ptr& ss1, const osg::ref_ptr& ss2) const + { + //std::cout << "CompareStateSet: " << ss1->compare(*ss2, false) << " " << ss1 << " " << ss2 << std::endl; + return ss1->compare(*ss2, true) < 0; + } + }; + + + private: + + void processGeometry(osg::Geometry* geo, osg::Matrix& m); + void processArray(const std::string& key, osg::Array* array, const osg::Matrix& m = osg::Matrix::identity(), bool isNormal = false); + + void processStateSet(osg::StateSet* stateset); + + std::string getUniqueName(const std::string& defaultValue = ""); + + typedef std::stack > StateSetStack; + typedef std::map< osg::ref_ptr, OBJMaterial, CompareStateSet> MaterialMap; + + + std::ostream& _fout; + std::list _nameStack; + StateSetStack _stateSetStack; + osg::ref_ptr _currentStateSet; + std::map _nameMap; + unsigned int _lastVertexIndex, _lastNormalIndex, _lastTexIndex; + MaterialMap _materialMap; + +}; + +#endif diff --git a/src/osgPlugins/obj/ReaderWriterOBJ.cpp b/src/osgPlugins/obj/ReaderWriterOBJ.cpp index b3f18027f..34d186316 100644 --- a/src/osgPlugins/obj/ReaderWriterOBJ.cpp +++ b/src/osgPlugins/obj/ReaderWriterOBJ.cpp @@ -8,6 +8,9 @@ * Modified by Robert Osfield to support per Drawable coord, normal and * texture coord arrays, bug fixes, and support for texture mapping. * + * Writing support added 2007 by Stephan Huber, http://digitalmind.de, + * some ideas taken from the dae-plugin + * * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for * real-time rendering of large 3D photo-realistic models. * The OSG homepage is http://www.openscenegraph.org/ @@ -41,6 +44,7 @@ #include #include "obj.h" +#include "OBJWriterNodeVisitor.h" #include #include @@ -58,6 +62,56 @@ public: virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const; virtual ReadResult readNode(std::istream& fin, const Options* options) const; + + virtual WriteResult writeObject(const osg::Object& obj,const std::string& fileName,const Options* options=NULL) const + { + const osg::Node* node = dynamic_cast(&obj); + if (node) + return writeNode(*node, fileName, options); + else + return WriteResult(WriteResult::FILE_NOT_HANDLED); + } + + virtual WriteResult writeNode(const osg::Node& node,const std::string& fileName,const Options* options =NULL) const + { + if (!acceptsExtension(osgDB::getFileExtension(fileName))) + return WriteResult(WriteResult::FILE_NOT_HANDLED); + + std::ofstream f(fileName.c_str()); + std::string materialFile = osgDB::getNameLessExtension(fileName) + ".mtl"; + OBJWriterNodeVisitor nv(f, osgDB::getSimpleFileName(materialFile)); + + // we must cast away constness + (const_cast(&node))->accept(nv); + + std::ofstream mf(materialFile.c_str()); + nv.writeMaterials(mf); + + return WriteResult(WriteResult::FILE_SAVED); + } + + + virtual WriteResult writeObject(const osg::Object& obj,std::ostream& fout,const Options* options=NULL) const + { + const osg::Node* node = dynamic_cast(&obj); + if (node) + return writeNode(*node, fout, options); + else + return WriteResult(WriteResult::FILE_NOT_HANDLED); + } + + virtual WriteResult writeNode(const osg::Node& node,std::ostream& fout,const Options* =NULL) const + { + // writing to a stream does not support materials + + OBJWriterNodeVisitor nv(fout); + + // we must cast away constness + (const_cast(&node))->accept(nv); + + return WriteResult(WriteResult::FILE_SAVED); + } + protected: