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