Coverage Report

Created: 2025-11-11 06:59

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