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

Revision 13041, 41.7 kB (checked in by robert, 3 years ago)

Ran script to remove trailing spaces and tabs

  • Property svn:eol-style set to native
Line 
1// -*-c++-*-
2
3/*
4 * 3DS reader/writer for Open Scene Graph
5 *
6 * Copyright (C) ???
7 *
8 * Writing support added 2009 by Sukender (Benoit Neil), http://sukender.free.fr,
9 * strongly inspired by the OBJ writer object by Stephan Huber
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
17/// [EXPERIMENTAL] Disables animation data (and matrix transforms) for compatibility with some 3rd party apps.
18/// Animations are not read by all 3DS importers. Thus disabling them may allow some 3rd-party apps, such as Rhinoceros (tested with 4.0) to correctly import 3DS files.
19/// However, having proper hierarchy with matrix transforms will become impossible.
20///\warning This is still experimental, hence the compile flag. This should become a reader/writer option as soon as it works as intented (maybe "noMatrixTransforms" could become a read/write option?).
21#define DISABLE_3DS_ANIMATION 0            // Default = 0
22
23#include <osg/io_utils>
24#include <osg/CullFace>
25#include <osg/Billboard>
26#include <osgDB/WriteFile>
27
28#include "WriterNodeVisitor.h"
29#include <assert.h>
30#include <string.h>
31
32
33void copyOsgMatrixToLib3dsMatrix(Lib3dsMatrix lib3ds_matrix, const osg::Matrix& osg_matrix)
34{
35    for(int row=0; row<4; ++row)
36    {
37        lib3ds_matrix[row][0] = osg_matrix.ptr()[row*4+0];
38        lib3ds_matrix[row][1] = osg_matrix.ptr()[row*4+1];
39        lib3ds_matrix[row][2] = osg_matrix.ptr()[row*4+2];
40        lib3ds_matrix[row][3] = osg_matrix.ptr()[row*4+3];
41    }
42}
43
44inline void copyOsgVectorToLib3dsVector(Lib3dsVector lib3ds_vector, const osg::Vec3f& osg_vector)
45{
46    lib3ds_vector[0] = osg_vector[0];
47    lib3ds_vector[1] = osg_vector[1];
48    lib3ds_vector[2] = osg_vector[2];
49}
50inline void copyOsgVectorToLib3dsVector(Lib3dsVector lib3ds_vector, const osg::Vec3d& osg_vector)
51{
52    lib3ds_vector[0] = osg_vector[0];
53    lib3ds_vector[1] = osg_vector[1];
54    lib3ds_vector[2] = osg_vector[2];
55}
56
57inline void copyOsgColorToLib3dsColor(Lib3dsVector lib3ds_vector, const osg::Vec4f& osg_vector)
58{
59    lib3ds_vector[0] = osg_vector[0];
60    lib3ds_vector[1] = osg_vector[1];
61    lib3ds_vector[2] = osg_vector[2];
62}
63inline void copyOsgColorToLib3dsColor(Lib3dsVector lib3ds_vector, const osg::Vec4d& osg_vector)
64{
65    lib3ds_vector[0] = osg_vector[0];
66    lib3ds_vector[1] = osg_vector[1];
67    lib3ds_vector[2] = osg_vector[2];
68}
69
70inline void copyOsgQuatToLib3dsQuat(float lib3ds_vector[4], const osg::Quat& osg_quat)
71{
72    //lib3ds_vector[0] = osg_quat[3];        // Not sure
73    //lib3ds_vector[1] = osg_quat[0];
74    //lib3ds_vector[2] = osg_quat[1];
75    //lib3ds_vector[3] = osg_quat[2];
76    // 3DS seems to store (angle in radians, axis_x, axis_y, axis_z), but it works with (axis_x, axis_y, axis_z, -angle in radians)!
77    osg::Quat::value_type angle, x, y, z;
78    osg_quat.getRotate(angle, x, y, z);
79    lib3ds_vector[0] = static_cast<float>(x);
80    lib3ds_vector[1] = static_cast<float>(y);
81    lib3ds_vector[2] = static_cast<float>(z);
82    lib3ds_vector[3] = static_cast<float>(-angle);
83}
84
85
86/// Checks if a filename (\b not path) is 8.3 (an empty name is never 8.3, and a path is never 8.3).
87/// Please note the '8' and '3' limitations are in \b bytes, not in characters (which is different when using UTF8).
88bool is83(const std::string & s)
89{
90    // 012345678901
91    // ABCDEFGH.ABC
92    if (s.find_first_of("/\\") != std::string::npos) return false;            // It should not be a path, but a filename
93    unsigned int len = s.length();
94    if (len > 12 || len == 0) return false;
95    size_t pointPos = s.rfind('.');
96    if (pointPos == std::string::npos) return len <= 8;        // Without point
97    // With point
98    if (pointPos > 8) return false;
99    if (len-1 - pointPos > 3) return false;
100    return true;
101}
102
103inline std::string::size_type maxNameLen(bool extendedFilePaths, bool isNodeName) {
104    if (extendedFilePaths) return 63;
105    return isNodeName ? 8 : (8+1+3);
106}
107
108/// Tests if the given string is a path supported by 3DS format (8.3, 63 chars max, non empty).
109inline bool is3DSName(const std::string & s, bool extendedFilePaths, bool isNodeName)
110{
111    unsigned int len = s.length();
112    if (len > maxNameLen(extendedFilePaths, isNodeName) || len == 0) return false;
113    // Extended paths are simply those that fits the 64 bytes buffer.
114    if (extendedFilePaths) return true;
115    // Short paths simply must have no subdirectory.
116    return is83(s);
117}
118
119
120// Use namespace qualification to avoid static-link symbol collitions
121// from multiply defined symbols.
122namespace plugin3ds
123{
124
125
126/** writes all primitives of a primitive-set out to a stream, decomposes quads to triangles, line-strips to lines etc */
127class PrimitiveIndexWriter : public osg::PrimitiveIndexFunctor
128{
129public:
130      PrimitiveIndexWriter(osg::Geometry  *    geo,
131                           ListTriangle  &    listTriangles,
132                           unsigned int        drawable_n,
133                           unsigned int        material) :
134          osg::PrimitiveIndexFunctor(),
135          _drawable_n(drawable_n),
136          _listTriangles(listTriangles),
137          _hasNormalCoords(geo->getNormalArray() != NULL),
138          _hasTexCoords(geo->getTexCoordArray(0) != NULL),
139          _geo(geo),
140          _lastFaceIndex(0),
141          _material(material)
142      {
143      }
144
145      unsigned int getNextFaceIndex() { return _lastFaceIndex; }
146
147      virtual void setVertexArray(unsigned int,const osg::Vec2*) {}
148
149      virtual void setVertexArray(unsigned int count,const osg::Vec3* vecs) {}
150
151      virtual void setVertexArray(unsigned int,const osg::Vec4* ) {}
152
153      virtual void setVertexArray(unsigned int,const osg::Vec2d*) {}
154
155      virtual void setVertexArray(unsigned int ,const osg::Vec3d* ) {}
156      virtual void setVertexArray(unsigned int,const osg::Vec4d* ) {}
157
158
159      // operator for triangles
160      void writeTriangle(unsigned int i1, unsigned int i2, unsigned int i3)
161      {
162          Triangle triangle;
163          triangle.t1 = i1;
164          triangle.t2 = i2;
165          triangle.t3 = i3;
166          triangle.material = _material;
167          _listTriangles.push_back(ListTriangle::value_type(triangle, _drawable_n));
168      }
169      virtual void begin(GLenum mode)
170      {
171          _modeCache = mode;
172          _indexCache.clear();
173      }
174
175      virtual void vertex(unsigned int vert)
176      {
177          _indexCache.push_back(vert);
178      }
179
180      virtual void end()
181      {
182          if (!_indexCache.empty())
183          {
184              drawElements(_modeCache,_indexCache.size(),&_indexCache.front());
185          }
186      }
187
188      virtual void drawArrays(GLenum mode,GLint first,GLsizei count);
189
190      virtual void drawElements(GLenum mode,GLsizei count,const GLubyte* indices)
191      {
192          drawElementsImplementation<GLubyte>(mode, count, indices);
193      }
194      virtual void drawElements(GLenum mode,GLsizei count,const GLushort* indices)
195      {
196          drawElementsImplementation<GLushort>(mode, count, indices);
197      }
198
199      virtual void drawElements(GLenum mode,GLsizei count,const GLuint* indices)
200      {
201          drawElementsImplementation<GLuint>(mode, count, indices);
202      }
203
204protected:
205
206    template<typename T>void drawElementsImplementation(GLenum mode, GLsizei count, const T* indices)
207    {
208        if (indices==0 || count==0) return;
209
210        typedef const T* IndexPointer;
211
212        switch(mode)
213        {
214        case(GL_TRIANGLES):
215            {
216                //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count / 3);
217                IndexPointer ilast = &indices[count];
218                for(IndexPointer  iptr=indices;iptr<ilast;iptr+=3)
219                    writeTriangle(*iptr,*(iptr+1),*(iptr+2));
220
221                break;
222            }
223        case(GL_TRIANGLE_STRIP):
224            {
225                //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count -2);
226                IndexPointer iptr = indices;
227                for(GLsizei i=2;i<count;++i,++iptr)
228                {
229                    if ((i%2)) writeTriangle(*(iptr),*(iptr+2),*(iptr+1));
230                    else       writeTriangle(*(iptr),*(iptr+1),*(iptr+2));
231                }
232                break;
233            }
234        case(GL_QUADS):
235            {
236                //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count /2);        // count/4*2
237                IndexPointer iptr = indices;
238                for(GLsizei i=3;i<count;i+=4,iptr+=4)
239                {
240                    writeTriangle(*(iptr),*(iptr+1),*(iptr+2));
241                    writeTriangle(*(iptr),*(iptr+2),*(iptr+3));
242                }
243                break;
244            }
245        case(GL_QUAD_STRIP):
246            {
247                //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + (count / 2 -1)*2);
248                IndexPointer iptr = indices;
249                for(GLsizei i=3;i<count;i+=2,iptr+=2)
250                {
251                    writeTriangle(*(iptr),*(iptr+1),*(iptr+2));
252                    writeTriangle(*(iptr+1),*(iptr+3),*(iptr+2));
253                }
254                break;
255            }
256        case(GL_POLYGON): // treat polygons as GL_TRIANGLE_FAN
257        case(GL_TRIANGLE_FAN):
258            {
259                //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count -2);
260                IndexPointer iptr = indices;
261                unsigned int first = *iptr;
262                ++iptr;
263                for(GLsizei i=2;i<count;++i,++iptr)
264                {
265                    writeTriangle(first,*(iptr),*(iptr+1));
266                }
267                break;
268            }
269        case(GL_POINTS):
270        case(GL_LINES):
271        case(GL_LINE_STRIP):
272        case(GL_LINE_LOOP):
273            // Not handled
274            break;
275
276        default:
277            // uhm should never come to this point :)
278            break;
279        }
280    }
281
282private:
283
284    PrimitiveIndexWriter& operator = (const PrimitiveIndexWriter&) { return *this; }
285
286    unsigned int         _drawable_n;
287    ListTriangle    &     _listTriangles;
288    GLenum               _modeCache;
289    std::vector<GLuint>  _indexCache;
290    bool                 _hasNormalCoords, _hasTexCoords;
291    osg::Geometry*       _geo;
292    unsigned int         _lastFaceIndex;
293    unsigned int         _material;
294};
295
296
297void PrimitiveIndexWriter::drawArrays(GLenum mode,GLint first,GLsizei count)
298{
299    switch(mode)
300    {
301    case(GL_TRIANGLES):
302        {
303            unsigned int pos=first;
304            for(GLsizei i=2;i<count;i+=3,pos+=3)
305            {
306                writeTriangle(pos,pos+1,pos+2);
307            }
308            break;
309        }
310    case(GL_TRIANGLE_STRIP):
311        {
312            unsigned int pos=first;
313            for(GLsizei i=2;i<count;++i,++pos)
314            {
315                if ((i%2)) writeTriangle(pos,pos+2,pos+1);
316                else       writeTriangle(pos,pos+1,pos+2);
317            }
318            break;
319        }
320    case(GL_QUADS):
321        {
322            unsigned int pos=first;
323            for(GLsizei i=3;i<count;i+=4,pos+=4)
324            {
325                writeTriangle(pos,pos+1,pos+2);
326                writeTriangle(pos,pos+2,pos+3);
327            }
328            break;
329        }
330    case(GL_QUAD_STRIP):
331        {
332            unsigned int pos=first;
333            for(GLsizei i=3;i<count;i+=2,pos+=2)
334            {
335                writeTriangle(pos,pos+1,pos+2);
336                writeTriangle(pos+1,pos+3,pos+2);
337            }
338            break;
339        }
340    case(GL_POLYGON): // treat polygons as GL_TRIANGLE_FAN
341    case(GL_TRIANGLE_FAN):
342        {
343            unsigned int pos=first+1;
344            for(GLsizei i=2;i<count;++i,++pos)
345            {
346                writeTriangle(first,pos,pos+1);
347            }
348            break;
349        }
350    case(GL_POINTS):
351    case(GL_LINES):
352    case(GL_LINE_STRIP):
353    case(GL_LINE_LOOP):
354        //break;
355    default:
356        OSG_WARN << "3DS WriterNodeVisitor: can't handle mode " << mode << std::endl;
357        break;
358    }
359}
360
361
362
363WriterNodeVisitor::Material::Material(WriterNodeVisitor & writerNodeVisitor, osg::StateSet * stateset, osg::Material* mat, osg::Texture* tex, int index) :
364    index(index),
365    diffuse(1,1,1,1),
366    ambient(0.2,0.2,0.2,1),
367    specular(0,0,0,1),
368    shininess(0),
369    transparency(0),
370    double_sided(false),
371    image(NULL),
372    texture_transparency(false),
373    texture_no_tile(true) // matches lib3ds_material.cpp initialize_texture_map(..) default flag setting
374{
375    //static unsigned int s_objmaterial_id = 0;
376    //++s_objmaterial_id;
377    if (mat)
378    {
379        assert(stateset);
380        diffuse = mat->getDiffuse(osg::Material::FRONT);
381        ambient = mat->getAmbient(osg::Material::FRONT);
382        specular = mat->getSpecular(osg::Material::FRONT);
383        shininess = mat->getShininess(osg::Material::FRONT) / 128.f;
384        // OpenGL shininess = pow(2, 10.0*mat->shininess);            (As in lib3ds example)
385        // => mat->shininess = log.2( OpenGL shininess ) /10        (if values are >0)
386        // => mat->shininess = log( OpenGL shininess ) / log(2) /10
387        //shininess = mat->getShininess(osg::Material::FRONT) <= 0 ? 0 : log( mat->getShininess(osg::Material::FRONT) ) / log(2.f) / 10.f;
388
389        transparency = 1-diffuse.w();
390        name = writerNodeVisitor.getUniqueName(mat->getName(),true,"mat");
391        osg::StateAttribute * attribute = stateset->getAttribute(osg::StateAttribute::CULLFACE);
392        if (!attribute)
393        {
394            double_sided = true;
395        }
396        else
397        {
398            assert(dynamic_cast<osg::CullFace *>(attribute));
399            osg::CullFace::Mode mode = static_cast<osg::CullFace *>(attribute)->getMode();
400            if (mode == osg::CullFace::BACK) double_sided = false;
401            else if (mode == osg::CullFace::FRONT)
402            {
403                OSG_WARN << "3DS Writer: Reversed face (culled FRONT) not supported yet." << std::endl;
404                double_sided = false;
405            }
406            else
407            {
408                assert(mode == osg::CullFace::FRONT_AND_BACK);
409                OSG_WARN << "3DS Writer: Invisible face (culled FRONT_AND_BACK) not supported yet." << std::endl;
410                double_sided = false;
411            }
412        }
413    }
414    if (tex)
415    {
416        osg::Image* img = tex->getImage(0);
417        if(img)
418        {
419            texture_transparency = (stateset->getMode(GL_BLEND) == osg::StateAttribute::ON);
420            osg::Texture::WrapMode wrapS = tex->getWrap(osg::Texture2D::WRAP_S);
421            texture_no_tile = !(wrapS == osg::Texture2D::REPEAT || wrapS == osg::Texture2D::MIRROR);
422            image = img;
423        }
424    }
425
426    if (name.empty())
427    {
428        std::stringstream ss;
429        ss << "m" << index;
430        name = ss.str();
431    }
432}
433
434
435/// Converts an extension to a 3-letters long one equivalent.
436std::string convertExt(const std::string & path, bool extendedFilePaths)
437{
438    if (extendedFilePaths) return path;        // Extensions are not truncated for extended filenames
439
440    std::string ext = osgDB::getFileExtensionIncludingDot(path);
441    if (ext == ".tiff") ext = ".tif";
442    else if (ext == ".jpeg") ext = ".jpg";
443    else if (ext == ".jpeg2000" || ext == ".jpg2000") ext = ".jpc";
444    return osgDB::getNameLessExtension(path) + ext;
445}
446
447
448WriterNodeVisitor::WriterNodeVisitor(Lib3dsFile * file3ds, const std::string & fileName,
449                const osgDB::ReaderWriter::Options* options,
450                const std::string & srcDirectory) :
451    osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
452    _succeeded(true),
453    _srcDirectory(srcDirectory),
454    _file3ds(file3ds),
455    _currentStateSet(new osg::StateSet()),
456    _lastMaterialIndex(0),
457    _lastMeshIndex(0),
458    _cur3dsNode(NULL),
459    _options(options),
460    _imageCount(0),
461    _extendedFilePaths(false)
462{
463    if (!fileName.empty())
464        _directory = options->getDatabasePathList().empty() ? osgDB::getFilePath(fileName) : options->getDatabasePathList().front();
465
466    if (options)
467    {
468        std::istringstream iss(options->getOptionString());
469        std::string opt;
470        while (iss >> opt)
471        {
472            if (opt == "extended3dsFilePaths" || opt == "extended3DSFilePaths")
473                _extendedFilePaths = true;
474        }
475    }
476}
477
478
479void WriterNodeVisitor::writeMaterials()
480{
481    unsigned int nbMat = _materialMap.size();
482    lib3ds_file_reserve_materials(_file3ds, nbMat, 1);
483    // Ugly thing: it seems lib3ds_file_insert_material() doesn't support insertion in a random order (else materials are not assigned the right way)
484    for (unsigned int iMat=0; iMat<nbMat; ++iMat)
485    {
486        for(MaterialMap::iterator itr = _materialMap.begin(); itr != _materialMap.end(); ++itr)
487        {
488            const Material & mat = itr->second;
489            if (mat.index != static_cast<int>(iMat)) continue;        // Ugly thing (2)
490
491            Lib3dsMaterial * mat3ds = lib3ds_material_new(osgDB::getSimpleFileName(mat.name).c_str());
492            copyOsgColorToLib3dsColor(mat3ds->ambient,  mat.ambient);
493            copyOsgColorToLib3dsColor(mat3ds->diffuse,  mat.diffuse);
494            copyOsgColorToLib3dsColor(mat3ds->specular, mat.specular);
495            mat3ds->shininess = mat.shininess;
496            mat3ds->transparency = mat.transparency;
497            mat3ds->two_sided = mat.double_sided ? 1 : 0;
498            if (mat.image)
499            {
500                std::string path;
501                ImageSet::const_iterator itImage( _imageSet.find(mat.image.get()) );
502                if (itImage != _imageSet.end())
503                {
504                    // Image has been already used
505                    path = itImage->second;
506                }
507                else
508                {
509                    // First time we 'see' this image
510                    if (mat.image->getFileName().empty())
511                    {
512                        std::ostringstream oss;
513                        oss << "Image_" << _imageCount++ << ".rgb";
514                        path = oss.str();
515                    }
516                    else
517                    {
518                        path = osgDB::getPathRelative(_srcDirectory, mat.image->getFileName());
519                    }
520                    path = convertExt(path, _extendedFilePaths);
521                    path = getUniqueName(path, false, "");
522
523                    // Write
524                    const std::string fullPath( osgDB::concatPaths(_directory, path) );
525                    osgDB::makeDirectoryForFile(fullPath);
526                    osgDB::writeImageFile(*(mat.image), fullPath, _options);
527
528                    // Insert in map
529                    _imageSet.insert(ImageSet::value_type(mat.image.get(), path));
530                }
531
532                Lib3dsTextureMap & tex = mat3ds->texture1_map;
533                strcpy(tex.name, path.c_str());
534                // Here we don't assume anything about initial flags state (actually it is set to LIB3DS_TEXTURE_NO_TILE by lib3DS, but this is subject to change)
535                if (mat.texture_transparency) tex.flags |= LIB3DS_TEXTURE_ALPHA_SOURCE;
536                else tex.flags &= ~LIB3DS_TEXTURE_ALPHA_SOURCE;
537                if (mat.texture_no_tile) tex.flags |= LIB3DS_TEXTURE_NO_TILE;
538                else tex.flags &= ~LIB3DS_TEXTURE_NO_TILE;
539            }
540            if (!succeeded())
541                return;
542            lib3ds_file_insert_material(_file3ds, mat3ds, itr->second.index);
543            break;        // Ugly thing (3)
544        }
545    }
546}
547
548/// Truncates an UTF8 string so that it does not takes more than a given \b bytes amount (\b excluding the potential NULL end character).
549/// The function assumes the UTF8 string is valid.
550///\return A valid UTF8 string which size is less or equal to \c byteLimit.
551// May be moved in osgDB/ConvertUTF?
552std::string utf8TruncateBytes(const std::string & s, std::string::size_type byteLimit) {
553    // Untruncated strings
554    if (s.size() <= byteLimit) return s;
555
556    // Truncated strings
557    std::string::const_iterator it=s.begin(), itEnd=s.begin()+byteLimit;
558    std::string::const_iterator itStop=it;
559    // Note: itEnd is < s.end(), so that we can always write "it+1"
560    for(; it!=itEnd; ++it) {
561        unsigned char c = static_cast<unsigned char>(*it);
562        if ((c & 0x80) == 0) itStop=it+1;        // 7 bits ANSI. itStop must then point after that character.
563        else if ((c & 0x40) != 0) itStop=it;    // UTF8 sequence start: this is also past-the-end for the previous character (ANSI or UTF8)
564    }
565    return std::string(s.begin(), itStop);
566}
567
568#ifdef OSG_USE_UTF8_FILENAME
569#    define truncateFilenameBytes(str, size) utf8TruncateBytes(str, size)
570#else
571#    define truncateFilenameBytes(str, size) std::string(str, 0, size)
572#endif
573
574std::string WriterNodeVisitor::getUniqueName(const std::string& _defaultValue, bool isNodeName, const std::string & _defaultPrefix, int currentPrefixLen)
575{
576    //const unsigned int MAX_LENGTH = maxNameLen(_extendedFilePaths);
577    const unsigned int MAX_PREFIX_LENGTH = _extendedFilePaths ? 52 : 6;        // Arbitrarily defined for short names, kept enough room for displaying UINT_MAX (10 characters) for long names.
578    assert(_defaultPrefix.length()<=4);        // Default prefix is too long (implementation error)
579    const std::string defaultPrefix(_defaultPrefix.empty() ? "_" : _defaultPrefix);
580    if (currentPrefixLen<0) currentPrefixLen = osg::maximum(_defaultPrefix.length(), _defaultValue.length());
581    currentPrefixLen = osg::clampBelow(currentPrefixLen, static_cast<int>(MAX_PREFIX_LENGTH));
582
583    // Tests if default name is valid and unique
584    NameMap & nameMap = isNodeName ? _nodeNameMap : _imageNameMap;
585    PrefixMap & prefixMap = isNodeName ? _nodePrefixMap : _imagePrefixMap;
586
587    // Handling of paths is simple. Algorithm:
588    //    - For short names, subdirectories are simply forbidden. Use the simple file name.
589    //    - Else, the whole (relative) path must simply be <64 chars.
590    // After this, begin enumeration.
591    std::string parentPath, filename, ext, namePrefix;
592    unsigned int max_val = 0;
593
594    // TODO Move the two parts of this giant if/else into two separate functions for better readability.
595    if (_extendedFilePaths)
596    {
597        // Tests if default name is valid and unique
598        if (is3DSName(_defaultValue, _extendedFilePaths, isNodeName))
599        {
600            std::pair<NameMap::iterator, bool> insertion( nameMap.insert(_defaultValue) );
601            if (insertion.second) return _defaultValue;        // Return if element is newly inserted in the map (else there is a naming collision)
602        }
603
604        // Simply separate name and last extension
605        filename = osgDB::getNameLessExtension(osgDB::getSimpleFileName(_defaultValue));
606        if (!isNodeName)
607        {
608            ext = osgDB::getFileExtensionIncludingDot(_defaultValue);
609            if (ext == ".") ext = "";
610        }
611
612        // Compute parent path
613        // Pre-condition: paths are supposed to be relative.
614        // If full path is too long (>MAX_PREFIX_LENGTH), cut path to let enough space for simple file name.
615        // Do not cut in the middle of a name, but at path separators.
616        parentPath = osgDB::getFilePath(_defaultValue);
617        if (_defaultValue.length() >MAX_PREFIX_LENGTH)        // Not parentPath but _defaultValue!
618        {
619            // Nodes names: keep last directories (used only for the root name, generally named after the full file path)
620            // Images names: keep first directories (for images)
621            if (isNodeName) std::reverse(parentPath.begin(), parentPath.end());
622            unsigned lenToDelete(filename.length() + ext.length() + 1);
623            lenToDelete = osg::clampBelow(lenToDelete, MAX_PREFIX_LENGTH);
624            parentPath = truncateFilenameBytes(parentPath, MAX_PREFIX_LENGTH - lenToDelete);        // +1 for the path separator
625            std::string::size_type separator = parentPath.find_last_of("/\\");
626            if (separator != std::string::npos) parentPath = parentPath.substr(0, separator);
627            if (isNodeName) std::reverse(parentPath.begin(), parentPath.end());
628        }
629
630        // Assert "MAX_PREFIX_LENGTH - parent path length - extension length -1" is >=0 and truncate name to this length to get our new prefix.
631        assert(parentPath.length() + ext.length() <= MAX_PREFIX_LENGTH);
632        const unsigned int len = MAX_PREFIX_LENGTH - (parentPath.length() + ext.length() +1);
633        namePrefix = truncateFilenameBytes(filename, len);
634        if (namePrefix.empty()) namePrefix = defaultPrefix;
635
636        // Truncate the filename to get our new prefix
637        namePrefix = truncateFilenameBytes(filename, currentPrefixLen);
638
639        // Enough space has been reserved for UINT_MAX values
640        max_val = UINT_MAX;
641    }
642    else
643    {
644        // Get last extension, and make filename have no extension
645        filename = osgDB::getNameLessAllExtensions(osgDB::getSimpleFileName(_defaultValue));
646        if (!isNodeName)
647        {
648            ext = truncateFilenameBytes(osgDB::getFileExtensionIncludingDot(_defaultValue), 4);        // 4 chars = dot + 3 chars
649            if (ext == ".") ext = "";
650        }
651
652        // Tests if STRIPPED default name is valid and unique
653        const std::string strippedName( filename + ext );
654        if (is3DSName(strippedName, _extendedFilePaths, isNodeName))
655        {
656            std::pair<NameMap::iterator, bool> insertion( nameMap.insert(strippedName) );
657            if (insertion.second) return strippedName;        // Return if element is newly inserted in the map (else there is a naming collision)
658        }
659
660        namePrefix = filename;
661        if (namePrefix.empty()) namePrefix = defaultPrefix;
662        // Truncate the filename to get our new prefix
663        namePrefix = truncateFilenameBytes(namePrefix, currentPrefixLen);
664
665        // Compute the maximum enumeration value
666        max_val = static_cast<unsigned int>(pow(10., 8. - namePrefix.length())) -1;
667    }
668    assert(namePrefix.size() <= MAX_PREFIX_LENGTH);
669
670    // Find the current enumeration value (searchStart)
671    unsigned int searchStart(0);
672    PrefixMap::iterator pairPrefix( prefixMap.find(namePrefix) );
673    if (pairPrefix != prefixMap.end())
674    {
675        searchStart = pairPrefix->second;
676    }
677    else
678    {
679        // Check if truncated name is ok
680        const std::string res( osgDB::concatPaths(parentPath, namePrefix + ext) );
681        if (nameMap.find(res) == nameMap.end()) {
682            prefixMap.insert(std::pair<std::string, unsigned int>(namePrefix, 0));
683            nameMap.insert(res);
684            return res;
685        }
686    }
687
688    // Search for a free value
689    for(unsigned int i = searchStart; i <= max_val; ++i)
690    {
691        std::stringstream ss;
692        ss << namePrefix << i;
693        const std::string res( osgDB::concatPaths(parentPath, ss.str() + ext) );
694        if (nameMap.find(res) == nameMap.end())
695        {
696            if (pairPrefix != prefixMap.end())
697            {
698                pairPrefix->second = i + 1;
699            } else
700            {
701                prefixMap.insert(std::pair<std::string, unsigned int>(namePrefix, i + 1));
702            }
703            nameMap.insert(res);
704            return res;
705        }
706    }
707
708    // Failed finding a name
709    // Try with a shorter prefix if possible
710    if (currentPrefixLen>1) return getUniqueName(_defaultValue, isNodeName, defaultPrefix, currentPrefixLen-1);
711    // Try with default prefix if not arleady done
712    if (defaultPrefix != std::string("_")) return getUniqueName(_defaultValue, isNodeName, "_", 1);
713
714    // No more names
715    OSG_NOTIFY(osg::FATAL) << "No more names available!" << std::endl;
716    _succeeded = false;
717    return "ERROR";
718}
719
720int WriterNodeVisitor::processStateSet(osg::StateSet* ss)
721{
722    MaterialMap::const_iterator itr = _materialMap.find(ss);
723    if (itr != _materialMap.end())
724    {
725        assert(itr->second.index>=0);
726        return itr->second.index;
727    }
728
729    osg::Material* mat = dynamic_cast<osg::Material*>(ss->getAttribute(osg::StateAttribute::MATERIAL));
730    osg::Texture* tex = dynamic_cast<osg::Texture*>(ss->getTextureAttribute(0, osg::StateAttribute::TEXTURE));
731
732    if (mat || tex)
733    {
734        int matNum = _lastMaterialIndex;
735        _materialMap.insert(std::make_pair(osg::ref_ptr<osg::StateSet>(ss), Material(*this, ss, mat, tex, matNum) ));
736        ++_lastMaterialIndex;
737        return matNum;
738    }
739    return -1;
740}
741
742/**
743*  Add a vertice to the index and link it with the Triangle index and the drawable.
744*  \param index_vert is the map where the vertice are stored.
745*  \param index is the indice of the vertice's position in the vec3.
746*  \param drawable_n is the number of the drawable.
747*  \return the position of the vertice in the final mesh.
748*/
749unsigned int
750WriterNodeVisitor::getMeshIndexForGeometryIndex(MapIndices & index_vert,
751                                                unsigned int index,
752                                                unsigned int drawable_n)
753{
754    MapIndices::iterator itIndex = index_vert.find(std::pair<unsigned int, unsigned int>(index, drawable_n));
755    if (itIndex == index_vert.end())
756    {
757        unsigned int indexMesh = index_vert.size();
758        index_vert.insert(std::make_pair(std::pair<unsigned int, unsigned int>(index, drawable_n), indexMesh));
759        return indexMesh;
760    }
761    return itIndex->second;
762}
763
764
765void
766WriterNodeVisitor::buildMesh(osg::Geode        & geo,
767                             const osg::Matrix & mat,
768                             MapIndices        & index_vert,
769                             bool                texcoords,
770                             Lib3dsMesh        * mesh)
771{
772    OSG_DEBUG << "Building Mesh" << std::endl;
773    assert(mesh);
774
775    // Write points
776    assert(index_vert.size() <= MAX_VERTICES);
777    lib3ds_mesh_resize_vertices(mesh, index_vert.size(), texcoords ? 1 : 0, 0);
778
779    for(MapIndices::iterator it = index_vert.begin(); it != index_vert.end();++it)
780    {
781        osg::Geometry *g = geo.getDrawable( it->first.second )->asGeometry();
782        const osg::Array * basevecs = g->getVertexArray();
783        assert(basevecs);
784        if (!basevecs || basevecs->getNumElements()==0) continue;
785        if (basevecs->getType() == osg::Array::Vec3ArrayType)
786        {
787            const osg::Vec3Array & vecs= *static_cast<const osg::Vec3Array *>(basevecs);
788            copyOsgVectorToLib3dsVector(mesh->vertices[it->second], vecs[it->first.first]*mat);
789        }
790        else if (basevecs->getType() == osg::Array::Vec3dArrayType)
791        {
792            // Handle double presision vertices by converting them to float with a warning
793            OSG_NOTICE << "3DS format only supports single precision vertices. Converting double precision to single." << std::endl;
794            const osg::Vec3dArray & vecs= *static_cast<const osg::Vec3dArray *>(basevecs);
795            copyOsgVectorToLib3dsVector(mesh->vertices[it->second], vecs[it->first.first]*mat);
796        }
797        else
798        {
799            OSG_NOTIFY(osg::FATAL) << "Vertex array is not Vec3 or Vec3d. Not implemented" << std::endl;
800            _succeeded = false;
801            return;
802        }
803    }
804
805    // Write texture coords (Texture 0 only)
806    if (texcoords)
807    {
808        for(MapIndices::iterator it = index_vert.begin(); it != index_vert.end(); ++it)
809        {
810            osg::Geometry *g = geo.getDrawable( it->first.second )->asGeometry();
811            const osg::Array * texarray = g->getNumTexCoordArrays()>=1 ? g->getTexCoordArray(0) : NULL;
812            if (!texarray || texarray->getNumElements()==0) continue;
813
814            if (g->getTexCoordArray(0)->getType() != osg::Array::Vec2ArrayType)
815            {
816                OSG_NOTIFY(osg::FATAL) << "Texture coords array is not Vec2. Not implemented" << std::endl;
817                _succeeded = false;
818                return;
819            }
820            const osg::Vec2Array & vecs= *static_cast<const osg::Vec2Array *>(texarray);
821            mesh->texcos[it->second][0] = vecs[it->first.first][0];
822            mesh->texcos[it->second][1] = vecs[it->first.first][1];
823        }
824    }
825    lib3ds_file_insert_mesh(_file3ds, mesh, _lastMeshIndex);
826    ++_lastMeshIndex;
827
828    Lib3dsMeshInstanceNode * node3ds = lib3ds_node_new_mesh_instance(mesh, mesh->name, NULL, NULL, NULL);
829    lib3ds_file_append_node(_file3ds, reinterpret_cast<Lib3dsNode*>(node3ds), reinterpret_cast<Lib3dsNode*>(_cur3dsNode));
830}
831
832unsigned int
833WriterNodeVisitor::calcVertices(osg::Geode & geo)
834{
835    unsigned int numVertice = 0;
836    for (unsigned int i = 0; i < geo.getNumDrawables(); ++i)
837    {
838        osg::Geometry *g = geo.getDrawable( i )->asGeometry();
839        if (g!=NULL && g->getVertexArray()) numVertice += g->getVertexArray()->getNumElements();
840    }
841    return numVertice;
842}
843
844
845void
846WriterNodeVisitor::buildFaces(osg::Geode        & geo,
847                              const osg::Matrix & mat,
848                              ListTriangle      & listTriangles,
849                              bool                texcoords)
850{
851    unsigned int nbTrianglesRemaining = listTriangles.size();
852    unsigned int nbVerticesRemaining  = calcVertices(geo);        // May set _succeded to false
853    if (!succeeded()) return;
854
855    std::string name( getUniqueName(geo.getName().empty() ? geo.className() : geo.getName(), true, "geo") );
856    if (!succeeded()) return;
857    Lib3dsMesh *mesh = lib3ds_mesh_new( name.c_str() );
858    if (!mesh)
859    {
860        OSG_NOTIFY(osg::FATAL) << "Allocation error" << std::endl;
861        _succeeded = false;
862        return;
863    }
864
865    //copyOsgMatrixToLib3dsMatrix(mesh->matrix, mat);
866    lib3ds_mesh_resize_faces   (mesh, osg::minimum(nbTrianglesRemaining, MAX_FACES));
867    lib3ds_mesh_resize_vertices(mesh, osg::minimum(nbVerticesRemaining,  MAX_VERTICES), texcoords ? 0 : 1, 0);        // Not mandatory but will allocate once a big block
868
869    // Test if the mesh will be split and needs sorting
870    if (nbVerticesRemaining >= MAX_VERTICES || nbTrianglesRemaining >= MAX_FACES)
871    {
872        OSG_INFO << "Sorting elements..." << std::endl;
873        WriterCompareTriangle cmp(geo, nbVerticesRemaining);
874        std::sort(listTriangles.begin(), listTriangles.end(), cmp);
875    }
876
877    MapIndices index_vert;
878    unsigned int numFace = 0;        // Current face index
879    for (ListTriangle::iterator it = listTriangles.begin(); it != listTriangles.end(); ++it) //Go through the triangle list to define meshs
880    {
881        // Test if the mesh will be full after adding a face
882        if (index_vert.size()+3 >= MAX_VERTICES || numFace+1 >= MAX_FACES)
883        {
884            // Finnish mesh
885            lib3ds_mesh_resize_faces   (mesh, numFace);
886            //lib3ds_mesh_resize_vertices() will be called in buildMesh()
887            buildMesh(geo, mat, index_vert, texcoords, mesh);        // May set _succeded to false
888            if (!succeeded())
889            {
890                lib3ds_mesh_free(mesh);
891                return;
892            }
893
894            // "Reset" values and start over a new mesh
895            index_vert.clear();
896            nbTrianglesRemaining -= numFace;
897            numFace = 0;
898            // We can't call a thing like "nbVerticesRemaining -= ...;" because points may be used multiple times.
899            // [Sukender: An optimisation here would take too much time I think.]
900
901            mesh = lib3ds_mesh_new( getUniqueName(geo.getName().empty() ? geo.className() : geo.getName(), true, "geo").c_str());
902            if (!mesh)
903            {
904                OSG_NOTIFY(osg::FATAL) << "Allocation error" << std::endl;
905                _succeeded = false;
906                return;
907            }
908            lib3ds_mesh_resize_faces   (mesh, osg::minimum(nbTrianglesRemaining, MAX_FACES));
909            lib3ds_mesh_resize_vertices(mesh, osg::minimum(nbVerticesRemaining,  MAX_VERTICES), texcoords ? 0 : 1, 0);        // Not mandatory but will allocate once a big block
910        }
911        Lib3dsFace & face = mesh->faces[numFace++];
912        face.index[0] = getMeshIndexForGeometryIndex(index_vert, it->first.t1, it->second);
913        face.index[1] = getMeshIndexForGeometryIndex(index_vert, it->first.t2, it->second);
914        face.index[2] = getMeshIndexForGeometryIndex(index_vert, it->first.t3, it->second);
915        face.material = it->first.material;
916    }
917
918    buildMesh(geo, mat, index_vert, texcoords, mesh);        // May set _succeded to false
919    if (!succeeded())
920    {
921        lib3ds_mesh_free(mesh);
922        return;
923    }
924}
925
926void
927WriterNodeVisitor::createListTriangle(osg::Geometry * geo,
928                                      ListTriangle  & listTriangles,
929                                      bool          & texcoords,
930                                      unsigned int  & drawable_n)
931{
932    const osg::Array * basevecs = geo->getVertexArray();
933    if (!basevecs || basevecs->getNumElements()==0) return;
934
935    // Texture coords
936    const osg::Array * basetexvecs = geo->getNumTexCoordArrays()>=1 ? geo->getTexCoordArray(0) : NULL;
937    if (basetexvecs)
938    {
939        unsigned int nb = basetexvecs->getNumElements();
940        if (nb != geo->getVertexArray()->getNumElements())
941        {
942            OSG_NOTIFY(osg::FATAL) << "There are more/less texture coords than vertices (corrupted geometry)" << std::endl;
943            _succeeded = false;
944            return;
945        }
946        texcoords = true;
947    }
948
949    int material = processStateSet(_currentStateSet.get());
950
951    for(unsigned int i = 0; i < geo->getNumPrimitiveSets(); ++i) //Fill the Triangle List
952    {
953        osg::PrimitiveSet* ps = geo->getPrimitiveSet(i);
954        PrimitiveIndexWriter pif(geo, listTriangles, drawable_n, material);
955        ps->accept(pif);
956    }
957}
958
959void WriterNodeVisitor::apply( osg::Geode &node )
960{
961    pushStateSet(node.getStateSet());
962    //_nameStack.push_back(node.getName());
963    unsigned int count = node.getNumDrawables();
964    ListTriangle listTriangles;
965    bool texcoords = false;
966    for ( unsigned int i = 0; i < count; i++ )
967    {
968        osg::Geometry *g = node.getDrawable( i )->asGeometry();
969        if ( g != NULL )
970        {
971            pushStateSet(g->getStateSet());
972            createListTriangle(g, listTriangles, texcoords, i);        // May set _succeded to false
973            popStateSet(g->getStateSet());
974            if (!succeeded()) break;
975        }
976    }
977    if (succeeded() && count > 0)
978    {
979#if DISABLE_3DS_ANIMATION
980        osg::Matrix mat( osg::computeLocalToWorld(getNodePath()) );
981        buildFaces(node, mat, listTriangles, texcoords);        // May set _succeded to false
982#else
983        buildFaces(node, osg::Matrix(), listTriangles, texcoords);        // May set _succeded to false
984#endif
985    }
986    popStateSet(node.getStateSet());
987    //_nameStack.pop_back();
988    if (succeeded())
989        traverse(node);
990}
991
992void WriterNodeVisitor::apply( osg::Billboard &node )
993{
994    // TODO Does not handle Billboards' points yet
995
996    pushStateSet(node.getStateSet());
997    Lib3dsMeshInstanceNode * parent = _cur3dsNode;
998
999    unsigned int count = node.getNumDrawables();
1000    ListTriangle listTriangles;
1001    bool texcoords = false;
1002    OSG_NOTICE << "Warning: 3DS writer is incomplete for Billboards (rotation not implemented)." << std::endl;
1003#if DISABLE_3DS_ANIMATION
1004    osg::Matrix m( osg::computeLocalToWorld(getNodePath()) );
1005#endif
1006    for ( unsigned int i = 0; i < count; i++ )
1007    {
1008        osg::Geometry *g = node.getDrawable( i )->asGeometry();
1009        if ( g != NULL )
1010        {
1011            listTriangles.clear();
1012            _cur3dsNode = parent;
1013
1014            pushStateSet(g->getStateSet());
1015            createListTriangle(g, listTriangles, texcoords, i);
1016            popStateSet(g->getStateSet());        // May set _succeded to false
1017            if (!succeeded()) break;
1018
1019            osg::Matrix pointLocalMat(osg::Matrix::translate(node.getPosition(i)));        // TODO handle rotation
1020#if DISABLE_3DS_ANIMATION
1021            osg::Matrix currentBillboardWorldMat(pointLocalMat * m);
1022            apply3DSMatrixNode(node, &pointLocalMat, "bil");                            // Add a 3DS matrix node (with local matrix)
1023            buildFaces(node, currentBillboardWorldMat, listTriangles, texcoords);        // May set _succeded to false
1024#else
1025            apply3DSMatrixNode(node, &pointLocalMat, "bil");                            // Add a 3DS matrix node (with local matrix)
1026            buildFaces(node, osg::Matrix(), listTriangles, texcoords);                    // May set _succeded to false
1027#endif
1028            if (!succeeded()) break;
1029        }
1030    }
1031
1032    if (succeeded())
1033        traverse(node);
1034    _cur3dsNode = parent;
1035    popStateSet(node.getStateSet());
1036}
1037
1038
1039
1040void WriterNodeVisitor::apply(osg::Group &node)
1041{
1042    pushStateSet(node.getStateSet());
1043    Lib3dsMeshInstanceNode * parent = _cur3dsNode;
1044#if DISABLE_3DS_ANIMATION
1045    osg::Matrix mat( osg::computeLocalToWorld(getNodePath()) );
1046    apply3DSMatrixNode(node, &mat, "grp");
1047#else
1048    apply3DSMatrixNode(node, NULL, "grp");
1049#endif
1050    if (succeeded())
1051        traverse(node);
1052    _cur3dsNode = parent;
1053    popStateSet(node.getStateSet());
1054}
1055
1056void WriterNodeVisitor::apply(osg::MatrixTransform &node)
1057{
1058    pushStateSet(node.getStateSet());
1059    Lib3dsMeshInstanceNode * parent = _cur3dsNode;
1060#if DISABLE_3DS_ANIMATION
1061    osg::Matrix mat( osg::computeLocalToWorld(getNodePath()) );
1062#else
1063    osg::Matrix mat( node.getMatrix() );
1064#endif
1065    apply3DSMatrixNode(node, &mat, "mtx");
1066    if (succeeded())
1067        traverse(node);
1068    _cur3dsNode = parent;
1069    popStateSet(node.getStateSet());
1070}
1071
1072void WriterNodeVisitor::apply3DSMatrixNode(osg::Node &node, const osg::Matrix * m, const char * const prefix)
1073{
1074    // Note: Creating a mesh instance with no transform and then copying the matrix doesn't work (matrix seems to be a temporary/computed value)
1075    Lib3dsMeshInstanceNode * parent = _cur3dsNode;
1076    Lib3dsMeshInstanceNode * node3ds = NULL;
1077    if (m)
1078    {
1079        osg::Vec3 osgScl, osgPos;
1080        osg::Quat osgRot, osgSo;
1081        m->decompose(osgPos, osgRot, osgScl, osgSo);
1082
1083        float pos[3];
1084        float scl[3];
1085        float rot[4];
1086        copyOsgVectorToLib3dsVector(pos, osgPos);
1087        copyOsgVectorToLib3dsVector(scl, osgScl);
1088        copyOsgQuatToLib3dsQuat(rot, osgRot);
1089        node3ds = lib3ds_node_new_mesh_instance(NULL, getUniqueName(node.getName().empty() ? node.className() : node.getName(), true, prefix).c_str(), pos, scl, rot);
1090    }
1091    else
1092    {
1093        node3ds = lib3ds_node_new_mesh_instance(NULL, getUniqueName(node.getName().empty() ? node.className() : node.getName(), true, prefix).c_str(), NULL, NULL, NULL);
1094    }
1095
1096    lib3ds_file_append_node(_file3ds, reinterpret_cast<Lib3dsNode*>(node3ds), reinterpret_cast<Lib3dsNode*>(parent));
1097    _cur3dsNode = node3ds;
1098}
1099
1100// end namespace plugin3ds
1101}
Note: See TracBrowser for help on using the browser.