// -*-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. * * 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/ */ #if defined(_MSC_VER) #pragma warning( disable : 4786 ) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "glm.h" class ReaderWriterOBJ : public osgDB::ReaderWriter { public: ReaderWriterOBJ() { } virtual const char* className() { return "Wavefront OBJ Reader"; } virtual bool acceptsExtension(const std::string& extension) { return (extension == "obj"); } virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*); protected: osg::Drawable* makeDrawable(GLMmodel* obj, GLMgroup* grp, osg::StateSet**); }; // register with Registry to instantiate the above reader/writer. osgDB::RegisterReaderWriterProxy g_objReaderWriterProxy; // read file and convert to OSG. osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*) { GLMmodel* obj = glmReadOBJ((char*) fileName.c_str()); if (!obj) return ReadResult::FILE_NOT_HANDLED; std::string directory = osgDB::getFilePath(fileName); osg::notify(osg::INFO) << "vertices " << obj->numvertices << std::endl; osg::notify(osg::INFO) << "normals " << obj->numnormals << std::endl; osg::notify(osg::INFO) << "texcoords " << obj->numtexcoords << std::endl; osg::notify(osg::INFO) << "face normals " << obj->numfacetnorms << std::endl; osg::notify(osg::INFO) << "tris " << obj->numtriangles << std::endl; osg::notify(osg::INFO) << "materials " << obj->nummaterials << std::endl; osg::notify(osg::INFO) << "groups " << obj->numgroups << std::endl; if (obj->numnormals==0) { osg::notify(osg::NOTICE) << "No normals in .obj file, automatically calculating normals..."<< std::endl; glmFacetNormals(obj); glmVertexNormals(obj,90.0f); } unsigned int i; // materials osg::StateSet** osg_mtl = NULL; if (obj->nummaterials > 0) { osg_mtl = new osg::StateSet*[obj->nummaterials]; for (i = 0; i < obj->nummaterials; i++) { GLMmaterial* omtl = &(obj->materials[i]); osg::notify(osg::DEBUG_INFO) << "mtl: " << omtl->name << std::endl; osg::StateSet* stateset = new osg::StateSet; osg_mtl[i] = stateset; osg::Material* mtl = new osg::Material; mtl->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(omtl->ambient[0], omtl->ambient[1], omtl->ambient[2], omtl->ambient[3])); mtl->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(omtl->diffuse[0], omtl->diffuse[1], omtl->diffuse[2], omtl->diffuse[3])); mtl->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(omtl->specular[0], omtl->specular[1], omtl->specular[2], omtl->specular[3])); mtl->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4(omtl->emmissive[0], omtl->emmissive[1], omtl->emmissive[2], omtl->emmissive[3])); // note, osg shininess scales between 0.0 and 1.0. mtl->setShininess(osg::Material::FRONT_AND_BACK, omtl->shininess/128.0f); stateset->setAttribute(mtl); if (omtl->textureName) { std::string fileName = osgDB::findFileInDirectory(omtl->textureName,directory,true); if (!fileName.empty()) { osg::Image* osg_image = osgDB::readImageFile(fileName.c_str()); if (osg_image) { osg::Texture* osg_texture = new osg::Texture; osg_texture->setImage(osg_image); stateset->setAttributeAndModes(osg_texture,osg::StateAttribute::ON); if (omtl->textureReflection) { osg::TexGen* osg_texgen = new osg::TexGen; osg_texgen->setMode(osg::TexGen::SPHERE_MAP); stateset->setAttributeAndModes(osg_texgen,osg::StateAttribute::ON); } } else { osg::notify(osg::NOTICE) << "Warning: Cannot create texture "<textureName<< std::endl; } } else { osg::notify(osg::WARN) << "texture '"<textureName<<"' not found"<< std::endl; } } } } // toplevel group or transform osg::Group* osg_top = NULL; if (obj->position[0] != 0.0f || obj->position[2] != 0.0f || obj->position[2] != 0.0f) { osg::Transform* xform = new osg::Transform; // note obj_x -> osg_x, // obj_y -> osg_z, // obj_z -> osg_y, xform->setMatrix(osg::Matrix::translate(obj->position[0], obj->position[2], obj->position[1])); osg_top = xform; } else osg_top = new osg::Group; osg_top->setName(obj->pathname); // subgroups // XXX one Geode per group is probably not necessary... GLMgroup* ogrp = obj->groups; while (ogrp) { if (ogrp->numtriangles > 0) { osg::Geode* osg_geo = new osg::Geode; osg_geo->setName(ogrp->name); osg_geo->addDrawable(makeDrawable(obj,ogrp, osg_mtl)); osg_top->addChild(osg_geo); } ogrp = ogrp->next; } // free glmDelete(obj); if (osg_mtl) delete[] osg_mtl; return osg_top; } // make drawable from OBJ group osg::Drawable* ReaderWriterOBJ::makeDrawable(GLMmodel* obj, GLMgroup* grp, osg::StateSet** mtl) { GLMtriangle* tris = obj->triangles; unsigned int ntris = grp->numtriangles; unsigned int i = 0; // geoset osg::GeoSet* gset = new osg::GeoSet; // primitives are only triangles gset->setPrimType(osg::GeoSet::TRIANGLES); gset->setNumPrims(ntris); int* primLen = new int[ntris]; gset->setPrimLengths(primLen); // the following code for mapping the coords, normals and texcoords // is complicated greatly by the need to create seperate out the // sets of coords etc for each drawable. typedef std::vector IndexVec; // fill an vector full of 0's, one for each vertex. IndexVec vcount(obj->numvertices+1,0); IndexVec ncount(obj->numnormals+1,0); IndexVec tcount(obj->numtexcoords+1,0); bool needNormals = obj->normals && obj->normals>0; bool needTexcoords = obj->texcoords && obj->numtexcoords>0; // first count the number of vertices used in this group. for (i = 0; i < ntris; i++) { GLMtriangle* tri = &(tris[grp->triangles[i]]); // increment the count once for each traingle corner. ++vcount[tri->vindices[0]]; ++vcount[tri->vindices[1]]; ++vcount[tri->vindices[2]]; if (needNormals) { ++ncount[tri->nindices[0]]; ++ncount[tri->nindices[1]]; ++ncount[tri->nindices[2]]; } if (needTexcoords) { ++tcount[tri->tindices[0]]; ++tcount[tri->tindices[1]]; ++tcount[tri->tindices[2]]; } } IndexVec::iterator itr; int numCoords = 0; for(itr=vcount.begin(); itr!=vcount.end(); ++itr) { if ((*itr)>0) ++numCoords; } if (numCoords==0) return NULL; int numNormals = 0; for(itr=ncount.begin(); itr!=ncount.end(); ++itr) { if ((*itr)>0) ++numNormals; } int numTexcoords = 0; for(itr=tcount.begin(); itr!=tcount.end(); ++itr) { if ((*itr)>0) ++numTexcoords; } // allocate drawables vertices. osg::Vec3* coords = new osg::Vec3[numCoords]; // allocate drawables normals. osg::Vec3* normals = NULL; if (numNormals>0) normals = new osg::Vec3[numNormals]; // allocate drawables textcoords. osg::Vec2* texcoords = NULL; if (numTexcoords>0) texcoords = new osg::Vec2[numTexcoords]; // fill an vector full of 0's, one for each vertex. IndexVec vmapping(obj->numvertices+1,-1); IndexVec nmapping(obj->numnormals+1,-1); IndexVec tmapping(obj->numtexcoords+1,-1); int coordCount = 0; for (i = 0; i < vcount.size(); i++) { if (vcount[i]>0) { // note obj_x -> osg_x, // obj_y -> -osg_z, // obj_z -> osg_y, coords[coordCount].set(obj->vertices[i*3+0], -obj->vertices[i*3+2], obj->vertices[i*3+1]); vmapping[i]=coordCount; ++coordCount; } } int normCount = 0; for (i = 0; i < ncount.size(); i++) { if (ncount[i]>0) { // note obj_x -> osg_x, // obj_y -> osg_z, // obj_z -> osg_y, // to rotate the about x axis to have model z upwards. normals[normCount].set(obj->normals[i*3+0], -obj->normals[i*3+2], obj->normals[i*3+1]); nmapping[i]=normCount; ++normCount; } } int texCount = 0; for (i = 0; i < tcount.size(); i++) { if (tcount[i]>0) { texcoords[texCount].set(obj->texcoords[i*2+0], obj->texcoords[i*2+1]); tmapping[i]=texCount; ++texCount; } } // index arrays unsigned int* cindex = NULL; cindex = new unsigned int[ntris * 3]; unsigned int* nindex = NULL; if (normals) nindex = new unsigned int[ntris * 3]; unsigned int* tindex = NULL; if (texcoords) tindex = new unsigned int[ntris * 3]; // setup arrays for (i = 0; i < ntris; i++) { primLen[i] = 3; GLMtriangle* tri = &(tris[grp->triangles[i]]); cindex[i*3+0] = vmapping[tri->vindices[0]]; cindex[i*3+1] = vmapping[tri->vindices[1]]; cindex[i*3+2] = vmapping[tri->vindices[2]]; if (nindex) { nindex[i*3+0] = nmapping[tri->nindices[0]]; nindex[i*3+1] = nmapping[tri->nindices[1]]; nindex[i*3+2] = nmapping[tri->nindices[2]]; } if (tindex) { tindex[i*3+0] = tmapping[tri->tindices[0]]; tindex[i*3+1] = tmapping[tri->tindices[1]]; tindex[i*3+2] = tmapping[tri->tindices[2]]; } } if (coords && cindex) gset->setCoords(coords, cindex); if (normals && nindex) { gset->setNormals(normals, nindex); gset->setNormalBinding(osg::GeoSet::BIND_PERVERTEX); } if (texcoords && tindex) { gset->setTextureCoords(texcoords, tindex); gset->setTextureBinding(osg::GeoSet::BIND_PERVERTEX); } // state and material (if any) if (mtl) { gset->setStateSet(mtl[grp->material]); } else osg::notify(osg::INFO) << "Group " << grp->name << " has no material" << std::endl; return gset; }