Coverage Report

Created: 2024-08-02 07:04

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