// 30 Oct 2002 // AC3D loader for models generated by the AC3D modeller (www.ac3d.org) // part of this source code were supplied by the AC3D project (Andy Colebourne) // eg the basic parsing of an AC3D file. // Conversion from AC3D scenegraph to OSG by GW Michel. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Exception.h" #include "Geode.h" namespace ac3d { osg::Node* readFile(std::istream& stream, const osgDB::ReaderWriter::Options* options); } class geodeVisitor : public osg::NodeVisitor { // collects geodes from scene sub-graph attached to 'this' public: geodeVisitor(): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} ~geodeVisitor() { _geodelist.clear();} // one apply for each type of Node that might be a user transform virtual void apply(osg::Geode& geode) { _geodelist.push_back(&geode); } virtual void apply(osg::Group& gp){ traverse(gp); // must continue subgraph traversal. } std::vector getGeodes() {return _geodelist;} protected: typedef std::vector Geodelist; Geodelist _geodelist; }; class ReaderWriterAC : public osgDB::ReaderWriter { public: virtual const char* className() const { return "AC3D Database Reader"; } virtual bool acceptsExtension(const std::string& extension) const { return osgDB::equalCaseInsensitive(extension,"ac"); } virtual ReadResult readNode(const std::string& file,const Options* options) const { std::string ext = osgDB::getFileExtension(file); if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; // GWM added Dec 2003 - get full path name (change in osgDB handling of files). std::string fileName = osgDB::findDataFile( file, options ); osg::notify(osg::INFO) << "osgDB ac3d reader: starting reading \"" << fileName << "\"" << std::endl; // Anders Backmann - correct return if path not found if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; // allocate per file data and start reading std::ifstream fin; fin.open(fileName.c_str(), std::ios::in); if (!fin.is_open()) return ReadResult::FILE_NOT_FOUND; // code for setting up the database path so that internally referenced file are // searched for on relative paths. osg::ref_ptr local_opt; if (options) local_opt = static_cast(options->clone(osg::CopyOp::DEEP_COPY_ALL)); else local_opt = new Options; local_opt->setDatabasePath(osgDB::getFilePath(fileName)); ReadResult result = readNode(fin, local_opt.get()); if (result.validNode()) result.getNode()->setName(fileName); return result; } virtual ReadResult readNode(std::istream& fin, const Options* options) const { std::string header; fin >> header; if (header.substr(0, 4) != "AC3D") return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED; return ac3d::readFile(fin, options); } virtual WriteResult writeNode(const osg::Node& node,const std::string& fileName, const Options* /*options*/) const { std::string ext = osgDB::getFileExtension(fileName); if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED; geodeVisitor vs; // this collects geodes. std::vectoriNumMaterials; const_cast(node).accept(vs); // this parses the tree to streamd Geodes std::vector glist=vs.getGeodes(); std::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary); // Write out the file header std::vector::iterator itr; fout << "AC3Db" << std::endl; // output the Materials for (itr=glist.begin();itr!= glist.end();itr++) { iNumMaterials.push_back(const_cast(static_cast(*itr))->ProcessMaterial(fout,itr-glist.begin())); } // output the Geometry unsigned int nfirstmat=0; fout << "OBJECT world" << std::endl; fout << "kids " << (glist.end()-glist.begin()) << std::endl; for (itr=glist.begin();itr!= glist.end();itr++) { const_cast(static_cast(*itr))->ProcessGeometry(fout,nfirstmat); nfirstmat+=iNumMaterials[itr-glist.begin()]; } fout.close(); return WriteResult::FILE_SAVED; } virtual WriteResult writeNode(const osg::Node& node,std::ostream& fout, const Options* opts) const { try { // write ac file. if(dynamic_cast(&node)) { const osg::Group *gp=dynamic_cast(&node); const unsigned int nch=gp->getNumChildren(); for (unsigned int i=0; igetChild(i)), fout, opts); } } else osg::notify(osg::WARN)<<"File must start with a geode "< g_readerWriter_AC_Proxy; namespace ac3d { enum { ObjectTypeNormal = 0, ObjectTypeGroup = 1, ObjectTypeLight = 2, SurfaceTypePolygon = 0, SurfaceTypeLineLoop = 1, SurfaceTypeLineStrip = 2, SurfaceShaded = 1<<4, SurfaceTwoSided = 1<<5 }; /// Returns a possibly quoted string given in the end of the current line in the stream static std::string readString(std::istream& stream) { std::string s; stream >> std::ws; if (stream.peek() != '\"') { // Not quoted, just read the string stream >> s; } else { // look for quoted strings // throw away the quote stream.get(); // extract characters until either an error happens or a quote is found while (stream.good()) { std::istream::char_type c; stream.get(c); if (c == '\"') break; s += c; } } return s; } static void setTranslucent(osg::StateSet* stateSet) { osg::BlendFunc* blendFunc = new osg::BlendFunc; blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); blendFunc->setDestination(osg::BlendFunc::ONE_MINUS_SRC_ALPHA); stateSet->setAttribute(blendFunc); stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } // Just a container to store an ac3d material class MaterialData { public: MaterialData() : mMaterial(new osg::Material), mColorArray(new osg::Vec4Array(1)) { } void readMaterial(std::istream& stream) { // note that this might be quoted std::string name = readString(stream); mMaterial->setName(name); std::string tmp; stream >> tmp; osg::Vec4 diffuse; stream >> diffuse[0] >> diffuse[1] >> diffuse[2]; mMaterial->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); stream >> tmp; osg::Vec4 ambient; stream >> ambient[0] >> ambient[1] >> ambient[2]; mMaterial->setAmbient(osg::Material::FRONT_AND_BACK, ambient); stream >> tmp; osg::Vec4 emmissive; stream >> emmissive[0] >> emmissive[1] >> emmissive[2]; mMaterial->setEmission(osg::Material::FRONT_AND_BACK, emmissive); stream >> tmp; osg::Vec4 specular; stream >> specular[0] >> specular[1] >> specular[2]; mMaterial->setSpecular(osg::Material::FRONT_AND_BACK, specular); stream >> tmp; float shininess; stream >> shininess; mMaterial->setShininess(osg::Material::FRONT_AND_BACK, shininess); stream >> tmp; float transparency; stream >> transparency; mMaterial->setTransparency(osg::Material::FRONT_AND_BACK, transparency); mTranslucent = 0 < transparency; // must correspond to the material we use for the color array below mMaterial->setColorMode(osg::Material::DIFFUSE); // this must be done past the transparency setting ... (*mColorArray)[0] = mMaterial->getDiffuse(osg::Material::FRONT_AND_BACK); } void toStateSet(osg::StateSet* stateSet) const { stateSet->setAttribute(mMaterial.get()); if (mTranslucent) setTranslucent(stateSet); } osg::Vec4Array* getColorArray() const { return mColorArray.get(); } private: osg::ref_ptr mMaterial; osg::ref_ptr mColorArray; bool mTranslucent; }; class TextureData { public: TextureData() : mTranslucent(false) { } bool setTexture(const std::string& name, const osgDB::ReaderWriter::Options* options) { mTexture2D = new osg::Texture2D; mTexture2D->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); mTexture2D->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); std::string absFileName = osgDB::findDataFile(name, options); if (absFileName.empty()) { osg::notify(osg::FATAL) << "osgDB ac3d reader: could not find texture \"" << name << "\"" << std::endl; return false; } mImage = osgDB::readImageFile(absFileName, options); if (!mImage.valid()) { osg::notify(osg::FATAL) << "osgDB ac3d reader: could not read texture \"" << name << "\"" << std::endl; return false; } mTexture2D->setImage(mImage.get()); mTranslucent = mImage->isImageTranslucent(); return true; } bool valid() const { return mImage.valid(); } std::string getFileName() const { if (!mImage.valid()) return std::string(); return mImage->getFileName(); } void toTextureStateSet(osg::StateSet* stateSet) const { if (!valid()) return; osg::TexEnv* texEnv = new osg::TexEnv; texEnv->setMode(osg::TexEnv::MODULATE); stateSet->setTextureAttribute(0, texEnv); stateSet->setTextureAttribute(0, mTexture2D.get()); stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); if (mTranslucent) setTranslucent(stateSet); } private: osg::ref_ptr mTexture2D; osg::ref_ptr mImage; bool mTranslucent; }; class FileData { public: FileData(const osgDB::ReaderWriter::Options* options) : mOptions(options), mLightIndex(1) { } TextureData toTextureData(const std::string& texName) { TextureDataMap::iterator i = mTextureStates.find(texName); if (i == mTextureStates.end()) mTextureStates[texName].setTexture(texName, mOptions.get()); return mTextureStates[texName]; } osg::Light* getNextLight() { osg::Light* light = new osg::Light; light->setLightNum(mLightIndex++); return light; } void addMaterial(const MaterialData& material) { mMaterials.push_back(material); } unsigned getNumMaterials() const { return mMaterials.size(); } const MaterialData& getMaterial(unsigned idx) const { return mMaterials[idx]; } private: /// Stores the ac3d file reader options, only used for reading texture files osg::ref_ptr mOptions; /// The list of ac3d MATERIALS std::vector mMaterials; /// Local per model texture attribute cache. /// ... images are usualy cached in the registries object cache typedef std::map TextureDataMap; TextureDataMap mTextureStates; /// Hack to include light nodes from ac3d into the scenegraph unsigned mLightIndex; }; struct RefData { RefData(const osg::Vec3& _weightedNormal, const osg::Vec2& _texCoord, bool _smooth) : weightedFlatNormal(_weightedNormal), weightedFlatNormalLength(_weightedNormal.length()), texCoord(_texCoord), smooth(_smooth) { } // weighted flat surface normal osg::Vec3 weightedFlatNormal; float weightedFlatNormalLength; osg::Vec2 texCoord; // resulting vertex normal osg::Vec3 finalNormal; // if zero no need to smooth unsigned smooth; }; struct VertexData { VertexData(const osg::Vec3& vertex) : _vertex(vertex) {} unsigned addRefData(const RefData& refData) { unsigned index = _refs.size(); _refs.push_back(refData); return index; } void collect(float cosCreaseAngle, const RefData& ref) { unsigned size = _refs.size(); for (unsigned i = 0; i < size; ++i) { if (_refs[i].smooth == ~0u) { float dot = _refs[i].weightedFlatNormal*ref.weightedFlatNormal; float lengths = _refs[i].weightedFlatNormalLength*ref.weightedFlatNormalLength; if (cosCreaseAngle*lengths <= dot) { // Ok put that into the current set _refs[i].smooth = ref.smooth; collect(cosCreaseAngle, _refs[i]); } } } } void smoothNormals(float cosCreaseAngle) { // compute sets of vertices smoothed to the same normal // if smooth is zero we do not need to smooth // in a first pass mark all refs not yet in a set to ~0u unsigned size = _refs.size(); for (unsigned i = 0; i < size; ++i) { if (_refs[i].smooth) { _refs[i].smooth = ~0u; } } // Now collect the sets unsigned currentSet = 1; for (unsigned i = 0; i < size; ++i) { if (_refs[i].smooth == ~0u) { _refs[i].smooth = currentSet++; collect(cosCreaseAngle, _refs[i]); } } // smooth and normalize the sets for (--currentSet; 0 < currentSet; --currentSet) { osg::Vec3 normal(0, 0, 0); for (unsigned i = 0; i < size; ++i) { if (_refs[i].smooth == currentSet) { normal += _refs[i].weightedFlatNormal; } } normal.normalize(); for (unsigned i = 0; i < size; ++i) { if (_refs[i].smooth == currentSet) { _refs[i].finalNormal = normal; } } } // normalize the ones which do not need smoothing for (unsigned i = 0; i < size; ++i) { if (_refs[i].smooth == 0) { _refs[i].finalNormal = _refs[i].weightedFlatNormal; _refs[i].finalNormal.normalize(); } } } osg::Vec3 _vertex; std::vector _refs; }; struct VertexIndex { VertexIndex(unsigned _vertexIndex = 0, unsigned _refIndex = 0) : vertexIndex(_vertexIndex), refIndex(_refIndex) { } unsigned vertexIndex; unsigned refIndex; }; class VertexSet : public osg::Referenced { public: VertexSet() : _dirty(true) { } void reserve(unsigned n) { _vertices.reserve(n); } unsigned size() const { return _vertices.size(); } void setCreaseAngle(float crease) { _dirty = true; if (crease <= 0) _cosCreaseAngle = 1; else if (180 <= crease) _cosCreaseAngle = -1; else _cosCreaseAngle = cosf(osg::DegreesToRadians(crease)); } void addVertex(const osg::Vec3& vertex) { _dirty = true; _vertices.push_back(vertex); } const osg::Vec3& getVertex(unsigned index) { return _vertices[index]._vertex; } const osg::Vec3& getVertex(const VertexIndex& vertexIndex) { return _vertices[vertexIndex.vertexIndex]._vertex; } const osg::Vec3& getNormal(const VertexIndex& vertexIndex) { if (_dirty) smoothNormals(); return _vertices[vertexIndex.vertexIndex]._refs[vertexIndex.refIndex].finalNormal; } const osg::Vec2& getTexCoord(const VertexIndex& vertexIndex) { return _vertices[vertexIndex.vertexIndex]._refs[vertexIndex.refIndex].texCoord; } VertexIndex addRefData(unsigned i, const RefData& refData) { if (_vertices.size() <= i) { osg::notify(osg::FATAL) << "osgDB ac3d reader: internal error, got invalid vertex index!" << std::endl; return VertexIndex(0, 0); } _dirty = true; return VertexIndex(i, _vertices[i].addRefData(refData)); } private: void smoothNormals() { std::vector::iterator i; for (i = _vertices.begin(); i != _vertices.end(); ++i) { i->smoothNormals(_cosCreaseAngle); } _dirty = false; } std::vector _vertices; float _cosCreaseAngle; bool _dirty; }; class PrimitiveBin : public osg::Referenced { public: PrimitiveBin(unsigned flags, VertexSet* vertexSet) : _geode(new osg::Geode), _vertexSet(vertexSet), _flags(flags) { } virtual bool beginPrimitive(unsigned nRefs) = 0; virtual bool vertex(unsigned vertexIndex, const osg::Vec2& texCoord) = 0; virtual bool endPrimitive() = 0; virtual osg::Geode* finalize(const MaterialData& material, const TextureData& textureData) = 0; protected: bool isLineLoop() const { return (_flags & SurfaceTypeLineLoop)!=0; } bool isLineStrip() const { return (_flags & SurfaceTypeLineStrip)!=0; } bool isTwoSided() const { return (_flags & SurfaceTwoSided)!=0; } bool isSmooth() const { return (_flags & SurfaceShaded)!=0; } osg::ref_ptr _geode; osg::ref_ptr _vertexSet; private: unsigned _flags; }; class LineBin : public PrimitiveBin { private: osg::ref_ptr _geometry; osg::ref_ptr _vertices; osg::ref_ptr _texCoords; struct Ref { osg::Vec2 texCoord; unsigned index; }; std::vector _refs; public: LineBin(unsigned flags, VertexSet* vertexSet) : PrimitiveBin(flags, vertexSet), _geometry(new osg::Geometry), _vertices(new osg::Vec3Array), _texCoords(new osg::Vec2Array) { _geometry->setVertexArray(_vertices.get()); _geometry->setTexCoordArray(0, _texCoords.get()); osg::StateSet* stateSet = _geode->getOrCreateStateSet(); stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } virtual bool beginPrimitive(unsigned nRefs) { // Check if we have enough for a line or someting broken ... if (nRefs < 2) { osg::notify(osg::WARN) << "osgDB ac3d reader: detected line with less than 2 vertices!" << std::endl; return false; } _refs.reserve(nRefs); _refs.resize(0); return true; } virtual bool vertex(unsigned vertexIndex, const osg::Vec2& texCoord) { Ref ref; ref.index = vertexIndex; ref.texCoord = texCoord; _refs.push_back(ref); return true; } virtual bool endPrimitive() { GLint type; if (isLineLoop()) type = osg::PrimitiveSet::LINE_LOOP; else if (isLineStrip()) type = osg::PrimitiveSet::LINE_STRIP; else { osg::notify(osg::FATAL) << "osgDB ac3d reader: non surface flags in surface bin!" << std::endl; return false; } unsigned nRefs = _refs.size(); unsigned start = _vertices->size(); for (unsigned i = 0; i < nRefs; ++i) { osg::Vec3 vertex = _vertexSet->getVertex(_refs[i].index); _vertices->push_back(vertex); _texCoords->push_back(_refs[i].texCoord); } _geometry->addPrimitiveSet(new osg::DrawArrays(type, start, nRefs)); return true; } virtual osg::Geode* finalize(const MaterialData& material, const TextureData& textureData) { _geode->addDrawable(_geometry.get()); material.toStateSet(_geode->getOrCreateStateSet()); _geometry->setColorArray(material.getColorArray()); _geometry->setColorBinding(osg::Geometry::BIND_OVERALL); _geometry->setNormalBinding(osg::Geometry::BIND_OFF); return _geode.get(); } }; class SurfaceBin : public PrimitiveBin { private: struct Ref { osg::Vec2 texCoord; unsigned index; }; std::vector _refs; struct TriangleData { VertexIndex index[3]; }; std::vector _triangles; struct QuadData { VertexIndex index[4]; }; std::vector _quads; struct PolygonData { std::vector index; }; std::vector _polygons; std::vector _toTessellatePolygons; public: SurfaceBin(unsigned flags, VertexSet *vertexSet) : PrimitiveBin(flags, vertexSet) { } virtual bool beginPrimitive(unsigned nRefs) { _refs.reserve(nRefs); _refs.clear(); // Check if we have enough for a line or someting broken ... if (nRefs < 3) { osg::notify(osg::WARN) << "osgDB ac3d reader: detected surface with less than 3 vertices!" << std::endl; return false; } return true; } virtual bool vertex(unsigned vertexIndex, const osg::Vec2& texCoord) { Ref ref; ref.index = vertexIndex; ref.texCoord = texCoord; _refs.push_back(ref); return true; } virtual bool endPrimitive() { unsigned nRefs = _refs.size(); // Compute the normal times the enclosed area. // During that check if the surface is convex. If so, put in the surface as such. bool needTessellation = false; osg::Vec3 prevEdgeNormal; osg::Vec3 weightedNormal(0, 0, 0); osg::Vec3 v0 = _vertexSet->getVertex(_refs[0].index); for (unsigned i = 2; i < nRefs; ++i) { osg::Vec3 side1 = _vertexSet->getVertex(_refs[i-1].index) - v0; osg::Vec3 side2 = _vertexSet->getVertex(_refs[i].index) - v0; osg::Vec3 newNormal = side1^side2; if (!needTessellation) { if (3 < nRefs && newNormal*weightedNormal < 0) { needTessellation = true; } if (i < 3) { prevEdgeNormal = newNormal; } else // if (3 <= i) // due to the for loop { osg::Vec3 sideim1 = _vertexSet->getVertex(_refs[i-1].index) - _vertexSet->getVertex(_refs[i-2].index); osg::Vec3 sidei = _vertexSet->getVertex(_refs[i].index) - _vertexSet->getVertex(_refs[i-2].index); osg::Vec3 edgeNormal = sideim1^sidei; if (edgeNormal*prevEdgeNormal < 0) { needTessellation = true; } prevEdgeNormal = edgeNormal; } } weightedNormal += newNormal; } if (needTessellation) { unsigned polygonIndex = _toTessellatePolygons.size(); _toTessellatePolygons.resize(polygonIndex + 1); for (unsigned i = 0; i < nRefs; ++i) { RefData refData(weightedNormal, _refs[i].texCoord, isSmooth()); VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData); _toTessellatePolygons[polygonIndex].index.push_back(vertexIndex); } } else if (nRefs == 3) { unsigned triangleIndex = _triangles.size(); _triangles.resize(triangleIndex + 1); for (unsigned i = 0; i < 3; ++i) { RefData refData(weightedNormal, _refs[i].texCoord, isSmooth()); VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData); _triangles[triangleIndex].index[i] = vertexIndex; } } else if (nRefs == 4) { unsigned quadIndex = _quads.size(); _quads.resize(quadIndex + 1); for (unsigned i = 0; i < 4; ++i) { RefData refData(weightedNormal, _refs[i].texCoord, isSmooth()); VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData); _quads[quadIndex].index[i] = vertexIndex; } } else { unsigned polygonIndex = _polygons.size(); _polygons.resize(polygonIndex + 1); for (unsigned i = 0; i < nRefs; ++i) { RefData refData(weightedNormal, _refs[i].texCoord, isSmooth()); VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData); _polygons[polygonIndex].index.push_back(vertexIndex); } } return true; } void pushVertex(const VertexIndex& vertexIndex, osg::Vec3Array* vertexArray, osg::Vec3Array* normalArray, osg::Vec2Array* texcoordArray) { vertexArray->push_back(_vertexSet->getVertex(vertexIndex)); normalArray->push_back(_vertexSet->getNormal(vertexIndex)); if (texcoordArray) texcoordArray->push_back(_vertexSet->getTexCoord(vertexIndex)); } virtual osg::Geode* finalize(const MaterialData& material, const TextureData& textureData) { osg::StateSet* stateSet = _geode->getOrCreateStateSet(); material.toStateSet(stateSet); textureData.toTextureStateSet(stateSet); stateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON); // Single- or doublesided culling if (isTwoSided()) { stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); } else { osg::CullFace* cullFace = new osg::CullFace; cullFace->setMode(osg::CullFace::BACK); stateSet->setAttribute(cullFace); stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON); } // Flat or smooth shading osg::ShadeModel* shadeModel = new osg::ShadeModel; if (isSmooth()) shadeModel->setMode(osg::ShadeModel::SMOOTH); else shadeModel->setMode(osg::ShadeModel::FLAT); stateSet->setAttribute(shadeModel); // Set up the arrays, allways store texture coords, may be we need them later ... osg::Geometry* geometry = new osg::Geometry; _geode->addDrawable(geometry); geometry->setColorArray(material.getColorArray()); geometry->setColorBinding(osg::Geometry::BIND_OVERALL); geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); osg::Vec3Array* normalArray = new osg::Vec3Array; geometry->setNormalArray(normalArray); osg::Vec3Array* vertexArray = new osg::Vec3Array; geometry->setVertexArray(vertexArray); osg::Vec2Array* texcoordArray = 0; if (textureData.valid()) { texcoordArray = new osg::Vec2Array; geometry->setTexCoordArray(0, texcoordArray); } // At first handle the the polygons to tessellate, fix them and append the other polygons later if (!_toTessellatePolygons.empty()) { unsigned start = vertexArray->size(); osg::DrawArrayLengths* drawArrayLengths = new osg::DrawArrayLengths(osg::PrimitiveSet::POLYGON, start); drawArrayLengths->reserve(_toTessellatePolygons.size()); for (unsigned i = 0; i < _toTessellatePolygons.size(); ++i) { for (unsigned j = 0; j < _toTessellatePolygons[i].index.size(); ++j) { pushVertex(_toTessellatePolygons[i].index[j], vertexArray, normalArray, texcoordArray); } drawArrayLengths->push_back(_toTessellatePolygons[i].index.size()); } geometry->addPrimitiveSet(drawArrayLengths); osgUtil::Tessellator Tessellator; Tessellator.retessellatePolygons(*geometry); } // handle triangles if (!_triangles.empty()) { unsigned start = vertexArray->size(); for (unsigned i = 0; i < _triangles.size(); ++i) { for (unsigned j = 0; j < 3; ++j) { pushVertex(_triangles[i].index[j], vertexArray, normalArray, texcoordArray); } } osg::DrawArrays* drawArray = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, start, 3*_triangles.size()); geometry->addPrimitiveSet(drawArray); } // handle quads if (!_quads.empty()) { unsigned start = vertexArray->size(); for (unsigned i = 0; i < _quads.size(); ++i) { for (unsigned j = 0; j < 4; ++j) { pushVertex(_quads[i].index[j], vertexArray, normalArray, texcoordArray); } } osg::DrawArrays* drawArray = new osg::DrawArrays(osg::PrimitiveSet::QUADS, start, 4*_quads.size()); geometry->addPrimitiveSet(drawArray); } // handle polygons if (!_polygons.empty()) { unsigned start = vertexArray->size(); osg::DrawArrayLengths* drawArrayLengths = new osg::DrawArrayLengths(osg::PrimitiveSet::POLYGON, start); drawArrayLengths->reserve(_polygons.size()); for (unsigned i = 0; i < _polygons.size(); ++i) { for (unsigned j = 0; j < _polygons[i].index.size(); ++j) { pushVertex(_polygons[i].index[j], vertexArray, normalArray, texcoordArray); } drawArrayLengths->push_back(_polygons[i].index.size()); } geometry->addPrimitiveSet(drawArrayLengths); } return _geode.get(); } }; struct Bins { PrimitiveBin* getOrCreatePrimitiveBin(unsigned flags, VertexSet* vertexSet) { if ((flags & SurfaceTypeLineLoop) || (flags & SurfaceTypeLineStrip)) { if (!lineBin.valid()) { lineBin = new LineBin(flags, vertexSet); } return lineBin.get(); } else if (flags & SurfaceShaded) { if (flags & SurfaceTwoSided) { if (!smoothDoubleSurfaceBin.valid()) { smoothDoubleSurfaceBin = new SurfaceBin(flags, vertexSet); } return smoothDoubleSurfaceBin.get(); } else { if (!smoothSingleSurfaceBin.valid()) { smoothSingleSurfaceBin = new SurfaceBin(flags, vertexSet); } return smoothSingleSurfaceBin.get(); } } else { if (flags & SurfaceTwoSided) { if (!flatDoubleSurfaceBin.valid()) { flatDoubleSurfaceBin = new SurfaceBin(flags, vertexSet); } return flatDoubleSurfaceBin.get(); } else { if (!flatSingleSurfaceBin.valid()) { flatSingleSurfaceBin = new SurfaceBin(flags, vertexSet); } return flatSingleSurfaceBin.get(); } } } void finalize(osg::Group* group, const MaterialData& material, const TextureData& textureData) { if (lineBin.valid()) { group->addChild(lineBin->finalize(material, textureData)); } if (smoothDoubleSurfaceBin.valid()) { group->addChild(smoothDoubleSurfaceBin->finalize(material, textureData)); } if (smoothSingleSurfaceBin.valid()) { group->addChild(smoothSingleSurfaceBin->finalize(material, textureData)); } if (flatDoubleSurfaceBin.valid()) { group->addChild(flatDoubleSurfaceBin->finalize(material, textureData)); } if (flatSingleSurfaceBin.valid()) { group->addChild(flatSingleSurfaceBin->finalize(material, textureData)); } } private: osg::ref_ptr lineBin; osg::ref_ptr flatDoubleSurfaceBin; osg::ref_ptr flatSingleSurfaceBin; osg::ref_ptr smoothDoubleSurfaceBin; osg::ref_ptr smoothSingleSurfaceBin; }; osg::Node* readObject(std::istream& stream, FileData& fileData, const osg::Matrix& parentTransform, TextureData textureData) { // most of this logic came from Andy Colebourne (developer of the AC3D editor) so it had better be right! // The transform configured in this current object level osg::Matrix transform; // The vertex pool in this object osg::ref_ptr vertexSet = new VertexSet; osg::ref_ptr group = new osg::Group; osg::Vec2 textureOffset(0, 0); osg::Vec2 textureRepeat(1, 1); float creaseAngle = 61; unsigned objectType = ObjectTypeGroup; while (!stream.eof() && stream.good()) { std::string token; stream >> token; if (token == "MATERIAL") { MaterialData mat; mat.readMaterial(stream); fileData.addMaterial(mat); } else if (token == "OBJECT") { std::string type; stream >> type; if (type == "group") objectType = ObjectTypeGroup; else if (type == "light") objectType = ObjectTypeLight; else if (type == "world") objectType = ObjectTypeGroup; else objectType = ObjectTypeNormal; } else if (token == "crease") { stream >> creaseAngle; } else if (token == "data") { int len; stream >> len; std::vector tmp(len); stream.read(&(tmp[0]), len); } else if (token == "name") { group->setName(readString(stream)); } else if (token == "texture") { // read the texture name std::string texname = readString(stream); // strip the path to the texture, just look in the directory we read the ac file std::string::size_type p = texname.rfind('\\'); if (p != std::string::npos) texname = texname.substr(p+1, std::string::npos); p = texname.rfind('/'); if (p != std::string::npos) texname = texname.substr(p+1, std::string::npos); textureData = fileData.toTextureData(texname); } else if (token == "texrep") { stream >> textureRepeat[0] >> textureRepeat[1]; } else if (token == "texoff") { stream >> textureOffset[0] >> textureOffset[1]; } else if (token == "rot") { for (unsigned n = 0; n < 3; ++n) for (unsigned m = 0; m < 3; ++m) stream >> transform(m, n); } else if (token == "loc") { for (unsigned n = 0; n < 3; ++n) stream >> transform(3, n); } else if (token == "url") { std::string url; stream >> url; group->addDescription(url); } else if (token == "numvert") { osg::Matrix currentTransform = transform*parentTransform; unsigned num; stream >> num; if (num != 0) { vertexSet->reserve(num); for (unsigned n = 0; n < num; ++n) { osg::Vec3 p; stream >> p[0] >> p[1] >> p[2]; vertexSet->addVertex(currentTransform.preMult(p)); } } } else if (token == "numsurf") { unsigned num; stream >> num; if (0 < num) { // list of materials required- generate one geode per material std::vector primitiveBins(fileData.getNumMaterials()); vertexSet->setCreaseAngle(creaseAngle); for (unsigned n = 0; n < num; ++n) { std::string token; stream >> token; if (token != "SURF") { osg::notify(osg::FATAL) << "osgDB ac3d reader: expected SURF line while reading object \"" << group->getName() << "\"!" << std::endl; return group.release(); } stream >> token; unsigned flags = strtol(token.c_str(), NULL, 0); stream >> token; if (token != "mat") { osg::notify(osg::FATAL) << "osgDB ac3d reader: expected mat line while reading object \"" << group->getName() << "\"!" << std::endl; return group.release(); } // read the material index unsigned matIdx; stream >> matIdx; if (primitiveBins.size() <= matIdx) { osg::notify(osg::FATAL) << "osgDB ac3d reader: invalid material number while reading object \"" << group->getName() << "\"" << std::endl; return group.release(); } // now get the correct PrimitiveBin PrimitiveBin* primitiveBin = 0; primitiveBin = primitiveBins[matIdx].getOrCreatePrimitiveBin(flags, vertexSet.get()); if (!primitiveBin) { osg::notify(osg::FATAL) << "osgDB ac3d reader: unexpected primitive flags while reading object \"" << group->getName() << "\"" << std::endl; return group.release(); } // read the refs stream >> token; if (token != "refs") { osg::notify(osg::FATAL) << "osgDB ac3d reader: expected refs line while reading object \"" << group->getName() << "\"" << std::endl; return group.release(); } unsigned nRefs = 0; stream >> nRefs; if (!stream) { osg::notify(osg::FATAL) << "osgDB ac3d reader: could not read number of refs while reading object \"" << group->getName() << "\"" << std::endl; return group.release(); } // in case this is an invalid refs count for this primitive // read further, but do not store that primitive bool acceptPrimitive = primitiveBin->beginPrimitive(nRefs); for (unsigned i = 0; i < nRefs; ++i) { // Read the vertex index unsigned index; stream >> index; if (vertexSet->size() <= index) { osg::notify(osg::FATAL) << "osgDB ac3d reader: invalid ref vertex index while reading object \"" << group->getName() << "\"" << std::endl; return group.release(); } // Read the texture corrdinates osg::Vec2 texCoord; stream >> texCoord[0] >> texCoord[1]; if (!stream) { osg::notify(osg::WARN) << "osgDB ac3d reader: could not parse texture coords while reading object \"" << group->getName() << "\" setting to (0,0)" << std::endl; stream.clear(); std::string dummy; std::getline(stream, dummy); } if (acceptPrimitive) { texCoord[0] = textureOffset[0] + texCoord[0]*textureRepeat[0]; texCoord[1] = textureOffset[1] + texCoord[1]*textureRepeat[1]; if (!primitiveBin->vertex(index, texCoord)) { return group.release(); } } } if (acceptPrimitive) { if (!primitiveBin->endPrimitive()) { return group.release(); } } } for (unsigned i = 0; i < primitiveBins.size(); ++i) primitiveBins[i].finalize(group.get(), fileData.getMaterial(i), textureData); } } else if (token == "kids") { unsigned num; stream >> num; if (num != 0) { for (unsigned n = 0; n < num; n++) { osg::Node *k = readObject(stream, fileData, transform*parentTransform, textureData); if (k == 0) { osg::notify(osg::FATAL) << "osgDB ac3d reader: error reading child object" << std::endl; return group.release(); } else { osg::LightSource *ls = dynamic_cast(k); if (ls) { osg::StateSet* lightStateSet = group->getOrCreateStateSet(); group->setStateSet(lightStateSet); group->setCullingActive(false); ls->setStateSetModes(*lightStateSet, osg::StateAttribute::ON); } group->addChild(k); } } } else if (objectType == ObjectTypeLight) { // add a light source to the scene 1 Nov 2003 osg::Light* ac3dLight = fileData.getNextLight(); osg::Matrix tt = transform*parentTransform; ac3dLight->setPosition(osg::Vec4(tt(3, 0), tt(3, 1), tt(3, 2), 1)); ac3dLight->setDirection(osg::Matrix::transform3x3(osg::Vec3(0.0f, 0.0f, -1.0f), tt)); ac3dLight->setAmbient(osg::Vec4(0.5f,0.5f,0.5f,1.0f)); ac3dLight->setDiffuse(osg::Vec4(0.5f,0.5f,0.5f,1.0f)); ac3dLight->setSpecular(osg::Vec4(1.0f,1.0f,0.5f,1.0f)); osg::LightSource* ac3dLightSource = new osg::LightSource; ac3dLightSource->setLight(ac3dLight); ac3dLightSource->setLocalStateSetModes(osg::StateAttribute::ON); // for some mad reason, you need to set this so that the light works. WHY? return ac3dLightSource; } return group.release(); } } return group.release(); } osg::Node* readFile(std::istream& stream, const osgDB::ReaderWriter::Options* options) { FileData fileData(options); osg::Matrix idetityTransform; osg::Node* node = readObject(stream, fileData, idetityTransform, TextureData()); if (node) node->setName("World"); return node; } } // namespace ac3d