1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2016, assimp team
7 
8 All rights reserved.
9 
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the following
12 conditions are met:
13 
14 * Redistributions of source code must retain the above
15   copyright notice, this list of conditions and the
16   following disclaimer.
17 
18 * Redistributions in binary form must reproduce the above
19   copyright notice, this list of conditions and the
20   following disclaimer in the documentation and/or other
21   materials provided with the distribution.
22 
23 * Neither the name of the assimp team, nor the names of its
24   contributors may be used to endorse or promote products
25   derived from this software without specific prior
26   written permission of the assimp team.
27 
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ---------------------------------------------------------------------------
40 */
41 
42 /** @file  3DSLoader.cpp
43  *  @brief Implementation of the 3ds importer class
44  *
45  *  http://www.the-labs.com/Blender/3DS-details.html
46  */
47 
48 
49 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
50 
51 // internal headers
52 #include "3DSLoader.h"
53 #include "Macros.h"
54 #include <assimp/IOSystem.hpp>
55 #include <assimp/scene.h>
56 #include <assimp/DefaultLogger.hpp>
57 #include "StringComparison.h"
58 
59 using namespace Assimp;
60 
61 static const aiImporterDesc desc = {
62     "Discreet 3DS Importer",
63     "",
64     "",
65     "Limited animation support",
66     aiImporterFlags_SupportBinaryFlavour,
67     0,
68     0,
69     0,
70     0,
71     "3ds prj"
72 };
73 
74 
75 // ------------------------------------------------------------------------------------------------
76 // Begins a new parsing block
77 // - Reads the current chunk and validates it
78 // - computes its length
79 #define ASSIMP_3DS_BEGIN_CHUNK()                                         \
80     while (true) {                                                       \
81     if (stream->GetRemainingSizeToLimit() < sizeof(Discreet3DS::Chunk)){ \
82         return;                                                          \
83     }                                                                    \
84     Discreet3DS::Chunk chunk;                                            \
85     ReadChunk(&chunk);                                                   \
86     int chunkSize = chunk.Size-sizeof(Discreet3DS::Chunk);               \
87     if(chunkSize <= 0)                                                   \
88         continue;                                                        \
89     const unsigned int oldReadLimit = stream->SetReadLimit(              \
90         stream->GetCurrentPos() + chunkSize);                            \
91 
92 
93 // ------------------------------------------------------------------------------------------------
94 // End a parsing block
95 // Must follow at the end of each parsing block, reset chunk end marker to previous value
96 #define ASSIMP_3DS_END_CHUNK()                  \
97     stream->SkipToReadLimit();                  \
98     stream->SetReadLimit(oldReadLimit);         \
99     if (stream->GetRemainingSizeToLimit() == 0) \
100         return;                                 \
101     }
102 
103 // ------------------------------------------------------------------------------------------------
104 // Constructor to be privately used by Importer
Discreet3DSImporter()105 Discreet3DSImporter::Discreet3DSImporter()
106     : stream(),
107     mLastNodeIndex(),
108     mCurrentNode(),
109     mRootNode(),
110     mScene(),
111     mMasterScale(),
112     bHasBG(),
113     bIsPrj()
114 {}
115 
116 // ------------------------------------------------------------------------------------------------
117 // Destructor, private as well
~Discreet3DSImporter()118 Discreet3DSImporter::~Discreet3DSImporter()
119 {}
120 
121 // ------------------------------------------------------------------------------------------------
122 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const123 bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
124 {
125     std::string extension = GetExtension(pFile);
126     if(extension == "3ds" || extension == "prj" ) {
127         return true;
128     }
129     if (!extension.length() || checkSig) {
130         uint16_t token[3];
131         token[0] = 0x4d4d;
132         token[1] = 0x3dc2;
133         //token[2] = 0x3daa;
134         return CheckMagicToken(pIOHandler,pFile,token,2,0,2);
135     }
136     return false;
137 }
138 
139 // ------------------------------------------------------------------------------------------------
140 // Loader registry entry
GetInfo() const141 const aiImporterDesc* Discreet3DSImporter::GetInfo () const
142 {
143     return &desc;
144 }
145 
146 // ------------------------------------------------------------------------------------------------
147 // Setup configuration properties
SetupProperties(const Importer *)148 void Discreet3DSImporter::SetupProperties(const Importer* /*pImp*/)
149 {
150     // nothing to be done for the moment
151 }
152 
153 // ------------------------------------------------------------------------------------------------
154 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)155 void Discreet3DSImporter::InternReadFile( const std::string& pFile,
156     aiScene* pScene, IOSystem* pIOHandler)
157 {
158     StreamReaderLE stream(pIOHandler->Open(pFile,"rb"));
159     this->stream = &stream;
160 
161     // We should have at least one chunk
162     if (stream.GetRemainingSize() < 16) {
163         throw DeadlyImportError("3DS file is either empty or corrupt: " + pFile);
164     }
165 
166     // Allocate our temporary 3DS representation
167     mScene = new D3DS::Scene();
168 
169     // Initialize members
170     mLastNodeIndex             = -1;
171     mCurrentNode               = new D3DS::Node();
172     mRootNode                  = mCurrentNode;
173     mRootNode->mHierarchyPos   = -1;
174     mRootNode->mHierarchyIndex = -1;
175     mRootNode->mParent         = NULL;
176     mMasterScale               = 1.0f;
177     mBackgroundImage           = "";
178     bHasBG                     = false;
179     bIsPrj                     = false;
180 
181     // Parse the file
182     ParseMainChunk();
183 
184     // Process all meshes in the file. First check whether all
185     // face indices haev valid values. The generate our
186     // internal verbose representation. Finally compute normal
187     // vectors from the smoothing groups we read from the
188     // file.
189     for (auto &mesh : mScene->mMeshes) {
190         if (mesh.mFaces.size() > 0 && mesh.mPositions.size() == 0)  {
191             delete mScene;
192             throw DeadlyImportError("3DS file contains faces but no vertices: " + pFile);
193         }
194         CheckIndices(mesh);
195         MakeUnique  (mesh);
196         ComputeNormalsWithSmoothingsGroups<D3DS::Face>(mesh);
197     }
198 
199     // Replace all occurrences of the default material with a
200     // valid material. Generate it if no material containing
201     // DEFAULT in its name has been found in the file
202     ReplaceDefaultMaterial();
203 
204     // Convert the scene from our internal representation to an
205     // aiScene object. This involves copying all meshes, lights
206     // and cameras to the scene
207     ConvertScene(pScene);
208 
209     // Generate the node graph for the scene. This is a little bit
210     // tricky since we'll need to split some meshes into submeshes
211     GenerateNodeGraph(pScene);
212 
213     // Now apply the master scaling factor to the scene
214     ApplyMasterScale(pScene);
215 
216     // Delete our internal scene representation and the root
217     // node, so the whole hierarchy will follow
218     delete mRootNode;
219     delete mScene;
220 
221     AI_DEBUG_INVALIDATE_PTR(mRootNode);
222     AI_DEBUG_INVALIDATE_PTR(mScene);
223     AI_DEBUG_INVALIDATE_PTR(this->stream);
224 }
225 
226 // ------------------------------------------------------------------------------------------------
227 // Applies a master-scaling factor to the imported scene
ApplyMasterScale(aiScene * pScene)228 void Discreet3DSImporter::ApplyMasterScale(aiScene* pScene)
229 {
230     // There are some 3DS files with a zero scaling factor
231     if (!mMasterScale)mMasterScale = 1.0f;
232     else mMasterScale = 1.0f / mMasterScale;
233 
234     // Construct an uniform scaling matrix and multiply with it
235     pScene->mRootNode->mTransformation *= aiMatrix4x4(
236         mMasterScale,0.0f, 0.0f, 0.0f,
237         0.0f, mMasterScale,0.0f, 0.0f,
238         0.0f, 0.0f, mMasterScale,0.0f,
239         0.0f, 0.0f, 0.0f, 1.0f);
240 
241     // Check whether a scaling track is assigned to the root node.
242 }
243 
244 // ------------------------------------------------------------------------------------------------
245 // Reads a new chunk from the file
ReadChunk(Discreet3DS::Chunk * pcOut)246 void Discreet3DSImporter::ReadChunk(Discreet3DS::Chunk* pcOut)
247 {
248     ai_assert(pcOut != NULL);
249 
250     pcOut->Flag = stream->GetI2();
251     pcOut->Size = stream->GetI4();
252 
253     if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSize())
254         throw DeadlyImportError("Chunk is too large");
255 
256     if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSizeToLimit())
257         DefaultLogger::get()->error("3DS: Chunk overflow");
258 }
259 
260 // ------------------------------------------------------------------------------------------------
261 // Skip a chunk
SkipChunk()262 void Discreet3DSImporter::SkipChunk()
263 {
264     Discreet3DS::Chunk psChunk;
265     ReadChunk(&psChunk);
266 
267     stream->IncPtr(psChunk.Size-sizeof(Discreet3DS::Chunk));
268     return;
269 }
270 
271 // ------------------------------------------------------------------------------------------------
272 // Process the primary chunk of the file
ParseMainChunk()273 void Discreet3DSImporter::ParseMainChunk()
274 {
275     ASSIMP_3DS_BEGIN_CHUNK();
276 
277     // get chunk type
278     switch (chunk.Flag)
279     {
280 
281     case Discreet3DS::CHUNK_PRJ:
282         bIsPrj = true;
283     case Discreet3DS::CHUNK_MAIN:
284         ParseEditorChunk();
285         break;
286     };
287 
288     ASSIMP_3DS_END_CHUNK();
289     // recursively continue processing this hierarchy level
290     return ParseMainChunk();
291 }
292 
293 // ------------------------------------------------------------------------------------------------
ParseEditorChunk()294 void Discreet3DSImporter::ParseEditorChunk()
295 {
296     ASSIMP_3DS_BEGIN_CHUNK();
297 
298     // get chunk type
299     switch (chunk.Flag)
300     {
301     case Discreet3DS::CHUNK_OBJMESH:
302 
303         ParseObjectChunk();
304         break;
305 
306     // NOTE: In several documentations in the internet this
307     // chunk appears at different locations
308     case Discreet3DS::CHUNK_KEYFRAMER:
309 
310         ParseKeyframeChunk();
311         break;
312 
313     case Discreet3DS::CHUNK_VERSION:
314         {
315         // print the version number
316         char buff[10];
317         ASSIMP_itoa10(buff,stream->GetI2());
318         DefaultLogger::get()->info(std::string("3DS file format version: ") + buff);
319         }
320         break;
321     };
322     ASSIMP_3DS_END_CHUNK();
323 }
324 
325 // ------------------------------------------------------------------------------------------------
ParseObjectChunk()326 void Discreet3DSImporter::ParseObjectChunk()
327 {
328     ASSIMP_3DS_BEGIN_CHUNK();
329 
330     // get chunk type
331     switch (chunk.Flag)
332     {
333     case Discreet3DS::CHUNK_OBJBLOCK:
334         {
335         unsigned int cnt = 0;
336         const char* sz = (const char*)stream->GetPtr();
337 
338         // Get the name of the geometry object
339         while (stream->GetI1())++cnt;
340         ParseChunk(sz,cnt);
341         }
342         break;
343 
344     case Discreet3DS::CHUNK_MAT_MATERIAL:
345 
346         // Add a new material to the list
347         mScene->mMaterials.push_back(D3DS::Material());
348         ParseMaterialChunk();
349         break;
350 
351     case Discreet3DS::CHUNK_AMBCOLOR:
352 
353         // This is the ambient base color of the scene.
354         // We add it to the ambient color of all materials
355         ParseColorChunk(&mClrAmbient,true);
356         if (is_qnan(mClrAmbient.r))
357         {
358             // We failed to read the ambient base color.
359             DefaultLogger::get()->error("3DS: Failed to read ambient base color");
360             mClrAmbient.r = mClrAmbient.g = mClrAmbient.b = 0.0f;
361         }
362         break;
363 
364     case Discreet3DS::CHUNK_BIT_MAP:
365         {
366         // Specifies the background image. The string should already be
367         // properly 0 terminated but we need to be sure
368         unsigned int cnt = 0;
369         const char* sz = (const char*)stream->GetPtr();
370         while (stream->GetI1())++cnt;
371         mBackgroundImage = std::string(sz,cnt);
372         }
373         break;
374 
375     case Discreet3DS::CHUNK_BIT_MAP_EXISTS:
376         bHasBG = true;
377         break;
378 
379     case Discreet3DS::CHUNK_MASTER_SCALE:
380         // Scene master scaling factor
381         mMasterScale = stream->GetF4();
382         break;
383     };
384     ASSIMP_3DS_END_CHUNK();
385 }
386 
387 // ------------------------------------------------------------------------------------------------
ParseChunk(const char * name,unsigned int num)388 void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
389 {
390     ASSIMP_3DS_BEGIN_CHUNK();
391 
392     // IMPLEMENTATION NOTE;
393     // Cameras or lights define their transformation in their parent node and in the
394     // corresponding light or camera chunks. However, we read and process the latter
395     // to to be able to return valid cameras/lights even if no scenegraph is given.
396 
397     // get chunk type
398     switch (chunk.Flag)
399     {
400     case Discreet3DS::CHUNK_TRIMESH:
401         {
402         // this starts a new triangle mesh
403         mScene->mMeshes.push_back(D3DS::Mesh());
404         D3DS::Mesh& m = mScene->mMeshes.back();
405 
406         // Setup the name of the mesh
407         m.mName = std::string(name, num);
408 
409         // Read mesh chunks
410         ParseMeshChunk();
411         }
412         break;
413 
414     case Discreet3DS::CHUNK_LIGHT:
415         {
416         // This starts a new light
417         aiLight* light = new aiLight();
418         mScene->mLights.push_back(light);
419 
420         light->mName.Set(std::string(name, num));
421 
422         // First read the position of the light
423         light->mPosition.x = stream->GetF4();
424         light->mPosition.y = stream->GetF4();
425         light->mPosition.z = stream->GetF4();
426 
427         light->mColorDiffuse = aiColor3D(1.f,1.f,1.f);
428 
429         // Now check for further subchunks
430         if (!bIsPrj) /* fixme */
431             ParseLightChunk();
432 
433         // The specular light color is identical the the diffuse light color. The ambient light color
434         // is equal to the ambient base color of the whole scene.
435         light->mColorSpecular = light->mColorDiffuse;
436         light->mColorAmbient  = mClrAmbient;
437 
438         if (light->mType == aiLightSource_UNDEFINED)
439         {
440             // It must be a point light
441             light->mType = aiLightSource_POINT;
442         }}
443         break;
444 
445     case Discreet3DS::CHUNK_CAMERA:
446         {
447         // This starts a new camera
448         aiCamera* camera = new aiCamera();
449         mScene->mCameras.push_back(camera);
450         camera->mName.Set(std::string(name, num));
451 
452         // First read the position of the camera
453         camera->mPosition.x = stream->GetF4();
454         camera->mPosition.y = stream->GetF4();
455         camera->mPosition.z = stream->GetF4();
456 
457         // Then the camera target
458         camera->mLookAt.x = stream->GetF4() - camera->mPosition.x;
459         camera->mLookAt.y = stream->GetF4() - camera->mPosition.y;
460         camera->mLookAt.z = stream->GetF4() - camera->mPosition.z;
461         float len = camera->mLookAt.Length();
462         if (len < 1e-5f) {
463 
464             // There are some files with lookat == position. Don't know why or whether it's ok or not.
465             DefaultLogger::get()->error("3DS: Unable to read proper camera look-at vector");
466             camera->mLookAt = aiVector3D(0.f,1.f,0.f);
467 
468         }
469         else camera->mLookAt /= len;
470 
471         // And finally - the camera rotation angle, in counter clockwise direction
472         const float angle =  AI_DEG_TO_RAD( stream->GetF4() );
473         aiQuaternion quat(camera->mLookAt,angle);
474         camera->mUp = quat.GetMatrix() * aiVector3D(0.f,1.f,0.f);
475 
476         // Read the lense angle
477         camera->mHorizontalFOV = AI_DEG_TO_RAD ( stream->GetF4() );
478         if (camera->mHorizontalFOV < 0.001f)  {
479             camera->mHorizontalFOV = AI_DEG_TO_RAD(45.f);
480         }
481 
482         // Now check for further subchunks
483         if (!bIsPrj) /* fixme */ {
484             ParseCameraChunk();
485         }}
486         break;
487     };
488     ASSIMP_3DS_END_CHUNK();
489 }
490 
491 // ------------------------------------------------------------------------------------------------
ParseLightChunk()492 void Discreet3DSImporter::ParseLightChunk()
493 {
494     ASSIMP_3DS_BEGIN_CHUNK();
495     aiLight* light = mScene->mLights.back();
496 
497     // get chunk type
498     switch (chunk.Flag)
499     {
500     case Discreet3DS::CHUNK_DL_SPOTLIGHT:
501         // Now we can be sure that the light is a spot light
502         light->mType = aiLightSource_SPOT;
503 
504         // We wouldn't need to normalize here, but we do it
505         light->mDirection.x = stream->GetF4() - light->mPosition.x;
506         light->mDirection.y = stream->GetF4() - light->mPosition.y;
507         light->mDirection.z = stream->GetF4() - light->mPosition.z;
508         light->mDirection.Normalize();
509 
510         // Now the hotspot and falloff angles - in degrees
511         light->mAngleInnerCone = AI_DEG_TO_RAD( stream->GetF4() );
512 
513         // FIX: the falloff angle is just an offset
514         light->mAngleOuterCone = light->mAngleInnerCone+AI_DEG_TO_RAD( stream->GetF4() );
515         break;
516 
517         // intensity multiplier
518     case Discreet3DS::CHUNK_DL_MULTIPLIER:
519         light->mColorDiffuse = light->mColorDiffuse * stream->GetF4();
520         break;
521 
522         // light color
523     case Discreet3DS::CHUNK_RGBF:
524     case Discreet3DS::CHUNK_LINRGBF:
525         light->mColorDiffuse.r *= stream->GetF4();
526         light->mColorDiffuse.g *= stream->GetF4();
527         light->mColorDiffuse.b *= stream->GetF4();
528         break;
529 
530         // light attenuation
531     case Discreet3DS::CHUNK_DL_ATTENUATE:
532         light->mAttenuationLinear = stream->GetF4();
533         break;
534     };
535 
536     ASSIMP_3DS_END_CHUNK();
537 }
538 
539 // ------------------------------------------------------------------------------------------------
ParseCameraChunk()540 void Discreet3DSImporter::ParseCameraChunk()
541 {
542     ASSIMP_3DS_BEGIN_CHUNK();
543     aiCamera* camera = mScene->mCameras.back();
544 
545     // get chunk type
546     switch (chunk.Flag)
547     {
548         // near and far clip plane
549     case Discreet3DS::CHUNK_CAM_RANGES:
550         camera->mClipPlaneNear = stream->GetF4();
551         camera->mClipPlaneFar  = stream->GetF4();
552         break;
553     }
554 
555     ASSIMP_3DS_END_CHUNK();
556 }
557 
558 // ------------------------------------------------------------------------------------------------
ParseKeyframeChunk()559 void Discreet3DSImporter::ParseKeyframeChunk()
560 {
561     ASSIMP_3DS_BEGIN_CHUNK();
562 
563     // get chunk type
564     switch (chunk.Flag)
565     {
566     case Discreet3DS::CHUNK_TRACKCAMTGT:
567     case Discreet3DS::CHUNK_TRACKSPOTL:
568     case Discreet3DS::CHUNK_TRACKCAMERA:
569     case Discreet3DS::CHUNK_TRACKINFO:
570     case Discreet3DS::CHUNK_TRACKLIGHT:
571     case Discreet3DS::CHUNK_TRACKLIGTGT:
572 
573         // this starts a new mesh hierarchy chunk
574         ParseHierarchyChunk(chunk.Flag);
575         break;
576     };
577 
578     ASSIMP_3DS_END_CHUNK();
579 }
580 
581 // ------------------------------------------------------------------------------------------------
582 // Little helper function for ParseHierarchyChunk
InverseNodeSearch(D3DS::Node * pcNode,D3DS::Node * pcCurrent)583 void Discreet3DSImporter::InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent)
584 {
585     if (!pcCurrent) {
586         mRootNode->push_back(pcNode);
587         return;
588     }
589 
590     if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos)  {
591         if(pcCurrent->mParent) {
592             pcCurrent->mParent->push_back(pcNode);
593         }
594         else pcCurrent->push_back(pcNode);
595         return;
596     }
597     return InverseNodeSearch(pcNode,pcCurrent->mParent);
598 }
599 
600 // ------------------------------------------------------------------------------------------------
601 // Find a node with a specific name in the import hierarchy
FindNode(D3DS::Node * root,const std::string & name)602 D3DS::Node* FindNode(D3DS::Node* root, const std::string& name)
603 {
604     if (root->mName == name)
605         return root;
606     for (std::vector<D3DS::Node*>::iterator it = root->mChildren.begin();it != root->mChildren.end(); ++it) {
607         D3DS::Node* nd;
608         if (( nd = FindNode(*it,name)))
609             return nd;
610     }
611     return NULL;
612 }
613 
614 // ------------------------------------------------------------------------------------------------
615 // Binary predicate for std::unique()
616 template <class T>
KeyUniqueCompare(const T & first,const T & second)617 bool KeyUniqueCompare(const T& first, const T& second)
618 {
619     return first.mTime == second.mTime;
620 }
621 
622 // ------------------------------------------------------------------------------------------------
623 // Skip some additional import data.
SkipTCBInfo()624 void Discreet3DSImporter::SkipTCBInfo()
625 {
626     unsigned int flags = stream->GetI2();
627 
628     if (!flags) {
629         // Currently we can't do anything with these values. They occur
630         // quite rare, so it wouldn't be worth the effort implementing
631         // them. 3DS ist not really suitable for complex animations,
632         // so full support is not required.
633         DefaultLogger::get()->warn("3DS: Skipping TCB animation info");
634     }
635 
636     if (flags & Discreet3DS::KEY_USE_TENS) {
637         stream->IncPtr(4);
638     }
639     if (flags & Discreet3DS::KEY_USE_BIAS) {
640         stream->IncPtr(4);
641     }
642     if (flags & Discreet3DS::KEY_USE_CONT) {
643         stream->IncPtr(4);
644     }
645     if (flags & Discreet3DS::KEY_USE_EASE_FROM) {
646         stream->IncPtr(4);
647     }
648     if (flags & Discreet3DS::KEY_USE_EASE_TO) {
649         stream->IncPtr(4);
650     }
651 }
652 
653 // ------------------------------------------------------------------------------------------------
654 // Read hierarchy and keyframe info
ParseHierarchyChunk(uint16_t parent)655 void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
656 {
657     ASSIMP_3DS_BEGIN_CHUNK();
658 
659     // get chunk type
660     switch (chunk.Flag)
661     {
662     case Discreet3DS::CHUNK_TRACKOBJNAME:
663 
664         // This is the name of the object to which the track applies. The chunk also
665         // defines the position of this object in the hierarchy.
666         {
667 
668         // First of all: get the name of the object
669         unsigned int cnt = 0;
670         const char* sz = (const char*)stream->GetPtr();
671 
672         while (stream->GetI1())++cnt;
673         std::string name = std::string(sz,cnt);
674 
675         // Now find out whether we have this node already (target animation channels
676         // are stored with a separate object ID)
677         D3DS::Node* pcNode = FindNode(mRootNode,name);
678         int instanceNumber = 1;
679 
680         if ( pcNode)
681         {
682             // if the source is not a CHUNK_TRACKINFO block it wont be an object instance
683             if (parent != Discreet3DS::CHUNK_TRACKINFO)
684             {
685                 mCurrentNode = pcNode;
686                 break;
687             }
688             pcNode->mInstanceCount++;
689             instanceNumber = pcNode->mInstanceCount;
690         }
691         pcNode = new D3DS::Node();
692         pcNode->mName = name;
693         pcNode->mInstanceNumber = instanceNumber;
694 
695         // There are two unknown values which we can safely ignore
696         stream->IncPtr(4);
697 
698         // Now read the hierarchy position of the object
699         uint16_t hierarchy = stream->GetI2() + 1;
700         pcNode->mHierarchyPos   = hierarchy;
701         pcNode->mHierarchyIndex = mLastNodeIndex;
702 
703         // And find a proper position in the graph for it
704         if (mCurrentNode && mCurrentNode->mHierarchyPos == hierarchy)   {
705 
706             // add to the parent of the last touched node
707             mCurrentNode->mParent->push_back(pcNode);
708             mLastNodeIndex++;
709         }
710         else if(hierarchy >= mLastNodeIndex)    {
711 
712             // place it at the current position in the hierarchy
713             mCurrentNode->push_back(pcNode);
714             mLastNodeIndex = hierarchy;
715         }
716         else    {
717             // need to go back to the specified position in the hierarchy.
718             InverseNodeSearch(pcNode,mCurrentNode);
719             mLastNodeIndex++;
720         }
721         // Make this node the current node
722         mCurrentNode = pcNode;
723         }
724         break;
725 
726     case Discreet3DS::CHUNK_TRACKDUMMYOBJNAME:
727 
728         // This is the "real" name of a $$$DUMMY object
729         {
730             const char* sz = (const char*) stream->GetPtr();
731             while (stream->GetI1());
732 
733             // If object name is DUMMY, take this one instead
734             if (mCurrentNode->mName == "$$$DUMMY")  {
735                 //DefaultLogger::get()->warn("3DS: Skipping dummy object name for non-dummy object");
736                 mCurrentNode->mName = std::string(sz);
737                 break;
738             }
739         }
740         break;
741 
742     case Discreet3DS::CHUNK_TRACKPIVOT:
743 
744         if ( Discreet3DS::CHUNK_TRACKINFO != parent)
745         {
746             DefaultLogger::get()->warn("3DS: Skipping pivot subchunk for non usual object");
747             break;
748         }
749 
750         // Pivot = origin of rotation and scaling
751         mCurrentNode->vPivot.x = stream->GetF4();
752         mCurrentNode->vPivot.y = stream->GetF4();
753         mCurrentNode->vPivot.z = stream->GetF4();
754         break;
755 
756 
757         // ////////////////////////////////////////////////////////////////////
758         // POSITION KEYFRAME
759     case Discreet3DS::CHUNK_TRACKPOS:
760         {
761         stream->IncPtr(10);
762         const unsigned int numFrames = stream->GetI4();
763         bool sortKeys = false;
764 
765         // This could also be meant as the target position for
766         // (targeted) lights and cameras
767         std::vector<aiVectorKey>* l;
768         if ( Discreet3DS::CHUNK_TRACKCAMTGT == parent || Discreet3DS::CHUNK_TRACKLIGTGT == parent)  {
769             l = & mCurrentNode->aTargetPositionKeys;
770         }
771         else l = & mCurrentNode->aPositionKeys;
772 
773         l->reserve(numFrames);
774         for (unsigned int i = 0; i < numFrames;++i) {
775             const unsigned int fidx = stream->GetI4();
776 
777             // Setup a new position key
778             aiVectorKey v;
779             v.mTime = (double)fidx;
780 
781             SkipTCBInfo();
782             v.mValue.x = stream->GetF4();
783             v.mValue.y = stream->GetF4();
784             v.mValue.z = stream->GetF4();
785 
786             // check whether we'll need to sort the keys
787             if (!l->empty() && v.mTime <= l->back().mTime)
788                 sortKeys = true;
789 
790             // Add the new keyframe to the list
791             l->push_back(v);
792         }
793 
794         // Sort all keys with ascending time values and remove duplicates?
795         if (sortKeys)   {
796             std::stable_sort(l->begin(),l->end());
797             l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiVectorKey>), l->end() );
798         }}
799 
800         break;
801 
802         // ////////////////////////////////////////////////////////////////////
803         // CAMERA ROLL KEYFRAME
804     case Discreet3DS::CHUNK_TRACKROLL:
805         {
806         // roll keys are accepted for cameras only
807         if (parent != Discreet3DS::CHUNK_TRACKCAMERA)   {
808             DefaultLogger::get()->warn("3DS: Ignoring roll track for non-camera object");
809             break;
810         }
811         bool sortKeys = false;
812         std::vector<aiFloatKey>* l = &mCurrentNode->aCameraRollKeys;
813 
814         stream->IncPtr(10);
815         const unsigned int numFrames = stream->GetI4();
816         l->reserve(numFrames);
817         for (unsigned int i = 0; i < numFrames;++i) {
818             const unsigned int fidx = stream->GetI4();
819 
820             // Setup a new position key
821             aiFloatKey v;
822             v.mTime = (double)fidx;
823 
824             // This is just a single float
825             SkipTCBInfo();
826             v.mValue = stream->GetF4();
827 
828             // Check whether we'll need to sort the keys
829             if (!l->empty() && v.mTime <= l->back().mTime)
830                 sortKeys = true;
831 
832             // Add the new keyframe to the list
833             l->push_back(v);
834         }
835 
836         // Sort all keys with ascending time values and remove duplicates?
837         if (sortKeys)   {
838             std::stable_sort(l->begin(),l->end());
839             l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiFloatKey>), l->end() );
840         }}
841         break;
842 
843 
844         // ////////////////////////////////////////////////////////////////////
845         // CAMERA FOV KEYFRAME
846     case Discreet3DS::CHUNK_TRACKFOV:
847         {
848             DefaultLogger::get()->error("3DS: Skipping FOV animation track. "
849                 "This is not supported");
850         }
851         break;
852 
853 
854         // ////////////////////////////////////////////////////////////////////
855         // ROTATION KEYFRAME
856     case Discreet3DS::CHUNK_TRACKROTATE:
857         {
858         stream->IncPtr(10);
859         const unsigned int numFrames = stream->GetI4();
860 
861         bool sortKeys = false;
862         std::vector<aiQuatKey>* l = &mCurrentNode->aRotationKeys;
863         l->reserve(numFrames);
864 
865         for (unsigned int i = 0; i < numFrames;++i) {
866             const unsigned int fidx = stream->GetI4();
867             SkipTCBInfo();
868 
869             aiQuatKey v;
870             v.mTime = (double)fidx;
871 
872             // The rotation keyframe is given as an axis-angle pair
873             const float rad = stream->GetF4();
874             aiVector3D axis;
875             axis.x = stream->GetF4();
876             axis.y = stream->GetF4();
877             axis.z = stream->GetF4();
878 
879             if (!axis.x && !axis.y && !axis.z)
880                 axis.y = 1.f;
881 
882             // Construct a rotation quaternion from the axis-angle pair
883             v.mValue = aiQuaternion(axis,rad);
884 
885             // Check whether we'll need to sort the keys
886             if (!l->empty() && v.mTime <= l->back().mTime)
887                 sortKeys = true;
888 
889             // add the new keyframe to the list
890             l->push_back(v);
891         }
892         // Sort all keys with ascending time values and remove duplicates?
893         if (sortKeys)   {
894             std::stable_sort(l->begin(),l->end());
895             l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiQuatKey>), l->end() );
896         }}
897         break;
898 
899         // ////////////////////////////////////////////////////////////////////
900         // SCALING KEYFRAME
901     case Discreet3DS::CHUNK_TRACKSCALE:
902         {
903         stream->IncPtr(10);
904         const unsigned int numFrames = stream->GetI2();
905         stream->IncPtr(2);
906 
907         bool sortKeys = false;
908         std::vector<aiVectorKey>* l = &mCurrentNode->aScalingKeys;
909         l->reserve(numFrames);
910 
911         for (unsigned int i = 0; i < numFrames;++i) {
912             const unsigned int fidx = stream->GetI4();
913             SkipTCBInfo();
914 
915             // Setup a new key
916             aiVectorKey v;
917             v.mTime = (double)fidx;
918 
919             // ... and read its value
920             v.mValue.x = stream->GetF4();
921             v.mValue.y = stream->GetF4();
922             v.mValue.z = stream->GetF4();
923 
924             // check whether we'll need to sort the keys
925             if (!l->empty() && v.mTime <= l->back().mTime)
926                 sortKeys = true;
927 
928             // Remove zero-scalings on singular axes - they've been reported to be there erroneously in some strange files
929             if (!v.mValue.x) v.mValue.x = 1.f;
930             if (!v.mValue.y) v.mValue.y = 1.f;
931             if (!v.mValue.z) v.mValue.z = 1.f;
932 
933             l->push_back(v);
934         }
935         // Sort all keys with ascending time values and remove duplicates?
936         if (sortKeys)   {
937             std::stable_sort(l->begin(),l->end());
938             l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare<aiVectorKey>), l->end() );
939         }}
940         break;
941     };
942 
943     ASSIMP_3DS_END_CHUNK();
944 }
945 
946 // ------------------------------------------------------------------------------------------------
947 // Read a face chunk - it contains smoothing groups and material assignments
ParseFaceChunk()948 void Discreet3DSImporter::ParseFaceChunk()
949 {
950     ASSIMP_3DS_BEGIN_CHUNK();
951 
952     // Get the mesh we're currently working on
953     D3DS::Mesh& mMesh = mScene->mMeshes.back();
954 
955     // Get chunk type
956     switch (chunk.Flag)
957     {
958     case Discreet3DS::CHUNK_SMOOLIST:
959         {
960         // This is the list of smoothing groups - a bitfield for every face.
961         // Up to 32 smoothing groups assigned to a single face.
962         unsigned int num = chunkSize/4, m = 0;
963         if (num > mMesh.mFaces.size())  {
964             throw DeadlyImportError("3DS: More smoothing groups than faces");
965         }
966         for (std::vector<D3DS::Face>::iterator i =  mMesh.mFaces.begin(); m != num;++i, ++m)    {
967             // nth bit is set for nth smoothing group
968             (*i).iSmoothGroup = stream->GetI4();
969         }}
970         break;
971 
972     case Discreet3DS::CHUNK_FACEMAT:
973         {
974         // at fist an asciiz with the material name
975         const char* sz = (const char*)stream->GetPtr();
976         while (stream->GetI1());
977 
978         // find the index of the material
979         unsigned int idx = 0xcdcdcdcd, cnt = 0;
980         for (std::vector<D3DS::Material>::const_iterator i =  mScene->mMaterials.begin();i != mScene->mMaterials.end();++i,++cnt)   {
981             // use case independent comparisons. hopefully it will work.
982             if ((*i).mName.length() && !ASSIMP_stricmp(sz, (*i).mName.c_str())) {
983                 idx = cnt;
984                 break;
985             }
986         }
987         if (0xcdcdcdcd == idx)  {
988             DefaultLogger::get()->error(std::string("3DS: Unknown material: ") + sz);
989         }
990 
991         // Now continue and read all material indices
992         cnt = (uint16_t)stream->GetI2();
993         for (unsigned int i = 0; i < cnt;++i)   {
994             unsigned int fidx = (uint16_t)stream->GetI2();
995 
996             // check range
997             if (fidx >= mMesh.mFaceMaterials.size())    {
998                 DefaultLogger::get()->error("3DS: Invalid face index in face material list");
999             }
1000             else mMesh.mFaceMaterials[fidx] = idx;
1001         }}
1002         break;
1003     };
1004     ASSIMP_3DS_END_CHUNK();
1005 }
1006 
1007 // ------------------------------------------------------------------------------------------------
1008 // Read a mesh chunk. Here's the actual mesh data
ParseMeshChunk()1009 void Discreet3DSImporter::ParseMeshChunk()
1010 {
1011     ASSIMP_3DS_BEGIN_CHUNK();
1012 
1013     // Get the mesh we're currently working on
1014     D3DS::Mesh& mMesh = mScene->mMeshes.back();
1015 
1016     // get chunk type
1017     switch (chunk.Flag)
1018     {
1019     case Discreet3DS::CHUNK_VERTLIST:
1020         {
1021         // This is the list of all vertices in the current mesh
1022         int num = (int)(uint16_t)stream->GetI2();
1023         mMesh.mPositions.reserve(num);
1024         while (num-- > 0)   {
1025             aiVector3D v;
1026             v.x = stream->GetF4();
1027             v.y = stream->GetF4();
1028             v.z = stream->GetF4();
1029             mMesh.mPositions.push_back(v);
1030         }}
1031         break;
1032     case Discreet3DS::CHUNK_TRMATRIX:
1033         {
1034         // This is the RLEATIVE transformation matrix of the current mesh. Vertices are
1035         // pretransformed by this matrix wonder.
1036         mMesh.mMat.a1 = stream->GetF4();
1037         mMesh.mMat.b1 = stream->GetF4();
1038         mMesh.mMat.c1 = stream->GetF4();
1039         mMesh.mMat.a2 = stream->GetF4();
1040         mMesh.mMat.b2 = stream->GetF4();
1041         mMesh.mMat.c2 = stream->GetF4();
1042         mMesh.mMat.a3 = stream->GetF4();
1043         mMesh.mMat.b3 = stream->GetF4();
1044         mMesh.mMat.c3 = stream->GetF4();
1045         mMesh.mMat.a4 = stream->GetF4();
1046         mMesh.mMat.b4 = stream->GetF4();
1047         mMesh.mMat.c4 = stream->GetF4();
1048         }
1049         break;
1050 
1051     case Discreet3DS::CHUNK_MAPLIST:
1052         {
1053         // This is the list of all UV coords in the current mesh
1054         int num = (int)(uint16_t)stream->GetI2();
1055         mMesh.mTexCoords.reserve(num);
1056         while (num-- > 0)   {
1057             aiVector3D v;
1058             v.x = stream->GetF4();
1059             v.y = stream->GetF4();
1060             mMesh.mTexCoords.push_back(v);
1061         }}
1062         break;
1063 
1064     case Discreet3DS::CHUNK_FACELIST:
1065         {
1066         // This is the list of all faces in the current mesh
1067         int num = (int)(uint16_t)stream->GetI2();
1068         mMesh.mFaces.reserve(num);
1069         while (num-- > 0)   {
1070             // 3DS faces are ALWAYS triangles
1071             mMesh.mFaces.push_back(D3DS::Face());
1072             D3DS::Face& sFace = mMesh.mFaces.back();
1073 
1074             sFace.mIndices[0] = (uint16_t)stream->GetI2();
1075             sFace.mIndices[1] = (uint16_t)stream->GetI2();
1076             sFace.mIndices[2] = (uint16_t)stream->GetI2();
1077 
1078             stream->IncPtr(2); // skip edge visibility flag
1079         }
1080 
1081         // Resize the material array (0xcdcdcdcd marks the default material; so if a face is
1082         // not referenced by a material, $$DEFAULT will be assigned to it)
1083         mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd);
1084 
1085         // Larger 3DS files could have multiple FACE chunks here
1086         chunkSize = stream->GetRemainingSizeToLimit();
1087         if ( chunkSize > (int) sizeof(Discreet3DS::Chunk ) )
1088             ParseFaceChunk();
1089         }
1090         break;
1091     };
1092     ASSIMP_3DS_END_CHUNK();
1093 }
1094 
1095 // ------------------------------------------------------------------------------------------------
1096 // Read a 3DS material chunk
ParseMaterialChunk()1097 void Discreet3DSImporter::ParseMaterialChunk()
1098 {
1099     ASSIMP_3DS_BEGIN_CHUNK();
1100     switch (chunk.Flag)
1101     {
1102     case Discreet3DS::CHUNK_MAT_MATNAME:
1103 
1104         {
1105         // The material name string is already zero-terminated, but we need to be sure ...
1106         const char* sz = (const char*)stream->GetPtr();
1107         unsigned int cnt = 0;
1108         while (stream->GetI1())
1109             ++cnt;
1110 
1111         if (!cnt)   {
1112             // This may not be, we use the default name instead
1113             DefaultLogger::get()->error("3DS: Empty material name");
1114         }
1115         else mScene->mMaterials.back().mName = std::string(sz,cnt);
1116         }
1117         break;
1118 
1119     case Discreet3DS::CHUNK_MAT_DIFFUSE:
1120         {
1121         // This is the diffuse material color
1122         aiColor3D* pc = &mScene->mMaterials.back().mDiffuse;
1123         ParseColorChunk(pc);
1124         if (is_qnan(pc->r)) {
1125             // color chunk is invalid. Simply ignore it
1126             DefaultLogger::get()->error("3DS: Unable to read DIFFUSE chunk");
1127             pc->r = pc->g = pc->b = 1.0f;
1128         }}
1129         break;
1130 
1131     case Discreet3DS::CHUNK_MAT_SPECULAR:
1132         {
1133         // This is the specular material color
1134         aiColor3D* pc = &mScene->mMaterials.back().mSpecular;
1135         ParseColorChunk(pc);
1136         if (is_qnan(pc->r)) {
1137             // color chunk is invalid. Simply ignore it
1138             DefaultLogger::get()->error("3DS: Unable to read SPECULAR chunk");
1139             pc->r = pc->g = pc->b = 1.0f;
1140         }}
1141         break;
1142 
1143     case Discreet3DS::CHUNK_MAT_AMBIENT:
1144         {
1145         // This is the ambient material color
1146         aiColor3D* pc = &mScene->mMaterials.back().mAmbient;
1147         ParseColorChunk(pc);
1148         if (is_qnan(pc->r)) {
1149             // color chunk is invalid. Simply ignore it
1150             DefaultLogger::get()->error("3DS: Unable to read AMBIENT chunk");
1151             pc->r = pc->g = pc->b = 0.0f;
1152         }}
1153         break;
1154 
1155     case Discreet3DS::CHUNK_MAT_SELF_ILLUM:
1156         {
1157         // This is the emissive material color
1158         aiColor3D* pc = &mScene->mMaterials.back().mEmissive;
1159         ParseColorChunk(pc);
1160         if (is_qnan(pc->r)) {
1161             // color chunk is invalid. Simply ignore it
1162             DefaultLogger::get()->error("3DS: Unable to read EMISSIVE chunk");
1163             pc->r = pc->g = pc->b = 0.0f;
1164         }}
1165         break;
1166 
1167     case Discreet3DS::CHUNK_MAT_TRANSPARENCY:
1168         {
1169         // This is the material's transparency
1170         float* pcf = &mScene->mMaterials.back().mTransparency;
1171         *pcf = ParsePercentageChunk();
1172 
1173         // NOTE: transparency, not opacity
1174         if (is_qnan(*pcf))
1175             *pcf = 1.0f;
1176         else *pcf = 1.0f - *pcf * (float)0xFFFF / 100.0f;
1177         }
1178         break;
1179 
1180     case Discreet3DS::CHUNK_MAT_SHADING:
1181         // This is the material shading mode
1182         mScene->mMaterials.back().mShading = (D3DS::Discreet3DS::shadetype3ds)stream->GetI2();
1183         break;
1184 
1185     case Discreet3DS::CHUNK_MAT_TWO_SIDE:
1186         // This is the two-sided flag
1187         mScene->mMaterials.back().mTwoSided = true;
1188         break;
1189 
1190     case Discreet3DS::CHUNK_MAT_SHININESS:
1191         { // This is the shininess of the material
1192         float* pcf = &mScene->mMaterials.back().mSpecularExponent;
1193         *pcf = ParsePercentageChunk();
1194         if (is_qnan(*pcf))
1195             *pcf = 0.0f;
1196         else *pcf *= (float)0xFFFF;
1197         }
1198         break;
1199 
1200     case Discreet3DS::CHUNK_MAT_SHININESS_PERCENT:
1201         { // This is the shininess strength of the material
1202         float* pcf = &mScene->mMaterials.back().mShininessStrength;
1203         *pcf = ParsePercentageChunk();
1204         if (is_qnan(*pcf))
1205             *pcf = 0.0f;
1206         else *pcf *= (float)0xffff / 100.0f;
1207         }
1208         break;
1209 
1210     case Discreet3DS::CHUNK_MAT_SELF_ILPCT:
1211         { // This is the self illumination strength of the material
1212         float f = ParsePercentageChunk();
1213         if (is_qnan(f))
1214             f = 0.0f;
1215         else f *= (float)0xFFFF / 100.0f;
1216         mScene->mMaterials.back().mEmissive = aiColor3D(f,f,f);
1217         }
1218         break;
1219 
1220     // Parse texture chunks
1221     case Discreet3DS::CHUNK_MAT_TEXTURE:
1222         // Diffuse texture
1223         ParseTextureChunk(&mScene->mMaterials.back().sTexDiffuse);
1224         break;
1225     case Discreet3DS::CHUNK_MAT_BUMPMAP:
1226         // Height map
1227         ParseTextureChunk(&mScene->mMaterials.back().sTexBump);
1228         break;
1229     case Discreet3DS::CHUNK_MAT_OPACMAP:
1230         // Opacity texture
1231         ParseTextureChunk(&mScene->mMaterials.back().sTexOpacity);
1232         break;
1233     case Discreet3DS::CHUNK_MAT_MAT_SHINMAP:
1234         // Shininess map
1235         ParseTextureChunk(&mScene->mMaterials.back().sTexShininess);
1236         break;
1237     case Discreet3DS::CHUNK_MAT_SPECMAP:
1238         // Specular map
1239         ParseTextureChunk(&mScene->mMaterials.back().sTexSpecular);
1240         break;
1241     case Discreet3DS::CHUNK_MAT_SELFIMAP:
1242         // Self-illumination (emissive) map
1243         ParseTextureChunk(&mScene->mMaterials.back().sTexEmissive);
1244         break;
1245     case Discreet3DS::CHUNK_MAT_REFLMAP:
1246         // Reflection map
1247         ParseTextureChunk(&mScene->mMaterials.back().sTexReflective);
1248         break;
1249     };
1250     ASSIMP_3DS_END_CHUNK();
1251 }
1252 
1253 // ------------------------------------------------------------------------------------------------
ParseTextureChunk(D3DS::Texture * pcOut)1254 void Discreet3DSImporter::ParseTextureChunk(D3DS::Texture* pcOut)
1255 {
1256     ASSIMP_3DS_BEGIN_CHUNK();
1257 
1258     // get chunk type
1259     switch (chunk.Flag)
1260     {
1261     case Discreet3DS::CHUNK_MAPFILE:
1262         {
1263         // The material name string is already zero-terminated, but we need to be sure ...
1264         const char* sz = (const char*)stream->GetPtr();
1265         unsigned int cnt = 0;
1266         while (stream->GetI1())
1267             ++cnt;
1268         pcOut->mMapName = std::string(sz,cnt);
1269         }
1270         break;
1271 
1272 
1273     case Discreet3DS::CHUNK_PERCENTF:
1274         // Manually parse the blend factor
1275         pcOut->mTextureBlend = stream->GetF4();
1276         break;
1277 
1278     case Discreet3DS::CHUNK_PERCENTW:
1279         // Manually parse the blend factor
1280         pcOut->mTextureBlend = (float)((uint16_t)stream->GetI2()) / 100.0f;
1281         break;
1282 
1283     case Discreet3DS::CHUNK_MAT_MAP_USCALE:
1284         // Texture coordinate scaling in the U direction
1285         pcOut->mScaleU = stream->GetF4();
1286         if (0.0f == pcOut->mScaleU)
1287         {
1288             DefaultLogger::get()->warn("Texture coordinate scaling in the x direction is zero. Assuming 1.");
1289             pcOut->mScaleU = 1.0f;
1290         }
1291         break;
1292     case Discreet3DS::CHUNK_MAT_MAP_VSCALE:
1293         // Texture coordinate scaling in the V direction
1294         pcOut->mScaleV = stream->GetF4();
1295         if (0.0f == pcOut->mScaleV)
1296         {
1297             DefaultLogger::get()->warn("Texture coordinate scaling in the y direction is zero. Assuming 1.");
1298             pcOut->mScaleV = 1.0f;
1299         }
1300         break;
1301 
1302     case Discreet3DS::CHUNK_MAT_MAP_UOFFSET:
1303         // Texture coordinate offset in the U direction
1304         pcOut->mOffsetU = -stream->GetF4();
1305         break;
1306 
1307     case Discreet3DS::CHUNK_MAT_MAP_VOFFSET:
1308         // Texture coordinate offset in the V direction
1309         pcOut->mOffsetV = stream->GetF4();
1310         break;
1311 
1312     case Discreet3DS::CHUNK_MAT_MAP_ANG:
1313         // Texture coordinate rotation, CCW in DEGREES
1314         pcOut->mRotation = -AI_DEG_TO_RAD( stream->GetF4() );
1315         break;
1316 
1317     case Discreet3DS::CHUNK_MAT_MAP_TILING:
1318         {
1319         const uint16_t iFlags = stream->GetI2();
1320 
1321         // Get the mapping mode (for both axes)
1322         if (iFlags & 0x2u)
1323             pcOut->mMapMode = aiTextureMapMode_Mirror;
1324 
1325         else if (iFlags & 0x10u)
1326             pcOut->mMapMode = aiTextureMapMode_Decal;
1327 
1328         // wrapping in all remaining cases
1329         else pcOut->mMapMode = aiTextureMapMode_Wrap;
1330         }
1331         break;
1332     };
1333 
1334     ASSIMP_3DS_END_CHUNK();
1335 }
1336 
1337 // ------------------------------------------------------------------------------------------------
1338 // Read a percentage chunk
ParsePercentageChunk()1339 float Discreet3DSImporter::ParsePercentageChunk()
1340 {
1341     Discreet3DS::Chunk chunk;
1342     ReadChunk(&chunk);
1343 
1344     if (Discreet3DS::CHUNK_PERCENTF == chunk.Flag)
1345         return stream->GetF4();
1346     else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag)
1347         return (float)((uint16_t)stream->GetI2()) / (float)0xFFFF;
1348     return get_qnan();
1349 }
1350 
1351 // ------------------------------------------------------------------------------------------------
1352 // Read a color chunk. If a percentage chunk is found instead it is read as a grayscale color
ParseColorChunk(aiColor3D * out,bool acceptPercent)1353 void Discreet3DSImporter::ParseColorChunk(aiColor3D* out,
1354     bool acceptPercent)
1355 {
1356     ai_assert(out != NULL);
1357 
1358     // error return value
1359     const float qnan = get_qnan();
1360     static const aiColor3D clrError = aiColor3D(qnan,qnan,qnan);
1361 
1362     Discreet3DS::Chunk chunk;
1363     ReadChunk(&chunk);
1364     const unsigned int diff = chunk.Size - sizeof(Discreet3DS::Chunk);
1365 
1366     bool bGamma = false;
1367 
1368     // Get the type of the chunk
1369     switch(chunk.Flag)
1370     {
1371     case Discreet3DS::CHUNK_LINRGBF:
1372         bGamma = true;
1373 
1374     case Discreet3DS::CHUNK_RGBF:
1375         if (sizeof(float) * 3 > diff)   {
1376             *out = clrError;
1377             return;
1378         }
1379         out->r = stream->GetF4();
1380         out->g = stream->GetF4();
1381         out->b = stream->GetF4();
1382         break;
1383 
1384     case Discreet3DS::CHUNK_LINRGBB:
1385         bGamma = true;
1386     case Discreet3DS::CHUNK_RGBB:
1387         if (sizeof(char) * 3 > diff)    {
1388             *out = clrError;
1389             return;
1390         }
1391         out->r = (float)(uint8_t)stream->GetI1() / 255.0f;
1392         out->g = (float)(uint8_t)stream->GetI1() / 255.0f;
1393         out->b = (float)(uint8_t)stream->GetI1() / 255.0f;
1394         break;
1395 
1396     // Percentage chunks are accepted, too.
1397     case Discreet3DS::CHUNK_PERCENTF:
1398         if (acceptPercent && 4 <= diff) {
1399             out->g = out->b = out->r = stream->GetF4();
1400             break;
1401         }
1402         *out = clrError;
1403         return;
1404 
1405     case Discreet3DS::CHUNK_PERCENTW:
1406         if (acceptPercent && 1 <= diff) {
1407             out->g = out->b = out->r = (float)(uint8_t)stream->GetI1() / 255.0f;
1408             break;
1409         }
1410         *out = clrError;
1411         return;
1412 
1413     default:
1414         stream->IncPtr(diff);
1415         // Skip unknown chunks, hope this won't cause any problems.
1416         return ParseColorChunk(out,acceptPercent);
1417     };
1418     (void)bGamma;
1419 }
1420 
1421 #endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER
1422