root/OpenSceneGraph/trunk/src/osgPlugins/3ds/ReaderWriter3DS.cpp @ 13041

Revision 13041, 55.1 kB (checked in by robert, 2 years ago)

Ran script to remove trailing spaces and tabs

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1
2#include <osg/Notify>
3#include <osg/Group>
4#include <osg/Geode>
5#include <osg/Geometry>
6#include <osg/Texture2D>
7#include <osg/Material>
8#include <osg/TexEnv>
9#include <osg/ref_ptr>
10#include <osg/MatrixTransform>
11#include <osg/BlendFunc>
12#include <osg/TexEnvCombine>
13#include <osg/CullFace>
14
15#include <osgDB/Registry>
16#include <osgDB/FileUtils>
17#include <osgDB/FileNameUtils>
18#include <osgDB/ReadFile>
19
20#include <osgUtil/TriStripVisitor>
21
22//MIKEC debug only for PrintVisitor
23#include <osg/NodeVisitor>
24
25#include "WriterNodeVisitor.h"
26#include "lib3ds/lib3ds.h"
27#include <stdlib.h>
28#include <string.h>
29
30#include <set>
31#include <map>
32#include <iostream>
33#include <sstream>
34#include <assert.h>
35
36using namespace std;
37using namespace osg;
38
39/// Implementation borrowed from boost and slightly modified
40template<class T> class scoped_array // noncopyable
41{
42private:
43    T * px;
44    scoped_array(scoped_array const &);
45    scoped_array & operator=(scoped_array const &);
46    typedef scoped_array<T> this_type;
47
48    void operator==( scoped_array const& ) const;
49    void operator!=( scoped_array const& ) const;
50
51public:
52    typedef T element_type;
53    explicit scoped_array( T * p = 0 ) : px( p ) {}
54
55    ~scoped_array() { delete[] px; }
56
57    void reset(T * p = 0) {
58        assert( p == 0 || p != px ); // catch self-reset errors
59        this_type(p).swap(*this);
60    }
61
62    T & operator[](std::ptrdiff_t i) const // never throws
63    {
64        assert( px != 0 );
65        assert( i >= 0 );
66        return px[i];
67    }
68
69    T * get() const // never throws
70    {
71        return px;
72    }
73
74    void swap(scoped_array & b) // never throws
75    {
76        T * tmp = b.px;
77        b.px = px;
78        px = tmp;
79    }
80};
81
82
83
84void copyLib3dsMatrixToOsgMatrix(osg::Matrix& osg_matrix, const Lib3dsMatrix lib3ds_matrix)
85{
86    osg_matrix.set(
87        lib3ds_matrix[0][0],lib3ds_matrix[0][1],lib3ds_matrix[0][2],lib3ds_matrix[0][3],
88        lib3ds_matrix[1][0],lib3ds_matrix[1][1],lib3ds_matrix[1][2],lib3ds_matrix[1][3],
89        lib3ds_matrix[2][0],lib3ds_matrix[2][1],lib3ds_matrix[2][2],lib3ds_matrix[2][3],
90        lib3ds_matrix[3][0],lib3ds_matrix[3][1],lib3ds_matrix[3][2],lib3ds_matrix[3][3]);
91}
92
93osg::Matrix copyLib3dsMatrixToOsgMatrix(const Lib3dsMatrix mat)
94{
95    osg::Matrix osgMatrix;
96    copyLib3dsMatrixToOsgMatrix(osgMatrix, mat);
97    return osgMatrix;
98}
99
100void copyLib3dsVec3ToOsgVec3(osg::Vec3f osgVec, const float vertices[3])
101{
102    return osgVec.set(vertices[0], vertices[1], vertices[2]);
103}
104
105osg::Vec3f copyLib3dsVec3ToOsgVec3(const float vertices[3])
106{
107    return osg::Vec3f(vertices[0], vertices[1], vertices[2]);
108}
109
110osg::Quat copyLib3dsQuatToOsgQuat(const float quat[4])
111{
112    return osg::Quat(quat[0], quat[1], quat[2], quat[3]);
113}
114
115
116
117class PrintVisitor : public NodeVisitor
118{
119public:
120    PrintVisitor(std::ostream& out):
121      NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
122          _out(out)
123      {
124          _indent = 0;
125          _step = 4;
126      }
127
128      inline void moveIn() { _indent += _step; }
129      inline void moveOut() { _indent -= _step; }
130      inline void writeIndent()
131      {
132          for(int i=0;i<_indent;++i) _out << " ";
133      }
134
135      virtual void apply(Node& node)
136      {
137          moveIn();
138          writeIndent(); _out << node.className() <<std::endl;
139          traverse(node);
140          moveOut();
141      }
142
143      virtual void apply(Geode& node)         { apply((Node&)node); }
144      virtual void apply(Billboard& node)     { apply((Geode&)node); }
145      virtual void apply(LightSource& node)   { apply((Group&)node); }
146      virtual void apply(ClipNode& node)      { apply((Group&)node); }
147
148      virtual void apply(Group& node)         { apply((Node&)node); }
149      virtual void apply(Transform& node)     { apply((Group&)node); }
150      virtual void apply(Projection& node)    { apply((Group&)node); }
151      virtual void apply(Switch& node)        { apply((Group&)node); }
152      virtual void apply(LOD& node)           { apply((Group&)node); }
153
154protected:
155
156    PrintVisitor& operator = (const PrintVisitor&) { return *this; }
157
158    std::ostream& _out;
159    int _indent;
160    int _step;
161};
162
163/// Possible options:
164///        - noMatrixTransforms: set the plugin to apply matrices into the mesh vertices ("old behaviour") instead of restoring them ("new behaviour"). You may use this option to avoid a few rounding errors.
165///        - checkForEspilonIdentityMatrices: if noMatrixTransforms is \b not set, then consider "almost identity" matrices to be identity ones (in case of rounding errors).
166///        - restoreMatrixTransformsNoMeshes: makes an exception to the behaviour when 'noMatrixTransforms' is \b not set for mesh instances. When a mesh instance has a transform on it, the reader creates a MatrixTransform above the Geode. If you don't want the hierarchy to be modified, then you can use this option to merge the transform into vertices.
167class ReaderWriter3DS : public osgDB::ReaderWriter
168{
169public:
170
171    ReaderWriter3DS();
172
173    virtual const char* className() const { return "3DS Auto Studio Reader/Writer"; }
174
175    virtual ReadResult readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const;
176    virtual ReadResult readNode(std::istream& fin, const Options* options) const;
177    virtual ReadResult doReadNode(std::istream& fin, const Options* options, const std::string & fileNamelib3ds) const;        ///< Subfunction of readNode()s functions.
178
179    virtual WriteResult writeNode(const osg::Node& /*node*/,const std::string& /*fileName*/,const Options* =NULL) const;
180    virtual WriteResult writeNode(const osg::Node& /*node*/,std::ostream& /*fout*/,const Options* =NULL) const;
181    virtual WriteResult doWriteNode(const osg::Node& /*node*/,std::ostream& /*fout*/,const Options*, const std::string & fileNamelib3ds) const;
182
183protected:
184    ReadResult constructFrom3dsFile(Lib3dsFile *f,const std::string& filename, const Options* options) const;
185
186    bool createFileObject(const osg::Node& node, Lib3dsFile * file3ds,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const;
187
188    /// An OSG state set with the original 3DS material attached (used to get info such as UV scaling & offset)
189    struct StateSetInfo
190    {
191        StateSetInfo(osg::StateSet * stateset=NULL, Lib3dsMaterial * lib3dsmat=NULL) : stateset(stateset), lib3dsmat(lib3dsmat) {}
192        StateSetInfo(const StateSetInfo & v) : stateset(v.stateset), lib3dsmat(v.lib3dsmat) {}
193        StateSetInfo & operator=(const StateSetInfo & v) { stateset=v.stateset; lib3dsmat=v.lib3dsmat; return *this; }
194
195        osg::StateSet * stateset;
196        Lib3dsMaterial * lib3dsmat;
197    };
198
199    class ReaderObject
200    {
201    public:
202        ReaderObject(const osgDB::ReaderWriter::Options* options);
203
204        typedef std::vector<StateSetInfo> StateSetMap;
205        typedef std::vector<int> FaceList;
206        typedef std::map<std::string,osg::StateSet*> GeoStateMap;
207
208        osg::Texture2D* createTexture(Lib3dsTextureMap *texture,const char* label,bool& transparancy);
209        StateSetInfo createStateSet(Lib3dsMaterial *materials);
210        osg::Drawable* createDrawable(Lib3dsMesh *meshes,FaceList& faceList, const osg::Matrix * matrix, StateSetInfo & ssi, bool smoothVertexNormals);
211
212        std::string _directory;
213        bool _useSmoothingGroups;
214
215        // MIKEC
216        osg::Node* processMesh(StateSetMap& drawStateMap,osg::Group* parent,Lib3dsMesh* mesh, const osg::Matrix * matrix);
217        osg::Node* processNode(StateSetMap& drawStateMap,Lib3dsFile *f,Lib3dsNode *node);
218    private:
219        const osgDB::ReaderWriter::Options* options;
220        bool noMatrixTransforms;            ///< Should the plugin apply matrices into the mesh vertices ("old behaviour"), instead of restoring matrices ("new behaviour")?
221        bool checkForEspilonIdentityMatrices;
222        bool restoreMatrixTransformsNoMeshes;
223        typedef std::map<unsigned int,FaceList> SmoothingFaceMap;
224        void addDrawableFromFace(osg::Geode * geode, FaceList & faceList, Lib3dsMesh * mesh, const osg::Matrix * matrix, StateSetInfo & ssi);
225
226        typedef std::map<std::string, osg::ref_ptr<osg::Texture2D> > TexturesMap;        // Should be an unordered map (faster)
227        TexturesMap texturesMap;
228};
229};
230
231// now register with Registry to instantiate the above
232// reader/writer.
233REGISTER_OSGPLUGIN(3ds, ReaderWriter3DS)
234
235ReaderWriter3DS::ReaderWriter3DS()
236{
237    supportsExtension("3ds","3D Studio model format");
238    //supportsOption("OutputTextureFiles","Write out the texture images to file");
239    //supportsOption("flipTexture", "flip texture upside-down");
240    supportsOption("extended3dsFilePaths", "(Write option) Keeps long texture filenames (not 8.3) when exporting 3DS, but can lead to compatibility problems.");
241    supportsOption("noMatrixTransforms", "(Read option) Set the plugin to apply matrices into the mesh vertices (\"old behaviour\") instead of restoring them (\"new behaviour\"). You may use this option to avoid a few rounding errors.");
242    supportsOption("checkForEspilonIdentityMatrices", "(Read option) If not set, then consider \"almost identity\" matrices to be identity ones (in case of rounding errors).");
243    supportsOption("restoreMatrixTransformsNoMeshes", "(Read option) Makes an exception to the behaviour when 'noMatrixTransforms' is not set for mesh instances. When a mesh instance has a transform on it, the reader creates a MatrixTransform above the Geode. If you don't want the hierarchy to be modified, then you can use this option to merge the transform into vertices.");
244
245#if 0
246    OSG_NOTICE<<"3DS reader sizes:"<<std::endl;
247    OSG_NOTICE<<"  sizeof(Lib3dsBool)="<<sizeof(Lib3dsBool)<<std::endl;
248    OSG_NOTICE<<"  sizeof(Lib3dsByte)="<<sizeof(Lib3dsByte)<<std::endl;
249    OSG_NOTICE<<"  sizeof(Lib3dsWord)="<<sizeof(Lib3dsWord)<<std::endl;
250    OSG_NOTICE<<"  sizeof(Lib3dsDword)="<<sizeof(Lib3dsDword)<<std::endl;
251    OSG_NOTICE<<"  sizeof(Lib3dsIntb)="<<sizeof(Lib3dsIntb)<<std::endl;
252    OSG_NOTICE<<"  sizeof(Lib3dsIntw)="<<sizeof(Lib3dsIntw)<<std::endl;
253    OSG_NOTICE<<"  sizeof(Lib3dsIntd)="<<sizeof(Lib3dsIntd)<<std::endl;
254    OSG_NOTICE<<"  sizeof(Lib3dsFloat)="<<sizeof(Lib3dsFloat)<<std::endl;
255    OSG_NOTICE<<"  sizeof(Lib3dsDouble)="<<sizeof(Lib3dsDouble)<<std::endl;
256    OSG_NOTICE<<"  sizeof(Lib3dsVector)="<<sizeof(Lib3dsVector)<<std::endl;
257    OSG_NOTICE<<"  sizeof(Lib3dsTexel)="<<sizeof(Lib3dsTexel)<<std::endl;
258    OSG_NOTICE<<"  sizeof(Lib3dsQuat)="<<sizeof(Lib3dsQuat)<<std::endl;
259    OSG_NOTICE<<"  sizeof(Lib3dsMatrix)="<<sizeof(Lib3dsMatrix)<<std::endl;
260    OSG_NOTICE<<"  sizeof(Lib3dsRgb)="<<sizeof(Lib3dsRgb)<<std::endl;
261    OSG_NOTICE<<"  sizeof(Lib3dsRgba)="<<sizeof(Lib3dsRgba)<<std::endl;
262#endif
263
264}
265
266ReaderWriter3DS::ReaderObject::ReaderObject(const osgDB::ReaderWriter::Options* options) :
267    _useSmoothingGroups(true),
268    options(options),
269    noMatrixTransforms(false),
270    checkForEspilonIdentityMatrices(false),
271    restoreMatrixTransformsNoMeshes(false)
272{
273    if (options)
274    {
275        std::istringstream iss(options->getOptionString());
276        std::string opt;
277        while (iss >> opt)
278        {
279            if (opt == "noMatrixTransforms")
280                noMatrixTransforms = true;
281            else if (opt == "checkForEspilonIdentityMatrices")
282                checkForEspilonIdentityMatrices = true;
283            else if (opt == "restoreMatrixTransformsNoMeshes")
284                restoreMatrixTransformsNoMeshes = true;
285        }
286    }
287}
288
289
290/**
291    These print methods for 3ds hacking
292*/
293void pad(int level)
294{
295    for(int i=0;i<level;i++) std::cout<<"  ";
296}
297void print(Lib3dsMesh *mesh,int level);
298void print(Lib3dsUserData *user,int level);
299void print(Lib3dsMeshInstanceNode *object,int level);
300void print(Lib3dsNode *node, int level);
301
302void print(Lib3dsMatrix matrix,int level)
303{
304    pad(level); cout << matrix[0][0] <<" "<< matrix[0][1] <<" "<< matrix[0][2] <<" "<< matrix[0][3] << endl;
305    pad(level); cout << matrix[1][0] <<" "<< matrix[1][1] <<" "<< matrix[1][2] <<" "<< matrix[1][3] << endl;
306    pad(level); cout << matrix[2][0] <<" "<< matrix[2][1] <<" "<< matrix[2][2] <<" "<< matrix[2][3] << endl;
307    pad(level); cout << matrix[3][0] <<" "<< matrix[3][1] <<" "<< matrix[3][2] <<" "<< matrix[3][3] << endl;
308}
309void print(Lib3dsMesh *mesh,int level)
310{
311    if (mesh)
312    {
313        pad(level); cout << "mesh name " << mesh->name  << endl;
314        print(mesh->matrix,level);
315    }
316    else
317    {
318        pad(level); cout << "no mesh " << endl;
319    }
320}
321void print(Lib3dsUserData *user,int level)
322{
323    if (user)
324    {
325        pad(level); cout << "user data" << endl;
326        //print(user->mesh,level+1);
327    }
328    else
329    {
330        pad(level); cout << "no user data" << endl;
331    }
332}
333
334void print(Lib3dsMeshInstanceNode *object,int level)
335{
336    if (object)
337    {
338        pad(level); cout << "objectdata instance [" << object->instance_name << "]" << endl;
339        pad(level); cout << "pivot     " << object->pivot[0] <<" "<< object->pivot[1] <<" "<< object->pivot[2] << endl;
340        pad(level); cout << "pos       " << object->pos[0] <<" "<< object->pos[1] <<" "<< object->pos[2] << endl;
341        pad(level); cout << "scl       " << object->scl[0] <<" "<< object->scl[1] <<" "<< object->scl[2] << endl;
342        pad(level); cout << "rot       " << object->rot[0] <<" "<< object->rot[1] <<" "<< object->rot[2] <<" "<< object->rot[3] << endl;
343    }
344    else
345    {
346        pad(level); cout << "no object data" << endl;
347    }
348}
349
350void print(Lib3dsNode *node, int level)
351{
352
353    pad(level); cout << "node name [" << node->name << "]" << endl;
354    pad(level); cout << "node id    " << node->user_id << endl;
355    pad(level); cout << "node parent id " << (node->parent ? static_cast<int>(node->parent->user_id) : -1) << endl;
356    pad(level); cout << "node matrix:" << endl;
357    print(node->matrix,level+1);
358
359    if (node->type == LIB3DS_NODE_MESH_INSTANCE)
360    {
361        pad(level); cout << "mesh instance data:" << endl;
362        print(reinterpret_cast<Lib3dsMeshInstanceNode *>(node),level+1);
363    }
364    else
365    {
366        pad(level); cout << "node is not a mesh instance (not handled)" << endl;
367    }
368
369    print(&node->user_ptr,level);
370
371    for(Lib3dsNode *child=node->childs; child; child=child->next)
372    {
373        print(child,level+1);
374    }
375
376}
377
378void ReaderWriter3DS::ReaderObject::addDrawableFromFace(osg::Geode * geode, FaceList & faceList,
379                                                        Lib3dsMesh * mesh,
380                                                        const osg::Matrix * matrix,
381                                                        StateSetInfo & ssi)
382{
383    if (_useSmoothingGroups)
384    {
385        SmoothingFaceMap smoothingFaceMap;
386        for (FaceList::iterator flitr=faceList.begin();
387            flitr!=faceList.end();
388            ++flitr)
389        {
390            // ChrisD: Worth bearing in mind that this splitting up of
391            // faces into smoothing groups is only correct for faces
392            // belonging to a single smoothing group. The smoothing group
393            // value is actually a bitmask for all the smoothing groups that
394            // a face may belong to.
395            smoothingFaceMap[mesh->faces[*flitr].smoothing_group].push_back(*flitr);
396        }
397
398        for(SmoothingFaceMap::iterator sitr=smoothingFaceMap.begin();
399            sitr!=smoothingFaceMap.end();
400            ++sitr)
401        {
402            // We only compute smoothed vertex normals for faces with non-zero smoothing group value.
403            const unsigned int smoothing_group = sitr->first;
404            bool smoothVertexNormals = (smoothing_group != 0);
405
406            // each smoothing group to have its own geom
407            // to ensure the vertices on adjacent groups
408            // don't get shared.
409            FaceList& smoothFaceList = sitr->second;
410            osg::ref_ptr<osg::Drawable> drawable = createDrawable(mesh, smoothFaceList, matrix, ssi, smoothVertexNormals);
411            if (drawable.valid())
412            {
413                if (ssi.stateset)
414                    drawable->setStateSet(ssi.stateset);
415                geode->addDrawable(drawable.get());
416            }
417        }
418    }
419    else // ignore smoothing groups.
420    {
421        // Create drawable with no smoothing of normals.
422        bool smoothVertexNormals = false;
423        osg::ref_ptr<osg::Drawable> drawable = createDrawable(mesh, faceList, matrix, ssi, smoothVertexNormals);
424        if (drawable.valid())
425        {
426            if (ssi.stateset)
427                drawable->setStateSet(ssi.stateset);
428            geode->addDrawable(drawable.get());
429        }
430    }
431}
432
433
434// Transforms points by matrix if 'matrix' is not NULL
435// Creates a Geode and Geometry (as parent,child) and adds the Geode to 'parent' parameter iff 'parent' is non-NULL
436// Returns ptr to the Geode
437osg::Node* ReaderWriter3DS::ReaderObject::processMesh(StateSetMap& drawStateMap,osg::Group* parent,Lib3dsMesh* mesh, const osg::Matrix * matrix)
438{
439    typedef std::vector<FaceList> MaterialFaceMap;
440    MaterialFaceMap materialFaceMap;
441    unsigned int numMaterials = drawStateMap.size();
442    materialFaceMap.insert(materialFaceMap.begin(), numMaterials, FaceList());        // Setup the map
443    FaceList defaultMaterialFaceList;
444    for (unsigned int i=0; i<mesh->nfaces; ++i)
445    {
446        if (mesh->faces[i].material>=0)
447        {
448            materialFaceMap[mesh->faces[i].material].push_back(i);
449        }
450        else
451        {
452            defaultMaterialFaceList.push_back(i);
453        }
454    }
455    if (materialFaceMap.empty() && defaultMaterialFaceList.empty())
456    {
457        OSG_NOTICE<<"Warning : no triangles assigned to mesh '"<<mesh->name<<"'"<< std::endl;
458        //OSG_INFO << "No material assigned to mesh '" << mesh->name << "'" << std::endl;
459        return NULL;
460    }
461    else
462    {
463        osg::Geode* geode = new osg::Geode;
464        geode->setName(mesh->name);
465        if (!defaultMaterialFaceList.empty())
466        {
467            StateSetInfo emptySSI;
468            addDrawableFromFace(geode, defaultMaterialFaceList, mesh, matrix, emptySSI);
469        }
470        for(unsigned int imat=0; imat<numMaterials; ++imat)
471        {
472            addDrawableFromFace(geode, materialFaceMap[imat], mesh, matrix, drawStateMap[imat]);
473        }
474        if (parent) parent->addChild(geode);
475        return geode;
476    }
477}
478
479
480/// Returns true if a matrix is 'almost' identity, meaning that the difference between each value and the corresponding identity value is less than an epsilon value.
481bool isIdentityEquivalent(const osg::Matrix & mat, osg::Matrix::value_type epsilon=1e-6)
482{
483    return osg::equivalent(mat(0,0), 1, epsilon) && osg::equivalent(mat(0,1), 0, epsilon) && osg::equivalent(mat(0,2), 0, epsilon) &&  osg::equivalent(mat(0,3), 0, epsilon) &&
484           osg::equivalent(mat(1,0), 0, epsilon) && osg::equivalent(mat(1,1), 1, epsilon) && osg::equivalent(mat(1,2), 0, epsilon) &&  osg::equivalent(mat(1,3), 0, epsilon) &&
485           osg::equivalent(mat(2,0), 0, epsilon) && osg::equivalent(mat(2,1), 0, epsilon) && osg::equivalent(mat(2,2), 1, epsilon) &&  osg::equivalent(mat(2,3), 0, epsilon) &&
486           osg::equivalent(mat(3,0), 0, epsilon) && osg::equivalent(mat(3,1), 0, epsilon) && osg::equivalent(mat(3,2), 0, epsilon) &&  osg::equivalent(mat(3,3), 1, epsilon);
487}
488
489
490/**
491How to cope with pivot points in 3ds (short version)
492
493  All object coordinates in 3ds are stored in world space, this is why you can just rip out the meshes and use/draw them without meddeling further
494  Unfortunately, this gets a bit wonky with objects with pivot points (conjecture: PP support is retro fitted into the .3ds format and so doesn't fit perfectly?)
495
496  Objects with pivot points have a position relative to their PP, so they have to undergo this transform:
497
498    invert the mesh matrix, apply this matrix to the object. This puts the object back at the origin
499    Transform the object by the nodes (negative) pivot point coords, this puts the PP at the origin
500    Transform the node by the node matrix, which does the orientation about the pivot point, (and currently) transforms the object back by a translation to the PP.
501
502*/
503osg::Node* ReaderWriter3DS::ReaderObject::processNode(StateSetMap& drawStateMap,Lib3dsFile *f,Lib3dsNode *node)
504{
505    // Get mesh
506    Lib3dsMeshInstanceNode * object = (node->type == LIB3DS_NODE_MESH_INSTANCE) ? reinterpret_cast<Lib3dsMeshInstanceNode *>(node) : NULL;
507    Lib3dsMesh * mesh = lib3ds_file_mesh_for_node(f,node);
508    assert(!(mesh && !object));        // Node must be a LIB3DS_NODE_MESH_INSTANCE if a mesh exists
509
510    // Retreive LOCAL transform
511    static const osg::Matrix::value_type MATRIX_EPSILON = 1e-10;
512    osg::Matrix osgWorldToNodeMatrix( copyLib3dsMatrixToOsgMatrix(node->matrix) );
513    osg::Matrix osgWorldToParentNodeMatrix;
514    if (node->parent)
515    {
516        // Matrices evaluated by lib3DS are multiplied by parents' ones
517        osgWorldToParentNodeMatrix = copyLib3dsMatrixToOsgMatrix(node->parent->matrix);
518    }
519    osg::Matrix osgNodeMatrix( osgWorldToNodeMatrix * osg::Matrix::inverse(osgWorldToParentNodeMatrix) );
520
521    // Test if we should create an intermediate Group (or MatrixTransform) and which matrix we should apply to the vertices
522    osg::Group* group = NULL;
523
524    // Get pivot point
525    osg::Vec3 pivot( object ? copyLib3dsVec3ToOsgVec3(object->pivot) : osg::Vec3() );
526    bool pivoted = pivot.x()!=0 || pivot.y()!=0 || pivot.z()!=0;
527
528    osg::Matrix meshMat;
529    if (mesh)
530    {
531        if (!noMatrixTransforms)
532        {
533            // There can be a transform directly on a mesh instance (= as if a osg::MatrixTransform and a osg::Geode were merged together) in object->pos/rot/scl
534            if (pivoted) {
535                meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix)) * osg::Matrix::translate(-pivot);
536            } else {
537                meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix));
538            }
539        }
540        else {
541            if (pivoted) {
542                meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix)) * osg::Matrix::translate(-pivot) * osgWorldToNodeMatrix;
543            } else {
544                meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix)) * osgWorldToNodeMatrix;        // ==Identity when not pivoted?
545            }
546            osgNodeMatrix = osg::Matrix::identity();        // Not sure it's useful, but it's harmless ;)
547        }
548    }
549
550    bool isOsgNodeMatrixIdentity = false;
551    if (osgNodeMatrix.isIdentity() || (checkForEspilonIdentityMatrices && isIdentityEquivalent(osgNodeMatrix, MATRIX_EPSILON)))
552    {
553        isOsgNodeMatrixIdentity = true;
554    }
555
556
557    //if (node->childs != NULL || pivoted || (!isOsgNodeMatrixIdentity && !noMatrixTransforms))
558    if (node->childs != NULL || (!isOsgNodeMatrixIdentity && !noMatrixTransforms))
559    {
560        if (isOsgNodeMatrixIdentity || noMatrixTransforms)
561        {
562            group = new osg::Group;
563        }
564        else
565        {
566            group = new osg::MatrixTransform(osgNodeMatrix);
567        }
568    }
569
570    if (group)
571    {
572        if (strcmp(node->name, "$$$DUMMY") == 0)
573        {
574            if (node->type == LIB3DS_NODE_MESH_INSTANCE)
575                group->setName(reinterpret_cast<Lib3dsMeshInstanceNode *>(node)->instance_name);
576        }
577        else if (node->type == LIB3DS_NODE_MESH_INSTANCE && strlen(reinterpret_cast<Lib3dsMeshInstanceNode *>(node)->instance_name) != 0)
578            group->setName(reinterpret_cast<Lib3dsMeshInstanceNode *>(node)->instance_name);
579        else
580            group->setName(node->name);
581
582        // Handle all children of this node for hierarchical assemblies
583        for (Lib3dsNode *p=node->childs; p!=NULL; p=p->next)
584        {
585            group->addChild(processNode(drawStateMap,f,p));
586        }
587    }
588    else
589    {
590        assert(node->childs == NULL);        // Else we must have a group to put childs into
591    }
592
593    // Handle mesh
594    if (mesh)
595    {
596        osg::Matrix * meshAppliedMatPtr = NULL;
597        if (!meshMat.isIdentity() && !(checkForEspilonIdentityMatrices && isIdentityEquivalent(meshMat, MATRIX_EPSILON)))
598        {
599            meshAppliedMatPtr = &meshMat;
600        }
601
602        if (group)
603        {
604            // add our geometry to group (where our children already are)
605            // creates geometry under modifier node
606            processMesh(drawStateMap,group,mesh,meshAppliedMatPtr);
607            return group;
608        }
609        else
610        {
611            // didnt use group for children
612            // return a ptr directly to the Geode for this mesh
613            return processMesh(drawStateMap,NULL,mesh,meshAppliedMatPtr);
614        }
615
616    }
617    else
618    {
619        // no mesh for this node - probably a camera or something of that persuasion
620        //cout << "no mesh for object " << node->name << endl;
621        return group; // we have no mesh, but we might have children
622    }
623}
624
625
626static long filei_seek_func(void *self, long offset, Lib3dsIoSeek origin)
627{
628    std::istream *f = reinterpret_cast<std::istream*>(self);
629    ios_base::seekdir o = ios_base::beg;
630    if (origin == LIB3DS_SEEK_CUR) o = ios_base::cur;
631    else if (origin == LIB3DS_SEEK_END) o = ios_base::end;
632
633    f->seekg(offset, o);
634    return f->fail() ? -1 : 0;
635}
636
637static long fileo_seek_func(void *self, long offset, Lib3dsIoSeek origin)
638{
639    std::ostream *f = reinterpret_cast<std::ostream*>(self);
640    ios_base::seekdir o = ios_base::beg;
641    if (origin == LIB3DS_SEEK_CUR) o = ios_base::cur;
642    else if (origin == LIB3DS_SEEK_END) o = ios_base::end;
643
644    f->seekp(offset, o);
645    return f->fail() ? -1 : 0;
646}
647
648static long filei_tell_func(void *self)
649{
650    std::istream *f = reinterpret_cast<std::istream*>(self);
651    return f->tellg();
652}
653
654static long fileo_tell_func(void *self)
655{
656    std::ostream *f = reinterpret_cast<std::ostream*>(self);
657    return f->tellp();
658}
659
660
661static size_t filei_read_func(void *self, void *buffer, size_t size)
662{
663    std::istream *f = reinterpret_cast<std::istream*>(self);
664    f->read(reinterpret_cast<char*>(buffer), size);
665    return f->gcount();
666}
667
668static size_t fileo_write_func(void *self, const void *buffer, size_t size)
669{
670    std::ostream *f = reinterpret_cast<std::ostream*>(self);
671    f->write(static_cast<const char*>(buffer), size);
672    return f->fail() ? 0 : size;
673}
674
675static void fileio_log_func(void *self, Lib3dsLogLevel level, int indent, const char *msg)
676{
677    osg::NotifySeverity l = osg::INFO;
678    // Intentionally NOT mapping 3DS levels with OSG levels
679    if (level == LIB3DS_LOG_ERROR) l = osg::WARN;
680    else if (level == LIB3DS_LOG_WARN) l = osg::NOTICE;
681    else if (level == LIB3DS_LOG_INFO) l = osg::INFO;
682    else if (level == LIB3DS_LOG_DEBUG) l = osg::DEBUG_INFO;
683    OSG_NOTIFY(l) << msg << std::endl;
684}
685
686
687osgDB::ReaderWriter::ReadResult ReaderWriter3DS::readNode(std::istream& fin,  const osgDB::ReaderWriter::Options* options) const
688{
689    std::string optFileName;
690    if (options)
691    {
692        optFileName = options->getPluginStringData("STREAM_FILENAME");
693        if (optFileName.empty()) optFileName = options->getPluginStringData("filename");
694    }
695    return doReadNode(fin, options, optFileName);
696}
697
698osgDB::ReaderWriter::ReadResult ReaderWriter3DS::doReadNode(std::istream& fin,  const osgDB::ReaderWriter::Options* options, const std::string & fileNamelib3ds) const
699{
700    osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
701    local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileNamelib3ds));
702
703    osgDB::ReaderWriter::ReadResult result = ReadResult::FILE_NOT_HANDLED;
704
705    // Prepare io structure to tell how to read the stream
706    Lib3dsIo io;
707    io.self = &fin;
708    io.seek_func = filei_seek_func;
709    io.tell_func = filei_tell_func;
710    io.read_func = filei_read_func;
711    io.write_func = NULL;
712    io.log_func = fileio_log_func;
713
714    Lib3dsFile * file3ds = lib3ds_file_new();
715    if (lib3ds_file_read(file3ds, &io) != 0)
716    {
717        result = constructFrom3dsFile(file3ds,fileNamelib3ds,options);
718        lib3ds_file_free(file3ds);
719    }
720
721    return(result);
722}
723
724osgDB::ReaderWriter::ReadResult ReaderWriter3DS::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const
725{
726    std::string ext = osgDB::getLowerCaseFileExtension(file);
727    if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
728
729    std::string fileName = osgDB::findDataFile( file, options );
730    if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
731
732    // Do not use the lib3ds_file_open() as:
733    //   1. It relies on FILE* instead of iostreams (less safe)
734    //   2. It doesn't allow us to set a custom log output
735    osgDB::ifstream fin(fileName.c_str(), std::ios_base::in | std::ios_base::binary);
736    if (!fin.good()) return ReadResult::ERROR_IN_READING_FILE;
737    return doReadNode(fin, options, fileName);
738/*
739    osgDB::ReaderWriter::ReadResult result = ReadResult::FILE_NOT_HANDLED;
740    Lib3dsFile *f = lib3ds_file_open(fileName.c_str());        // ,options
741
742    if (f)
743    {
744        osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
745        local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));
746
747        result = constructFrom3dsFile(f,file,local_opt.get());
748        lib3ds_file_free(f);
749    }
750
751    return result;
752*/
753}
754
755osgDB::ReaderWriter::ReadResult ReaderWriter3DS::constructFrom3dsFile(Lib3dsFile *f,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
756{
757    if (f==NULL) return ReadResult::FILE_NOT_HANDLED;
758
759    // MIKEC
760    // This appears to build the matrix structures for the 3ds structure
761    // It wasn't previously necessary because all the meshes are stored in world coordinates
762    // but is VERY necessary if you want to use pivot points...
763    lib3ds_file_eval(f,0.0f); // second param is time 't' for animated files
764
765    ReaderObject reader(options);
766
767    reader._directory = ( options && !options->getDatabasePathList().empty() ) ? options->getDatabasePathList().front() : osgDB::getFilePath(fileName);
768
769    ReaderObject::StateSetMap drawStateMap;
770    unsigned int numMaterials = f->nmaterials;
771    drawStateMap.insert(drawStateMap.begin(), numMaterials, StateSetInfo());        // Setup the map
772    for (unsigned int imat=0; imat<numMaterials; ++imat)
773    {
774        Lib3dsMaterial * mat = f->materials[imat];
775        drawStateMap[imat] = reader.createStateSet(mat);
776    }
777
778    if (osg::getNotifyLevel()>=osg::INFO)
779    {
780        int level=0;
781        std::cout << "NODE TRAVERSAL of 3ds file "<<f->name<<std::endl;
782        for(Lib3dsNode *node=f->nodes; node; node=node->next)
783        {
784            print(node,level+1);
785        }
786        std::cout << "MESH TRAVERSAL of 3ds file "<<f->name<<std::endl;
787        for (int imesh=0; imesh<f->nmeshes; ++imesh){
788            print(f->meshes[imesh],level+1);
789        }
790    }
791
792    // We can traverse by meshes (old method, broken for pivot points, but otherwise works), or by nodes (new method, not so well tested yet)
793    // if your model is broken, especially wrt object positions try setting this flag. If that fixes it,
794    // send me the model
795    bool traverse_nodes=false;
796
797    // MIKEC: have found 3ds files with NO node structure - only meshes, for this case we fall back to the old traverse-by-meshes code
798    // Loading and re-exporting these files from 3DS produces a file with correct node structure, so perhaps these are not 100% conformant?
799    if (f->nodes == NULL)
800    {
801        OSG_WARN<<"Warning: in 3ds loader: file has no nodes, traversing by meshes instead"<< std::endl;
802        traverse_nodes=true;
803    }
804
805    osg::Node* group = NULL;
806
807    if (traverse_nodes) // old method
808    {
809        group = new osg::Group();
810        for (int imesh=0; imesh<f->nmeshes; ++imesh)
811        {
812            reader.processMesh(drawStateMap,group->asGroup(),f->meshes[imesh],NULL);
813        }
814    }
815    else
816    { // new method
817        Lib3dsNode *node=f->nodes;
818        if (!node->next)
819        {
820            group = reader.processNode(drawStateMap,f,node);
821        }
822        else
823        {
824            group = new osg::Group();
825            for(; node; node=node->next)
826            {
827                group->asGroup()->addChild(reader.processNode(drawStateMap,f,node));
828            }
829        }
830    }
831    if (group && group->getName().empty()) group->setName(fileName);
832
833    if (osg::getNotifyLevel()>=osg::INFO)
834    {
835        OSG_INFO << "Final OSG node structure looks like this:"<< endl;
836        PrintVisitor pv(osg::notify(osg::INFO));
837        group->accept(pv);
838    }
839
840    return group;
841}
842
843struct RemappedFace
844{
845    Lib3dsFace* face;        // Original face definition.
846    osg::Vec3f normal;
847    unsigned short index[3]; // Indices to OSG vertex/normal/texcoord arrays.
848};
849
850struct VertexParams
851{
852    VertexParams() : matrix(NULL), smoothNormals(false), scaleUV(1.f, 1.f), offsetUV(0.f, 0.f) { }
853    const osg::Matrix* matrix;
854    bool smoothNormals;
855    osg::Vec2f scaleUV;
856    osg::Vec2f offsetUV;
857};
858
859static bool isFaceValid(const Lib3dsMesh* mesh, const Lib3dsFace* face)
860{
861    return
862        face->index[0] < mesh->nvertices &&
863        face->index[1] < mesh->nvertices &&
864        face->index[2] < mesh->nvertices;
865}
866
867/* ChrisD: addVertex handles the averaging of normals and spltting of vertices
868   required to implement normals for smoothing groups. When a shared
869   vertex is encountered when smoothing is required, normals are added
870   and normalized. When a shared vertex is encountered when smoothing is
871   not required, we must split the vertex if a different normal is required.
872   For example if we are processing a cube mesh with no smoothing group
873   made from 12 triangles and 8 vertices, the resultant mesh should have
874   24 vertices to accomodate the 3 different normals at each vertex.
875  */
876static void addVertex(
877    const Lib3dsMesh* mesh,
878    RemappedFace& remappedFace,
879    unsigned short int i,
880    osg::Geometry* geometry,
881    std::vector<int>& origToNewMapping,
882    std::vector<int>& splitVertexChain,
883    const VertexParams& params)
884{
885    osg::Vec3Array* vertices = (osg::Vec3Array*)geometry->getVertexArray();
886    osg::Vec3Array* normals = (osg::Vec3Array*)geometry->getNormalArray();
887    osg::Vec2Array* texCoords = (osg::Vec2Array*)geometry->getTexCoordArray(0);
888
889    unsigned short int index = remappedFace.face->index[i];
890    if (origToNewMapping[index] == -1)
891    {
892        int newIndex = vertices->size();
893        remappedFace.index[i] = newIndex;
894        origToNewMapping[index] = newIndex;
895
896        // Add the vertex position
897        osg::Vec3 vertex = copyLib3dsVec3ToOsgVec3(mesh->vertices[index]);
898        if (params.matrix) vertex = vertex * (*params.matrix);
899        vertices->push_back(vertex);
900
901        // Add the vertex normal
902        normals->push_back(remappedFace.normal);
903
904        // Add the texture coordinate.
905        if (texCoords)
906        {
907            osg::Vec2f texCoord(mesh->texcos[index][0], mesh->texcos[index][1]);
908            texCoord = componentMultiply(texCoord, params.scaleUV);
909            texCoord += params.offsetUV;
910            texCoords->push_back(texCoord);
911        }
912
913        // New vertex, so not split yet.
914        splitVertexChain.push_back(-1);
915    }
916    else
917    {
918        int newIndex = origToNewMapping[index];
919        if (params.smoothNormals)
920        {
921            // Average the normals on the shared vertex.
922            remappedFace.index[i] = newIndex;
923            osg::Vec3f normal = (*normals)[newIndex];
924            normal += remappedFace.normal;
925            normal.normalize();
926            (*normals)[newIndex] = normal;
927        }
928        else
929        {
930            // Find a split vertex chained from newIndex which has the 'same' normal.
931            int sharedVertexIndex = newIndex;
932            do
933            {
934                osg::Vec3f normal = (*normals)[sharedVertexIndex];
935                float normalDifference = (remappedFace.normal - normal).length2();
936                if (normalDifference < 1e-6) break;
937                sharedVertexIndex = splitVertexChain[sharedVertexIndex];
938            } while (sharedVertexIndex != -1);
939
940            if (sharedVertexIndex == -1)
941            {
942                // When different normals on a shared vertex required, split the vertex.
943                int splitVertexIndex = vertices->size();
944                remappedFace.index[i] = splitVertexIndex;
945                vertices->push_back((*vertices)[newIndex]);
946                normals->push_back(remappedFace.normal);
947                if (texCoords)
948                {
949                    texCoords->push_back((*texCoords)[newIndex]);
950                }
951                splitVertexChain[newIndex] = splitVertexIndex;
952                splitVertexChain.push_back(-1);
953            }
954            else
955            {
956                // When normals on shared vertex are identical (or very similar), keep it shared.
957                remappedFace.index[i] = sharedVertexIndex;
958            }
959        }
960    }
961}
962
963static bool addFace(
964    const Lib3dsMesh* mesh,
965    RemappedFace& remappedFace,
966    osg::Geometry* geometry,
967    std::vector<int>& origToNewMapping,
968    std::vector<int>& splitVertexChain,
969    const VertexParams& params)
970{
971    if (isFaceValid(mesh, remappedFace.face))
972    {
973        addVertex(mesh, remappedFace, 0, geometry, origToNewMapping, splitVertexChain, params);
974        addVertex(mesh, remappedFace, 1, geometry, origToNewMapping, splitVertexChain, params);
975        addVertex(mesh, remappedFace, 2, geometry, origToNewMapping, splitVertexChain, params);
976        return true;
977    }
978    else
979    {
980        // Avoids crash with corrupted files.
981        remappedFace.face = NULL;
982        return false;
983    }
984}
985
986/**
987use matrix to pretransform geometry, or NULL to do nothing
988*/
989osg::Drawable* ReaderWriter3DS::ReaderObject::createDrawable(Lib3dsMesh *m,FaceList& faceList, const osg::Matrix * matrix, StateSetInfo & ssi, bool smoothVertexNormals)
990{
991    // Avoid creating geoms for empty face list because otherwise osg asserts/crashes during render traversal.
992    if (faceList.empty()) return NULL;
993
994    osg::Geometry * geom = new osg::Geometry;
995
996    VertexParams params;
997    params.matrix = matrix;
998    params.smoothNormals = smoothVertexNormals;
999
1000    std::vector<RemappedFace> remappedFaces(faceList.size());
1001
1002    scoped_array<Lib3dsVector> normals( new Lib3dsVector[m->nfaces] );        // Temporary array
1003    lib3ds_mesh_calculate_face_normals(m, normals.get());
1004
1005    osg::ref_ptr<osg::Vec3Array> osg_vertices = new osg::Vec3Array();
1006    osg_vertices->reserve(m->nvertices);
1007    geom->setVertexArray(osg_vertices.get());
1008
1009    osg::ref_ptr<osg::Vec3Array> osg_normals = new osg::Vec3Array();
1010    osg_normals->reserve(m->nvertices);
1011    geom->setNormalArray(osg_normals.get());
1012    geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
1013
1014    osg::ref_ptr<osg::Vec2Array> osg_texCoords = NULL;
1015
1016    if (m->texcos)
1017    {
1018        osg_texCoords = new osg::Vec2Array();
1019        osg_texCoords->reserve(m->nvertices);
1020        geom->setTexCoordArray(0, osg_texCoords.get());
1021
1022        // Texture 0 parameters (only one texture supported for now)
1023        if (ssi.lib3dsmat && *(ssi.lib3dsmat->texture1_map.name))     // valid texture = name not empty
1024        {
1025            Lib3dsTextureMap & tex3ds = ssi.lib3dsmat->texture1_map;
1026            params.scaleUV = osg::Vec2f(tex3ds.scale[0], tex3ds.scale[1]);
1027            params.offsetUV = osg::Vec2f(tex3ds.offset[0], tex3ds.offset[1]);
1028            if (tex3ds.rotation != 0) OSG_NOTICE << "3DS texture rotation not supported yet" << std::endl;
1029            //TODO: tint_1, tint_2, tint_r, tint_g, tint_b
1030        }
1031    }
1032
1033    // The map between lib3ds mesh vertex indices and remapped osg vertices.
1034    std::vector<int> origToNewMapping(m->nvertices, -1);
1035
1036    // If osg vertices need to be split to hold a different vertex normal,
1037    // splitVertexChain allows us to look them up.
1038    std::vector<int> splitVertexChain;
1039    splitVertexChain.reserve(m->nvertices);
1040
1041    unsigned int faceIndex = 0;
1042    unsigned int faceCount = 0;
1043    for (FaceList::iterator itr = faceList.begin();
1044        itr != faceList.end();
1045        ++itr, ++faceIndex)
1046    {
1047        osg::Vec3 normal = copyLib3dsVec3ToOsgVec3(normals[*itr]);
1048        if (matrix) normal = osg::Matrix::transform3x3(normal, *(params.matrix));
1049        normal.normalize();
1050
1051        Lib3dsFace& face = m->faces[*itr];
1052        remappedFaces[faceIndex].face = &face;
1053        remappedFaces[faceIndex].normal = normal;
1054        if (addFace(m, remappedFaces[faceIndex], geom, origToNewMapping, splitVertexChain, params))
1055        {
1056            ++faceCount;
1057        }
1058    }
1059
1060    // 'Shrink to fit' all vertex arrays because potentially faceList refers to fewer vertices than the whole mesh.
1061    // This will almost certainly be the case where mesh has been broken down into smoothing groups.
1062    if (osg_vertices.valid() && osg_vertices->size() < osg_vertices->capacity()) osg_vertices->trim();
1063    if (osg_normals.valid() && osg_normals->size() < osg_normals->capacity()) osg_normals->trim();
1064    if (osg_texCoords.valid() && osg_texCoords->size() < osg_texCoords->capacity()) osg_texCoords->trim();
1065
1066    // Set geometry color to white.
1067    osg::ref_ptr<osg::Vec4ubArray> osg_colors = new osg::Vec4ubArray(1);
1068    (*osg_colors)[0].set(255,255,255,255);
1069    geom->setColorArray(osg_colors.get());
1070    geom->setColorBinding(osg::Geometry::BIND_OVERALL);
1071
1072    // Create triangle primitives.
1073    int numIndices = faceCount * 3;
1074    osg::ref_ptr<DrawElementsUShort> elements = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, numIndices);
1075    DrawElementsUShort::iterator index_itr = elements->begin();
1076
1077    for (unsigned int i = 0; i < remappedFaces.size(); ++i)
1078    {
1079        RemappedFace& remappedFace = remappedFaces[i];
1080        if (remappedFace.face != NULL)
1081        {
1082            *(index_itr++) = remappedFace.index[0];
1083            *(index_itr++) = remappedFace.index[1];
1084            *(index_itr++) = remappedFace.index[2];
1085        }
1086    }
1087
1088    geom->addPrimitiveSet(elements.get());
1089
1090#if 0
1091    osgUtil::TriStripVisitor tsv;
1092    tsv.stripify(*geom);
1093#endif
1094
1095    return geom;
1096}
1097
1098
1099osg::Texture2D*  ReaderWriter3DS::ReaderObject::createTexture(Lib3dsTextureMap *texture,const char* label,bool& transparency)
1100{
1101    if (texture && *(texture->name))
1102    {
1103        OSG_INFO<<"texture->name="<<texture->name<<", _directory="<<_directory<<std::endl;
1104
1105        // First try already loaded textures.
1106        TexturesMap::iterator itTex = texturesMap.find(texture->name);
1107        if (itTex != texturesMap.end()) {
1108            OSG_DEBUG << "Texture '" << texture->name << "' found in cache." << std::endl;
1109            return itTex->second.get();
1110        }
1111
1112        // Texture not in cache: locate and load.
1113        std::string fileName = osgDB::findFileInDirectory(texture->name,_directory,osgDB::CASE_INSENSITIVE);
1114        if (fileName.empty())
1115        {
1116            // file not found in .3ds file's directory, so we'll look in the datafile path list.
1117            fileName = osgDB::findDataFile(texture->name,options, osgDB::CASE_INSENSITIVE);
1118            OSG_INFO<<"texture->name="<<texture->name<<", _directory="<<_directory<<std::endl;
1119        }
1120
1121        if (fileName.empty())
1122        {
1123            if (osgDB::containsServerAddress(_directory))
1124            {
1125                // if 3DS file is loaded from http, just attempt to load texture from same location.
1126                fileName = _directory + "/" + texture->name;
1127            }
1128            else
1129            {
1130                OSG_WARN << "texture '"<<texture->name<<"' not found"<< std::endl;
1131                return NULL;
1132            }
1133        }
1134
1135        if (label) { OSG_DEBUG << label; }
1136        else { OSG_DEBUG << "texture name"; }
1137
1138        OSG_DEBUG << " '"<<texture->name<<"'"<< std::endl;
1139        OSG_DEBUG << "    texture flag        "<<texture->flags<< std::endl;
1140        OSG_DEBUG << "    LIB3DS_TEXTURE_DECALE       "<<((texture->flags)&LIB3DS_TEXTURE_DECALE)<< std::endl;
1141        OSG_DEBUG << "    LIB3DS_TEXTURE_MIRROR       "<<((texture->flags)&LIB3DS_TEXTURE_MIRROR)<< std::endl;
1142        OSG_DEBUG << "    LIB3DS_TEXTURE_NEGATE       "<<((texture->flags)&LIB3DS_TEXTURE_NEGATE)<< std::endl;
1143        OSG_DEBUG << "    LIB3DS_TEXTURE_NO_TILE      "<<((texture->flags)&LIB3DS_TEXTURE_NO_TILE)<< std::endl;
1144        OSG_DEBUG << "    LIB3DS_TEXTURE_SUMMED_AREA  "<<((texture->flags)&LIB3DS_TEXTURE_SUMMED_AREA)<< std::endl;
1145        OSG_DEBUG << "    LIB3DS_TEXTURE_ALPHA_SOURCE "<<((texture->flags)&LIB3DS_TEXTURE_ALPHA_SOURCE)<< std::endl;
1146        OSG_DEBUG << "    LIB3DS_TEXTURE_TINT         "<<((texture->flags)&LIB3DS_TEXTURE_TINT)<< std::endl;
1147        OSG_DEBUG << "    LIB3DS_TEXTURE_IGNORE_ALPHA "<<((texture->flags)&LIB3DS_TEXTURE_IGNORE_ALPHA)<< std::endl;
1148        OSG_DEBUG << "    LIB3DS_TEXTURE_RGB_TINT     "<<((texture->flags)&LIB3DS_TEXTURE_RGB_TINT)<< std::endl;
1149
1150        osg::ref_ptr<osg::Image> osg_image = osgDB::readRefImageFile(fileName.c_str(), options); //Absolute Path
1151        if (!osg_image.valid())
1152        {
1153            OSG_NOTICE << "Warning: Cannot create texture "<<texture->name<< std::endl;
1154            return NULL;
1155        }
1156        if (osg_image->getFileName().empty()) // it should be done in OSG with osgDB::readRefImageFile(fileName.c_str());
1157            osg_image->setFileName(fileName);
1158        osg::Texture2D* osg_texture = new osg::Texture2D;
1159        osg_texture->setImage(osg_image.get());
1160        osg_texture->setName(texture->name);
1161        // does the texture support transparancy?
1162        //transparency = ((texture->flags)&LIB3DS_TEXTURE_ALPHA_SOURCE)!=0;
1163
1164        // what is the wrap mode of the texture.
1165        osg::Texture2D::WrapMode wm = ((texture->flags)&LIB3DS_TEXTURE_NO_TILE) ?
1166                osg::Texture2D::CLAMP :
1167                osg::Texture2D::REPEAT;
1168        osg_texture->setWrap(osg::Texture2D::WRAP_S,wm);
1169        osg_texture->setWrap(osg::Texture2D::WRAP_T,wm);
1170        osg_texture->setWrap(osg::Texture2D::WRAP_R,wm);
1171                                 // bilinear.
1172        osg_texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_NEAREST);
1173
1174        // Insert in cache map
1175        texturesMap.insert(TexturesMap::value_type(texture->name, osg_texture));
1176        return osg_texture;
1177    }
1178    else
1179        return NULL;
1180}
1181
1182
1183ReaderWriter3DS::StateSetInfo ReaderWriter3DS::ReaderObject::createStateSet(Lib3dsMaterial *mat)
1184{
1185    if (mat==NULL) return StateSetInfo();
1186
1187    bool textureTransparency=false;
1188    bool transparency = false;
1189    float alpha = 1.0f - mat->transparency;
1190    int unit = 0;
1191
1192    osg::StateSet* stateset = new osg::StateSet;
1193    osg::Material* material = new osg::Material;
1194
1195    osg::Vec3 ambient(mat->ambient[0],mat->ambient[1],mat->ambient[2]);
1196    osg::Vec3 diffuse(mat->diffuse[0],mat->diffuse[1],mat->diffuse[2]);
1197    osg::Vec3 specular(mat->specular[0],mat->specular[1],mat->specular[2]);
1198    specular *= mat->shin_strength;
1199    float shininess = mat->shininess*128.0f;
1200
1201    // diffuse
1202    osg::Texture2D* texture1_map = createTexture(&(mat->texture1_map),"texture1_map",textureTransparency);
1203    if (texture1_map)
1204    {
1205        stateset->setTextureAttributeAndModes(unit, texture1_map, osg::StateAttribute::ON);
1206
1207        double factor = mat->texture1_map.percent;
1208        if(factor < 1.0)
1209        {
1210            osg::TexEnvCombine* texenv = new osg::TexEnvCombine();
1211            texenv->setCombine_RGB(osg::TexEnvCombine::MODULATE);
1212            texenv->setSource0_RGB(osg::TexEnvCombine::TEXTURE);
1213            texenv->setSource1_RGB(osg::TexEnvCombine::PREVIOUS);
1214            texenv->setSource2_RGB(osg::TexEnvCombine::CONSTANT);
1215            texenv->setConstantColor(osg::Vec4(factor, factor, factor, factor));
1216            stateset->setTextureAttributeAndModes(unit, texenv, osg::StateAttribute::ON);
1217        }
1218        else
1219        {
1220            // from an email from Eric Hamil, September 30, 2003.
1221            // According to the 3DS spec, and other
1222            // software (like Max, Lightwave, and Deep Exploration) a 3DS material that has
1223            // a non-white diffuse base color and a 100% opaque bitmap texture, will show the
1224            // texture with no influence from the base color.
1225
1226            // so we'll override material back to white.
1227            // and no longer require the decal hack below...
1228#if 0
1229            // Eric original fallback
1230            osg::Vec4 white(1.0f,1.0f,1.0f,alpha);
1231            material->setAmbient(osg::Material::FRONT_AND_BACK,white);
1232            material->setDiffuse(osg::Material::FRONT_AND_BACK,white);
1233            material->setSpecular(osg::Material::FRONT_AND_BACK,white);
1234#else
1235            // try alternative to avoid saturating with white
1236            // setting white as per OpenGL defaults.
1237            ambient.set(0.2f,0.2f,0.2f);
1238            diffuse.set(0.8f,0.8f,0.8f);
1239            specular.set(0.0f,0.0f,0.0f);
1240#endif
1241        }
1242
1243        unit++;
1244    }
1245
1246    // opacity
1247    osg::Texture* opacity_map = createTexture(&(mat->opacity_map),"opacity_map", textureTransparency);
1248    if (opacity_map && texture1_map)
1249    {
1250        if(texture1_map->getImage()->isImageTranslucent())
1251        {
1252            transparency = true;
1253
1254            stateset->setTextureAttributeAndModes(unit, opacity_map, osg::StateAttribute::ON);
1255
1256            double factor = mat->opacity_map.percent;
1257
1258                osg::TexEnvCombine* texenv = new osg::TexEnvCombine();
1259                texenv->setCombine_Alpha(osg::TexEnvCombine::INTERPOLATE);
1260                texenv->setSource0_Alpha(osg::TexEnvCombine::TEXTURE);
1261                texenv->setSource1_Alpha(osg::TexEnvCombine::PREVIOUS);
1262                texenv->setSource2_Alpha(osg::TexEnvCombine::CONSTANT);
1263                texenv->setConstantColor(osg::Vec4(factor, factor, factor, 1.0 - factor));
1264                stateset->setTextureAttributeAndModes(unit, texenv, osg::StateAttribute::ON);
1265
1266            unit++;
1267        }
1268        else
1269        {
1270            osg::notify(WARN)<<"The plugin does not support images without alpha channel for opacity"<<std::endl;
1271        }
1272    }
1273
1274    // material
1275    material->setName(mat->name);
1276    material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(ambient, alpha));
1277    material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(diffuse, alpha));
1278    material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(specular, alpha));
1279    material->setShininess(osg::Material::FRONT_AND_BACK, shininess);
1280
1281    stateset->setAttribute(material);
1282
1283    if ((alpha < 1.0f) || transparency)
1284    {
1285        //stateset->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
1286        stateset->setMode(GL_BLEND,osg::StateAttribute::ON);
1287        stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
1288    }
1289
1290    // Set back face culling state if single sided material applied.
1291    // This seems like a reasonable assumption given that the backface cull option
1292    // doesn't appear to be encoded directly in the 3DS format, and also because
1293    // it mirrors the effect of code in 3DS writer which uses the the face culling
1294    // attribute to determine the state of the 'two_sided' 3DS material being written.
1295    if (!mat->two_sided)
1296    {
1297        stateset->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));
1298    }
1299
1300/*
1301    osg::ref_ptr<osg::Texture> texture1_mask = createTexture(&(mat->texture1_mask),"texture1_mask",textureTransparancy);
1302    osg::ref_ptr<osg::Texture> texture2_map = createTexture(&(mat->texture2_map),"texture2_map",textureTransparancy);
1303    osg::ref_ptr<osg::Texture> texture2_mask = createTexture(&(mat->texture2_mask),"texture2_mask",textureTransparancy);
1304    osg::ref_ptr<osg::Texture> opacity_map = createTexture(&(mat->opacity_map),"opacity_map",textureTransparancy);
1305    osg::ref_ptr<osg::Texture> opacity_mask = createTexture(&(mat->opacity_mask),"opacity_mask",textureTransparancy);
1306    osg::ref_ptr<osg::Texture> bump_map = createTexture(&(mat->bump_map),"bump_map",textureTransparancy);
1307    osg::ref_ptr<osg::Texture> bump_mask = createTexture(&(mat->bump_mask),"bump_mask",textureTransparancy);
1308    osg::ref_ptr<osg::Texture> specular_map = createTexture(&(mat->specular_map),"specular_map",textureTransparancy);
1309    osg::ref_ptr<osg::Texture> specular_mask = createTexture(&(mat->specular_mask),"specular_mask",textureTransparancy);
1310    osg::ref_ptr<osg::Texture> shininess_map = createTexture(&(mat->shininess_map),"shininess_map",textureTransparancy);
1311    osg::ref_ptr<osg::Texture> shininess_mask = createTexture(&(mat->shininess_mask),"shininess_mask",textureTransparancy);
1312    osg::ref_ptr<osg::Texture> self_illum_map = createTexture(&(mat->self_illum_map),"self_illum_map",textureTransparancy);
1313    osg::ref_ptr<osg::Texture> self_illum_mask = createTexture(&(mat->self_illum_mask),"self_illum_mask",textureTransparancy);
1314    osg::ref_ptr<osg::Texture> reflection_map = createTexture(&(mat->reflection_map),"reflection_map",textureTransparancy);
1315    osg::ref_ptr<osg::Texture> reflection_mask = createTexture(&(mat->reflection_mask),"reflection_mask",textureTransparancy);
1316*/
1317    return StateSetInfo(stateset, mat);
1318}
1319
1320
1321osgDB::ReaderWriter::WriteResult ReaderWriter3DS::writeNode(const osg::Node& node,const std::string& fileName,const Options* options) const
1322{
1323    std::string ext = osgDB::getLowerCaseFileExtension(fileName);
1324    if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
1325
1326    osgDB::makeDirectoryForFile(fileName.c_str());
1327    osgDB::ofstream fout(fileName.c_str(), std::ios_base::out | std::ios_base::binary);
1328    if (!fout.good()) return WriteResult::ERROR_IN_WRITING_FILE;
1329    return doWriteNode(node, fout, options, fileName);
1330/*
1331    bool ok = true;
1332    Lib3dsFile * file3ds = lib3ds_file_new();
1333    if (!file3ds) return WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
1334
1335    osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
1336    local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));
1337    if (!createFileObject(node, file3ds, fileName, local_opt)) ok = false;
1338    if (ok && !lib3ds_file_save(file3ds, fileName.c_str())) ok = false;
1339    lib3ds_file_free(file3ds);
1340
1341    return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
1342*/
1343}
1344
1345
1346osgDB::ReaderWriter::WriteResult ReaderWriter3DS::writeNode(const osg::Node& node,std::ostream& fout,const Options* options) const
1347{
1348    //OSG_WARN << "!!WARNING!! 3DS write support is incomplete" << std::endl;
1349    std::string optFileName;
1350    if (options)
1351    {
1352        optFileName = options->getPluginStringData("STREAM_FILENAME");
1353    }
1354
1355    return doWriteNode(node, fout, options, optFileName);
1356}
1357
1358osgDB::ReaderWriter::WriteResult ReaderWriter3DS::doWriteNode(const osg::Node& node,std::ostream& fout, const Options* options, const std::string & fileNamelib3ds) const
1359{
1360    osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
1361    local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileNamelib3ds));
1362
1363    Lib3dsIo io;
1364    io.self = &fout;
1365    io.seek_func = fileo_seek_func;
1366    io.tell_func = fileo_tell_func;
1367    io.read_func = NULL;
1368    io.write_func = fileo_write_func;
1369    io.log_func = fileio_log_func;
1370
1371    Lib3dsFile * file3ds = lib3ds_file_new();
1372    if (!file3ds) return WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
1373
1374    bool ok = true;
1375    if (!createFileObject(node, file3ds, fileNamelib3ds, local_opt.get())) ok = false;
1376    if (ok && !lib3ds_file_write(file3ds, &io)) ok = false;
1377    lib3ds_file_free(file3ds);
1378
1379    return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
1380    //return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::FILE_NOT_HANDLED);
1381}
1382
1383bool ReaderWriter3DS::createFileObject(const osg::Node& node, Lib3dsFile * file3ds,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
1384{
1385    plugin3ds::WriterNodeVisitor w(file3ds, fileName, options, osgDB::getFilePath(node.getName()));
1386    const_cast<osg::Node &>(node).accept(w);                // Ugly const_cast<> for visitor...
1387    if (!w.succeeded()) return false;
1388    w.writeMaterials();
1389    return w.succeeded();
1390}
Note: See TracBrowser for help on using the browser.