root/OpenSceneGraph/trunk/src/osgPlugins/obj/ReaderWriterOBJ.cpp @ 3282

Revision 3282, 20.4 kB (checked in by robert, 10 years ago)

Improvements to the handling of OBJ files

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1// -*-c++-*-
2
3/*
4 * Wavefront OBJ loader for Open Scene Graph
5 *
6 * Copyright (C) 2001 Ulrich Hertlein <u.hertlein@web.de>
7 *
8 * Modified by Robert Osfield to support per Drawable coord, normal and
9 * texture coord arrays, bug fixes, and support for texture mapping.
10 *
11 * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for
12 * real-time rendering of large 3D photo-realistic models.
13 * The OSG homepage is http://www.openscenegraph.org/
14 */
15
16#if defined(_MSC_VER)
17    #pragma warning( disable : 4786 )
18#endif
19
20#include <string>
21
22#include <osg/Notify>
23#include <osg/Node>
24#include <osg/MatrixTransform>
25#include <osg/Geode>
26
27#include <osg/Geometry>
28#include <osg/StateSet>
29#include <osg/Material>
30#include <osg/Texture2D>
31#include <osg/TexGen>
32#include <osg/TexMat>
33
34#include <osgDB/Registry>
35#include <osgDB/ReadFile>
36#include <osgDB/FileUtils>
37#include <osgDB/FileNameUtils>
38
39#include <osgUtil/TriStripVisitor>
40#include <osgUtil/SmoothingVisitor>
41
42#include "glm.h"
43
44#include <map>
45#include <set>
46
47template <class T>
48struct DerefLess
49{
50    bool operator ()(T lhs,T rhs) const { return *lhs < *rhs; }
51};
52
53
54class ReaderWriterOBJ : public osgDB::ReaderWriter
55{
56public:
57    ReaderWriterOBJ() { }
58
59    virtual const char* className() { return "Wavefront OBJ Reader"; }
60    virtual bool acceptsExtension(const std::string& extension) {
61        return osgDB::equalCaseInsensitive(extension,"obj");
62    }
63
64    virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*);
65
66protected:
67
68    enum DrawableMode
69    {
70        DUPLICATE_COORDS,
71        USE_SEPERATE_INDICES,
72    };
73
74    osg::Drawable* makeDrawable(DrawableMode drawableMode, GLMmodel* obj, GLMgroup* grp);
75    osg::Drawable* makeDrawable_duplicateCoords(GLMmodel* obj, GLMgroup* grp);
76    osg::Drawable* makeDrawable_useSeperateIndices(GLMmodel* obj, GLMgroup* grp);
77   
78    typedef std::map< std::string, osg::ref_ptr<osg::Texture2D> > TextureMap;
79    typedef std::set< osg::ref_ptr<osg::Material>, DerefLess< osg::ref_ptr<osg::Material> > > MaterialSet;
80    typedef std::set< osg::ref_ptr<osg::StateSet>, DerefLess< osg::ref_ptr<osg::StateSet> > > StateSetSet;
81    typedef std::vector< osg::ref_ptr<osg::StateSet> > ObjMatierialOsgStateSetArray;
82   
83    class IndexMap
84    {
85        public:
86
87            IndexMap():
88                _maximumIn(-1),
89                _maximumOut(-1) {}
90
91            inline void updateMaximum(int index)
92            {
93                if (index>_maximumIn) _maximumIn=index;
94            }
95           
96            inline void  initialize()
97            {
98                _indices.assign(_maximumIn+1,-1);
99            }
100           
101            inline void insertIndex(int index)
102            {
103                if (_indices[index]<0) _indices[index]=++_maximumOut;
104            }
105
106            inline int index(int i) const { return _indices[i]; }
107
108            osg::Vec3Array* createVec3Array(const float* array)
109            {
110                osg::Vec3Array* vec3array = new osg::Vec3Array(_maximumOut+1);
111                for(unsigned int i=0;i<_indices.size();++i)
112                {
113                    if (_indices[i]>=0) (*vec3array)[_indices[i]].set(array[i*3],-array[i*3+2],array[i*3+1]);
114                }
115                return vec3array;
116            }
117           
118            osg::Vec2Array* createVec2Array(const float* array)
119            {
120                osg::Vec2Array* vec2array = new osg::Vec2Array(_maximumOut+1);
121                for(unsigned int i=0;i<_indices.size();++i)
122                {
123                    if (_indices[i]>=0) (*vec2array)[_indices[i]].set(array[i*2],array[i*2+1]);
124                }
125                return vec2array;
126            }
127
128            osg::UByte4Array* createUByte4Array(const osg::UByte4* array)
129            {
130                osg::UByte4Array* ubyte4array = new osg::UByte4Array(_maximumOut+1);
131                for(unsigned int i=0;i<_indices.size();++i)
132                {
133                    if (_indices[i]>=0) (*ubyte4array)[_indices[i]] = array[i];
134                }
135                return ubyte4array;
136            }
137           
138            typedef std::vector<int> Indices;
139
140            int _maximumIn;
141            int _maximumOut;
142            Indices _indices;
143           
144    };
145
146
147};
148
149
150// register with Registry to instantiate the above reader/writer.
151osgDB::RegisterReaderWriterProxy<ReaderWriterOBJ> g_objReaderWriterProxy;
152
153
154// read file and convert to OSG.
155osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(const std::string& file, const osgDB::ReaderWriter::Options*)
156{
157    std::string ext = osgDB::getLowerCaseFileExtension(file);
158    if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
159
160    std::string fileName = osgDB::findDataFile( file );
161    if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
162
163    GLMmodel* obj = glmReadOBJ((char*) fileName.c_str());
164    if (!obj)
165        return ReadResult::FILE_NOT_HANDLED;
166
167    std::string directory = osgDB::getFilePath(fileName);
168
169
170    osg::notify(osg::INFO) << "vertices " << obj->numvertices << std::endl;
171    osg::notify(osg::INFO)  << "normals " << obj->numnormals << std::endl;
172    osg::notify(osg::INFO)  << "texcoords " << obj->numtexcoords << std::endl;
173    osg::notify(osg::INFO)  << "face normals " << obj->numfacetnorms << std::endl;
174    osg::notify(osg::INFO)  << "tris " << obj->numtriangles << std::endl;
175    osg::notify(osg::INFO)  << "materials " << obj->nummaterials << std::endl;
176    osg::notify(osg::INFO)  << "groups " << obj->numgroups << std::endl;
177   
178   
179//     if (obj->numnormals==0)
180//     {
181//         osg::notify(osg::NOTICE)  << "No normals in .obj file, automatically calculating normals..."<< std::endl;
182//         glmFacetNormals(obj);
183//         glmVertexNormals(obj,90.0f);
184//     }
185
186
187    unsigned int i;
188
189
190    TextureMap textureMap;
191    MaterialSet materialSet;
192    StateSetSet statesetSet;
193   
194    // create a sphere mapped texgen just in case we need it.
195    osg::ref_ptr<osg::TexGen> osg_texgen = new osg::TexGen;
196    osg_texgen->setMode(osg::TexGen::SPHERE_MAP);
197
198    ObjMatierialOsgStateSetArray osg_mtl(obj->nummaterials);
199
200    for (i = 0; i < obj->nummaterials; i++)
201    {
202        GLMmaterial* omtl = &(obj->materials[i]);
203        osg::notify(osg::DEBUG_INFO) << "mtl: " << omtl->name << std::endl;
204
205        osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
206
207        osg::ref_ptr<osg::Material> mtl = new osg::Material;
208        mtl->setAmbient(osg::Material::FRONT_AND_BACK,
209                        osg::Vec4(omtl->ambient[0], omtl->ambient[1],
210                                  omtl->ambient[2], omtl->ambient[3]));
211        mtl->setDiffuse(osg::Material::FRONT_AND_BACK,
212                        osg::Vec4(omtl->diffuse[0], omtl->diffuse[1],
213                                  omtl->diffuse[2], omtl->diffuse[3]));
214        mtl->setSpecular(osg::Material::FRONT_AND_BACK,
215                         osg::Vec4(omtl->specular[0], omtl->specular[1],
216                                   omtl->specular[2], omtl->specular[3]));
217        mtl->setEmission(osg::Material::FRONT_AND_BACK,
218                         osg::Vec4(omtl->emmissive[0], omtl->emmissive[1],
219                                   omtl->emmissive[2], omtl->emmissive[3]));
220                                   
221        // note, osg shininess scales between 0.0 and 1.0.
222        mtl->setShininess(osg::Material::FRONT_AND_BACK, omtl->shininess);
223        mtl->setAlpha(osg::Material::FRONT_AND_BACK, omtl->alpha);
224
225        MaterialSet::iterator mitr = materialSet.find(mtl);
226        if (mitr==materialSet.end())
227        {
228            materialSet.insert(mtl);
229        }
230        else
231        {
232            mtl = *mitr;
233        }
234
235        stateset->setAttribute(mtl.get());
236
237        if (omtl->alpha<1.0f) {
238            stateset->setMode(GL_BLEND,osg::StateAttribute::ON);
239            stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
240        }
241
242        if (omtl->textureName)
243        {
244            TextureMap::iterator titr = textureMap.find(omtl->textureName);
245
246            osg::notify(osg::DEBUG_INFO) << "textureName: " << omtl->textureName << std::endl;
247
248            if (titr==textureMap.end())
249            {
250           
251                std::string fileName = osgDB::findFileInDirectory(omtl->textureName,directory,osgDB::CASE_INSENSITIVE);
252                if (fileName.empty()) fileName = osgDB::findDataFile(omtl->textureName,osgDB::CASE_INSENSITIVE);
253               
254                if (!fileName.empty())
255                {
256
257                    osg::notify(osg::DEBUG_INFO) << "filename: " << fileName << std::endl;
258
259                    osg::Image* osg_image = osgDB::readImageFile(fileName.c_str());
260                    if (osg_image)
261                    {
262
263                        osg::notify(osg::DEBUG_INFO) << "imageRead: " << omtl->textureName << std::endl;
264
265
266                        osg::Texture2D* osg_texture = new osg::Texture2D;
267                        osg_texture->setImage(osg_image);
268                        stateset->setTextureAttributeAndModes(0,osg_texture,osg::StateAttribute::ON);
269                       
270                        textureMap[omtl->textureName] = osg_texture;
271
272                        // Adjust the texture matrix if necessary
273                        bool needsUVScaling = (1.0 != omtl->textureUScale) || (1.0 != omtl->textureVScale);
274                        bool needsUVOffsetting = (0.0 != omtl->textureUOffset) || (0.0 != omtl->textureVOffset);
275
276                        if (needsUVScaling || needsUVOffsetting)
277                        {
278                            osg::TexMat* osg_texmat = new osg::TexMat;
279
280                            osg::Matrix scale;
281                            osg::Matrix translate;
282                            scale.makeIdentity();
283                            translate.makeIdentity();
284
285                            if (needsUVScaling)
286                            {
287                                scale.makeScale(omtl->textureUScale, omtl->textureVScale, 1.0f);
288                            }
289
290                            if (needsUVOffsetting)
291                            {
292                                translate.makeTranslate(omtl->textureUOffset, omtl->textureVOffset, 0.0f);
293                            }
294
295                            osg_texmat->setMatrix(scale * translate);
296                            stateset->setTextureAttributeAndModes(0,osg_texmat,osg::StateAttribute::ON);
297                        }
298                    }
299                    else
300                    {
301                        osg::notify(osg::NOTICE) << "Warning: Cannot create texture "<<omtl->textureName<< std::endl;
302                    }
303                }
304                else
305                {
306                    osg::notify(osg::WARN) << "texture '"<<omtl->textureName<<"' not found"<< std::endl;
307                }
308               
309            }
310            else
311            {
312                stateset->setTextureAttributeAndModes(0,titr->second.get(),osg::StateAttribute::ON);
313            }
314        }
315       
316        if (omtl->textureReflection)
317        {
318            stateset->setTextureAttributeAndModes(0,osg_texgen.get(),osg::StateAttribute::ON);
319        }
320       
321        StateSetSet::iterator sitr = statesetSet.find(stateset);
322        if (sitr==statesetSet.end())
323        {
324            osg_mtl[i] = stateset;
325            statesetSet.insert(stateset);
326        }
327        else
328        {
329            osg_mtl[i] = *sitr;
330        }       
331    }
332
333    // toplevel group or transform
334    osg::Group* osg_top = NULL;
335    if (obj->position[0] != 0.0f || obj->position[1] != 0.0f || obj->position[2] != 0.0f) {
336        osg::MatrixTransform* xform = new osg::MatrixTransform;
337        // note obj_x -> osg_x,
338        //      obj_y -> osg_z,
339        //      obj_z -> osg_y,
340        xform->setMatrix(osg::Matrix::translate(obj->position[0], -obj->position[2], obj->position[1]));
341        osg_top = xform;
342    }
343    else
344        osg_top = new osg::Group;
345
346    osg_top->setName(obj->pathname);
347
348    DrawableMode drawableMode = USE_SEPERATE_INDICES;
349//    DrawableMode drawableMode = DUPLICATE_COORDS;
350
351    // subgroups
352    // XXX one Geode per group is probably not necessary...
353    GLMgroup* ogrp = obj->groups;
354    while (ogrp) {
355        if (ogrp->numtriangles > 0) {
356
357            osg::Geode* osg_geo = new osg::Geode;
358            osg_geo->setName(ogrp->name);
359            osg::Drawable* drawable = makeDrawable(drawableMode,obj,ogrp);
360           
361            // state and material (if any)
362            if (!osg_mtl.empty()) {
363           
364                osg::notify(osg::NOTICE)<<"ogrp->material="<<ogrp->material<<std::endl;
365           
366                drawable->setStateSet(osg_mtl[ogrp->material].get());
367            }
368
369            osg_geo->addDrawable(drawable);
370            osg_top->addChild(osg_geo);
371        }
372        ogrp = ogrp->next;
373    }
374
375    // free
376    glmDelete(obj);
377
378    return osg_top;
379}
380
381osg::Drawable* ReaderWriterOBJ::makeDrawable(DrawableMode drawableMode, GLMmodel* obj, GLMgroup* grp)
382{
383    switch(drawableMode)
384    {
385    case(DUPLICATE_COORDS): return makeDrawable_duplicateCoords(obj,grp);
386    case(USE_SEPERATE_INDICES): return makeDrawable_useSeperateIndices(obj,grp);
387    }
388    return 0;
389}
390
391// make drawable from OBJ group
392osg::Drawable* ReaderWriterOBJ::makeDrawable_duplicateCoords(GLMmodel* obj, GLMgroup* grp)
393{
394
395    GLMtriangle* tris = obj->triangles;
396
397    unsigned int ntris = grp->numtriangles;
398    unsigned int i = 0;
399
400    // geometry
401    osg::Geometry* geom = new osg::Geometry;
402   
403    geom->setUseDisplayList(false);
404    // geom->setUseVertexBufferObjects(true);
405
406    // primitives are only triangles
407    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,0,ntris*3));
408
409    // the following code for mapping the coords, normals and texcoords
410    // is complicated greatly by the need to create separate out the
411    // sets of coords etc for each drawable.
412
413    bool needColors = obj->useColors && obj->colors;
414    bool needNormals = obj->normals && obj->normals;
415    bool needTexcoords = obj->texcoords && obj->numtexcoords>0 && grp->hastexcoords;
416   
417   
418    osg::Vec3Array* coordArray = new osg::Vec3Array(3*ntris);
419   
420    osg::Vec3Array::iterator coords = coordArray->begin();
421    geom->setVertexArray(coordArray);
422   
423    osg::UByte4Array::iterator colors = osg::UByte4Array::iterator();// dummy assignment to get round stupid compiler warnings.
424    if (needColors)
425    {
426        osg::UByte4Array* colorArray = new osg::UByte4Array(3*ntris);
427        geom->setColorArray(colorArray);
428        geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
429        colors = colorArray->begin();
430    }
431
432
433    osg::Vec3Array::iterator normals = osg::Vec3Array::iterator();// dummy assignment to get round stupid compiler warnings.
434    if (needNormals)
435    {
436        osg::Vec3Array* normalArray = new osg::Vec3Array(3*ntris);
437        geom->setNormalArray(normalArray);
438        geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
439        normals = normalArray->begin();
440    }
441
442    osg::Vec2Array::iterator texcoords = osg::Vec2Array::iterator(); // dummy assignment to get round stupid compiler warnings.
443    if (needTexcoords)
444    {
445        osg::Vec2Array* texCoordArray = new osg::Vec2Array(3*ntris);
446        geom->setTexCoordArray(0,texCoordArray);
447       
448        texcoords = texCoordArray->begin();
449    }
450
451    // first count the number of vertices used in this group.
452    for (i = 0; i < ntris; i++)
453    {
454        GLMtriangle* tri = &(tris[grp->triangles[i]]);
455       
456        for(int corner=0;corner<3;++corner)
457        {
458            int ci = tri->vindices[corner]*3;
459
460            // note rotate about the x axis to place the OBJ y up to OSG z up.
461            coords->set(obj->vertices[ci],-obj->vertices[ci+2],obj->vertices[ci+1]);
462            ++coords;
463           
464            if (needColors)
465            {
466                (*colors) = obj->colors[tri->vindices[corner]];
467                ++colors;
468            }
469
470            if (needNormals)
471            {
472                int ni = tri->nindices[corner]*3;
473
474                // note rotate about the x axis to place the OBJ y up to OSG z up.
475                normals->set(obj->normals[ni],-obj->normals[ni+2],obj->normals[ni+1]);
476                ++normals;
477            }
478
479            if (needTexcoords)
480            {
481                int ti = tri->tindices[corner]*2;
482                texcoords->set(obj->texcoords[ti+0], obj->texcoords[ti+1]);
483                ++texcoords;
484            }
485        }
486    }
487
488    return geom;
489}
490
491osg::Drawable* ReaderWriterOBJ::makeDrawable_useSeperateIndices(GLMmodel* obj, GLMgroup* grp)
492{
493
494    GLMtriangle* tris = obj->triangles;
495
496    unsigned int ntris = grp->numtriangles;
497    unsigned int i = 0;
498
499    // geometry
500    osg::Geometry* geom = new osg::Geometry;
501   
502    // geom->setUseDisplayList(false);
503    // geom->setUseVertexBufferObjects(true);
504
505    // the following code for mapping the coords, normals and texcoords
506    // is complicated greatly by the need to create separate out the
507    // sets of coords etc for each drawable.
508
509    bool needColors = obj->useColors && obj->colors;
510    bool needNormals = obj->normals && obj->normals;
511    bool needTexcoords = obj->texcoords && obj->numtexcoords>0 && grp->hastexcoords;
512   
513
514//    needNormals = false;   
515   
516    IndexMap vertexIndexMap;
517    IndexMap normalIndexMap;
518    IndexMap texcoordIndexMap;
519       
520    // find maxium value.
521    for (i = 0; i < ntris; i++)
522    {
523        GLMtriangle& tri = tris[grp->triangles[i]];
524        for(int corner=0;corner<3;++corner)
525        {
526            vertexIndexMap.updateMaximum(tri.vindices[corner]);
527            if (needNormals) normalIndexMap.updateMaximum(tri.nindices[corner]);
528            if (needTexcoords) texcoordIndexMap.updateMaximum(tri.tindices[corner]);
529
530        }
531    }
532   
533   
534    // intialialize map.
535    vertexIndexMap.initialize();
536    if (needNormals) normalIndexMap.initialize();
537    if (needTexcoords) texcoordIndexMap.initialize();
538   
539    // populate map.
540    for (i = 0; i < ntris; i++)
541    {
542        GLMtriangle& tri = tris[grp->triangles[i]];
543        for(int corner=0;corner<3;++corner)
544        {
545            vertexIndexMap.insertIndex(tri.vindices[corner]);
546            if (needNormals) normalIndexMap.insertIndex(tri.nindices[corner]);
547            if (needTexcoords) texcoordIndexMap.insertIndex(tri.tindices[corner]);
548        }
549    }
550
551    // copy data across to geometry.
552    geom->setVertexArray(vertexIndexMap.createVec3Array(obj->vertices));
553   
554    if (needColors)
555    {
556        geom->setColorArray(vertexIndexMap.createUByte4Array(obj->colors));
557        geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
558    }
559
560    if (needNormals)
561    {
562        geom->setNormalArray(normalIndexMap.createVec3Array(obj->normals));
563        geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
564    }
565
566    if (needTexcoords)
567    {
568        geom->setTexCoordArray(0,texcoordIndexMap.createVec2Array(obj->texcoords));
569    }
570   
571   
572    osg::ref_ptr<osg::UIntArray> vertexIndices = new osg::UIntArray(ntris*3);
573    osg::ref_ptr<osg::UIntArray> normalIndices = needNormals ? new osg::UIntArray(ntris*3) : 0;
574    osg::ref_ptr<osg::UIntArray> texcoordIndices = needTexcoords ? new osg::UIntArray(ntris*3) : 0;
575   
576    int vi=0;
577    for (i = 0; i < ntris; i++)
578    {
579        GLMtriangle& tri = (tris[grp->triangles[i]]);
580       
581        for(int corner=0;corner<3;++corner,++vi)
582        {
583            (*vertexIndices)[vi] = vertexIndexMap.index(tri.vindices[corner]);
584
585            if (needNormals)
586            {
587                (*normalIndices)[vi] = normalIndexMap.index(tri.nindices[corner]);
588            }
589
590            if (needTexcoords)
591            {
592                (*texcoordIndices)[vi] = texcoordIndexMap.index(tri.tindices[corner]);
593            }
594        }
595    }
596   
597    bool indexArraysEqual=true;
598    if (needNormals) indexArraysEqual=(*vertexIndices==*normalIndices);
599    if (indexArraysEqual && needTexcoords) indexArraysEqual=(*vertexIndices==*texcoordIndices);
600
601    if (indexArraysEqual)
602    {
603        geom->addPrimitiveSet(new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES,vertexIndices->begin(),vertexIndices->end()));
604    }
605    else
606    {
607        geom->setVertexIndices(vertexIndices.get());
608        if (needColors) geom->setColorIndices(vertexIndices.get());
609        if (needNormals) geom->setNormalIndices(normalIndices.get());
610        if (needTexcoords) geom->setTexCoordIndices(0,texcoordIndices.get());
611
612        // primitives are only triangles
613        geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,0,ntris*3));
614
615    }
616#if 0
617    osgUtil::TriStripVisitor tsv;
618    tsv.stripify(*geom);
619#endif
620
621    if (obj->numnormals==0)
622    {
623        osgUtil::SmoothingVisitor tsv;
624        tsv.smooth(*geom);
625    }
626    return geom;
627}
Note: See TracBrowser for help on using the browser.