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