// -*-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: ValueVisitor& operator = (const ValueVisitor&) { return *this; } 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 ObjPrimitiveIndexWriter : public osg::PrimitiveIndexFunctor { public: ObjPrimitiveIndexWriter(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* ) {} virtual void setVertexArray(unsigned int,const osg::Vec2d*) {} virtual void setVertexArray(unsigned int ,const osg::Vec3d* ) {} virtual void setVertexArray(unsigned int,const osg::Vec4d* ) {} 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 ObjPrimitiveIndexWriter::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, 0u)); 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(osg::ref_ptr(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); ObjPrimitiveIndexWriter 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(); }