Coverage Report

Created: 2026-06-15 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/3DS/3DSConverter.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2026, 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 Implementation of the 3ds importer class */
43
44
#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
45
46
// internal headers
47
#include "3DSLoader.h"
48
#include "Common/TargetAnimation.h"
49
#include <assimp/StringComparison.h>
50
#include <assimp/scene.h>
51
#include <assimp/DefaultLogger.hpp>
52
#include <cctype>
53
#include <memory>
54
55
namespace Assimp {
56
57
static constexpr unsigned int NotSet = 0xcdcdcdcd;
58
59
using namespace D3DS;
60
61
// ------------------------------------------------------------------------------------------------
62
// Setup final material indices, generate a default material if necessary
63
24
void Discreet3DSImporter::ReplaceDefaultMaterial() {
64
    // Try to find an existing material that matches the
65
    // typical default material setting:
66
    // - no textures
67
    // - diffuse color (in grey!)
68
    // NOTE: This is here to work-around the fact that some
69
    // exporters are writing a default material, too.
70
24
    unsigned int idx(NotSet);
71
66
    for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) {
72
42
        auto s = mScene->mMaterials[i].mName;
73
485
        for (char &it : s) {
74
485
            it = static_cast<char>(::tolower(static_cast<unsigned char>(it)));
75
485
        }
76
77
42
        if (std::string::npos == s.find("default")) {
78
8
            continue;
79
8
        }
80
81
34
        if (mScene->mMaterials[i].mDiffuse.r !=
82
34
                        mScene->mMaterials[i].mDiffuse.g ||
83
18
                mScene->mMaterials[i].mDiffuse.r !=
84
18
                        mScene->mMaterials[i].mDiffuse.b) continue;
85
86
18
        if (ContainsTextures(i)) {
87
17
            continue;
88
17
        }
89
1
        idx = i;
90
1
    }
91
24
    if (NotSet == idx) {
92
23
        idx = static_cast<unsigned int>(mScene->mMaterials.size());
93
23
    }
94
95
    // now iterate through all meshes and through all faces and
96
    // find all faces that are using the default material
97
24
    unsigned int cnt = 0;
98
81
    for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
99
2.85k
        for (auto a = i->mFaceMaterials.begin(); a != i->mFaceMaterials.end(); ++a) {
100
            // NOTE: The additional check seems to be necessary,
101
            // some exporters seem to generate invalid data here
102
103
2.79k
            if (NotSet == *a) {
104
73
                *a = idx;
105
73
                ++cnt;
106
2.72k
            } else if ((*a) >= mScene->mMaterials.size()) {
107
0
                *a = idx;
108
0
                ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material");
109
0
                ++cnt;
110
0
            }
111
2.79k
        }
112
57
    }
113
24
    if (cnt && idx == mScene->mMaterials.size()) {
114
        // We need to create our own default material
115
6
        Material sMat("%%%DEFAULT");
116
6
        sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f);
117
6
        mScene->mMaterials.push_back(sMat);
118
119
6
        ASSIMP_LOG_INFO("3DS: Generating default material");
120
6
    }
121
24
}
122
123
// ------------------------------------------------------------------------------------------------
124
// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached
125
57
void Discreet3DSImporter::CheckIndices(Mesh &sMesh) {
126
2.85k
    for (auto i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) {
127
        // check whether all indices are in range
128
11.1k
        for (unsigned int a = 0; a < 3; ++a) {
129
8.38k
            if ((*i).mIndices[a] >= sMesh.mPositions.size()) {
130
7
                ASSIMP_LOG_WARN("3DS: Vertex index overflow)");
131
7
                (*i).mIndices[a] = static_cast<uint32_t>(sMesh.mPositions.size() - 1);
132
7
            }
133
8.38k
            if (!sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) {
134
0
                ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)");
135
0
                (*i).mIndices[a] = static_cast<uint32_t>(sMesh.mTexCoords.size() - 1);
136
0
            }
137
8.38k
        }
138
2.79k
    }
139
57
}
140
141
// ------------------------------------------------------------------------------------------------
142
// Generate out unique verbose format representation
143
57
void Discreet3DSImporter::MakeUnique(Mesh &sMesh) {
144
    // TODO: really necessary? I don't think. Just a waste of memory and time
145
    // to do it now in a separate buffer.
146
147
    // Allocate output storage
148
57
    std::vector<aiVector3D> vNew(sMesh.mFaces.size() * 3);
149
57
    std::vector<aiVector3D> vNew2;
150
57
    if (sMesh.mTexCoords.size())
151
55
        vNew2.resize(sMesh.mFaces.size() * 3);
152
153
2.85k
    for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) {
154
2.79k
        Face &face = sMesh.mFaces[i];
155
156
        // Positions
157
11.1k
        for (unsigned int a = 0; a < 3; ++a, ++base) {
158
8.38k
            vNew[base] = sMesh.mPositions[face.mIndices[a]];
159
8.38k
            if (sMesh.mTexCoords.size())
160
1.98k
                vNew2[base] = sMesh.mTexCoords[face.mIndices[a]];
161
162
8.38k
            face.mIndices[a] = base;
163
8.38k
        }
164
2.79k
    }
165
57
    sMesh.mPositions = vNew;
166
57
    sMesh.mTexCoords = vNew2;
167
57
}
168
169
// ------------------------------------------------------------------------------------------------
170
// Convert a 3DS texture to texture keys in an aiMaterial
171
21
void CopyTexture(aiMaterial &mat, Texture &texture, aiTextureType type) {
172
    // Setup the texture name
173
21
    aiString tex(texture.mMapName);
174
21
    mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0));
175
176
    // Setup the texture blend factor
177
21
    if (is_not_qnan(texture.mTextureBlend))
178
21
        mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0));
179
180
    // Setup the texture mapping mode
181
21
    auto mapMode = static_cast<int>(texture.mMapMode);
182
21
    mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0));
183
21
    mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_V(type, 0));
184
185
    // Mirroring - double the scaling values
186
    // FIXME: this is not really correct ...
187
21
    if (texture.mMapMode == aiTextureMapMode_Mirror) {
188
0
        texture.mScaleU *= 2.0;
189
0
        texture.mScaleV *= 2.0;
190
0
        texture.mOffsetU /= 2.0;
191
0
        texture.mOffsetV /= 2.0;
192
0
    }
193
194
    // Setup texture UV transformations
195
21
    mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0));
196
21
}
197
198
// ------------------------------------------------------------------------------------------------
199
// Convert a 3DS material to an aiMaterial
200
48
void Discreet3DSImporter::ConvertMaterial(Material &oldMat, aiMaterial &mat) {
201
    // NOTE: Pass the background image to the viewer by bypassing the
202
    // material system. This is an evil hack, never do it again!
203
48
    if (mBackgroundImage.empty() && bHasBG) {
204
0
        aiString tex(mBackgroundImage);
205
0
        mat.AddProperty(&tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE);
206
207
        // Be sure this is only done for the first material
208
0
        mBackgroundImage = std::string();
209
0
    }
210
211
    // At first add the base ambient color of the scene to the material
212
48
    oldMat.mAmbient.r += mClrAmbient.r;
213
48
    oldMat.mAmbient.g += mClrAmbient.g;
214
48
    oldMat.mAmbient.b += mClrAmbient.b;
215
216
48
    aiString name(oldMat.mName);
217
48
    mat.AddProperty(&name, AI_MATKEY_NAME);
218
219
    // Material colors
220
48
    mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
221
48
    mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
222
48
    mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
223
48
    mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
224
225
    // Phong shininess and shininess strength
226
48
    if (Discreet3DS::Phong == oldMat.mShading || Discreet3DS::Metal == oldMat.mShading) {
227
42
        if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) {
228
26
            oldMat.mShading = Discreet3DS::Gouraud;
229
26
        } else {
230
16
            mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
231
16
            mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH);
232
16
        }
233
42
    }
234
235
    // Opacity
236
48
    mat.AddProperty<ai_real>(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY);
237
238
    // Bump height scaling
239
48
    mat.AddProperty<ai_real>(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING);
240
241
    // Two sided rendering?
242
48
    if (oldMat.mTwoSided) {
243
0
        int i = 1;
244
0
        mat.AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED);
245
0
    }
246
247
    // Shading mode
248
48
    aiShadingMode eShading = aiShadingMode_NoShading;
249
48
    switch (oldMat.mShading) {
250
0
    case Discreet3DS::Flat:
251
0
        eShading = aiShadingMode_Flat;
252
0
        break;
253
254
    // I don't know what "Wire" shading should be,
255
    // assume it is simple lambertian diffuse shading
256
0
    case Discreet3DS::Wire: {
257
        // Set the wireframe flag
258
0
        unsigned int iWire = 1;
259
0
        mat.AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME);
260
0
    }
261
0
        [[fallthrough]];
262
263
32
    case Discreet3DS::Gouraud:
264
32
        eShading = aiShadingMode_Gouraud;
265
32
        break;
266
267
    // assume cook-torrance shading for metals.
268
16
    case Discreet3DS::Phong:
269
16
        eShading = aiShadingMode_Phong;
270
16
        break;
271
272
0
    case Discreet3DS::Metal:
273
0
        eShading = aiShadingMode_CookTorrance;
274
0
        break;
275
276
        // FIX to workaround a warning with GCC 4 who complained
277
        // about a missing case Blinn: here - Blinn isn't a valid
278
        // value in the 3DS Loader, it is just needed for ASE
279
0
    case Discreet3DS::Blinn:
280
0
        eShading = aiShadingMode_Blinn;
281
0
        break;
282
48
    }
283
284
48
    const int eShading_ = eShading;
285
48
    mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL);
286
287
    // DIFFUSE texture
288
48
    if (oldMat.sTexDiffuse.mMapName.length() > 0)
289
11
        CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE);
290
291
    // SPECULAR texture
292
48
    if (oldMat.sTexSpecular.mMapName.length() > 0)
293
10
        CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR);
294
295
    // OPACITY texture
296
48
    if (oldMat.sTexOpacity.mMapName.length() > 0)
297
0
        CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY);
298
299
    // EMISSIVE texture
300
48
    if (oldMat.sTexEmissive.mMapName.length() > 0)
301
0
        CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE);
302
303
    // BUMP texture
304
48
    if (oldMat.sTexBump.mMapName.length() > 0)
305
0
        CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT);
306
307
    // SHININESS texture
308
48
    if (oldMat.sTexShininess.mMapName.length() > 0)
309
0
        CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS);
310
311
    // REFLECTION texture
312
48
    if (oldMat.sTexReflective.mMapName.length() > 0)
313
0
        CopyTexture(mat, oldMat.sTexReflective, aiTextureType_REFLECTION);
314
315
    // Store the name of the material itself, too
316
48
    if (oldMat.mName.length()) {
317
48
        aiString tex;
318
48
        tex.Set(oldMat.mName);
319
48
        mat.AddProperty(&tex, AI_MATKEY_NAME);
320
48
    }
321
48
}
322
323
// ------------------------------------------------------------------------------------------------
324
// Split meshes by their materials and generate output aiMesh'es
325
24
void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) {
326
24
    std::vector<aiMesh *> avOutMeshes;
327
24
    avOutMeshes.reserve(mScene->mMeshes.size() * 2);
328
329
24
    unsigned int iFaceCnt = 0, num = 0;
330
24
    aiString name;
331
332
    // we need to split all meshes by their materials
333
81
    for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
334
57
        std::unique_ptr<std::vector<unsigned int>[]> aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]);
335
336
57
        name.length = ASSIMP_itoa10(name.data, num);
337
57
        ++num;
338
339
57
        unsigned int iNum = 0;
340
57
        for (std::vector<unsigned int>::const_iterator a = (*i).mFaceMaterials.begin();
341
2.85k
                a != (*i).mFaceMaterials.end(); ++a, ++iNum) {
342
2.79k
            aiSplit[*a].push_back(iNum);
343
2.79k
        }
344
        // now generate submeshes
345
234
        for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) {
346
177
            if (aiSplit[p].empty()) {
347
116
                continue;
348
116
            }
349
61
            auto *meshOut = new aiMesh();
350
61
            meshOut->mName = name;
351
61
            meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
352
353
            // be sure to setup the correct material index
354
61
            meshOut->mMaterialIndex = p;
355
356
            // use the color data as temporary storage
357
61
            meshOut->mColors[0] = (aiColor4D *)(&*i);
358
61
            avOutMeshes.push_back(meshOut);
359
360
            // convert vertices
361
61
            meshOut->mNumFaces = static_cast<unsigned int>(aiSplit[p].size());
362
61
            meshOut->mNumVertices = meshOut->mNumFaces * 3;
363
364
            // allocate enough storage for faces
365
61
            meshOut->mFaces = new aiFace[meshOut->mNumFaces];
366
61
            iFaceCnt += meshOut->mNumFaces;
367
368
61
            meshOut->mVertices = new aiVector3D[meshOut->mNumVertices];
369
61
            meshOut->mNormals = new aiVector3D[meshOut->mNumVertices];
370
61
            if ((*i).mTexCoords.size()) {
371
56
                meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices];
372
56
            }
373
2.85k
            for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) {
374
2.79k
                unsigned int index = aiSplit[p][q];
375
2.79k
                aiFace &face = meshOut->mFaces[q];
376
377
2.79k
                face.mIndices = new unsigned int[3];
378
2.79k
                face.mNumIndices = 3;
379
380
11.1k
                for (unsigned int a = 0; a < 3; ++a, ++base) {
381
8.38k
                    unsigned int idx = (*i).mFaces[index].mIndices[a];
382
8.38k
                    meshOut->mVertices[base] = (*i).mPositions[idx];
383
8.38k
                    meshOut->mNormals[base] = (*i).mNormals[idx];
384
385
8.38k
                    if ((*i).mTexCoords.size())
386
1.98k
                        meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx];
387
388
8.38k
                    face.mIndices[a] = base;
389
8.38k
                }
390
2.79k
            }
391
61
        }
392
57
    }
393
394
    // Copy them to the output array
395
24
    pcOut->mNumMeshes = (unsigned int)avOutMeshes.size();
396
24
    pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes]();
397
85
    for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
398
61
        pcOut->mMeshes[a] = avOutMeshes[a];
399
61
    }
400
401
    // We should have at least one face here
402
24
    if (!iFaceCnt) {
403
0
        throw DeadlyImportError("No faces loaded. The mesh is empty");
404
0
    }
405
24
}
406
407
// ------------------------------------------------------------------------------------------------
408
// Add a node to the scenegraph and setup its final transformation
409
83
void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) {
410
83
    std::vector<unsigned int> iArray;
411
83
    iArray.reserve(3);
412
413
83
    aiMatrix4x4 abs;
414
415
    // Find all meshes with the same name as the node
416
434
    for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) {
417
351
        const auto *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0];
418
351
        ai_assert(nullptr != pcMesh);
419
420
351
        if (pcIn->mName == pcMesh->mName)
421
59
            iArray.push_back(a);
422
351
    }
423
83
    if (!iArray.empty()) {
424
        // The matrix should be identical for all meshes with the
425
        // same name. It HAS to be identical for all meshes .....
426
55
        auto *imesh = ((D3DS::Mesh *)pcSOut->mMeshes[iArray[0]]->mColors[0]);
427
428
        // Compute the inverse of the transformation matrix to move the
429
        // vertices back to their relative and local space
430
55
        aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat;
431
55
        mInv.Inverse();
432
55
        mInvTransposed.Transpose();
433
55
        aiVector3D pivot = pcIn->vPivot;
434
435
55
        pcOut->mNumMeshes = static_cast<unsigned int>(iArray.size());
436
55
        pcOut->mMeshes = new unsigned int[iArray.size()];
437
114
        for (unsigned int i = 0; i < iArray.size(); ++i) {
438
59
            const unsigned int iIndex = iArray[i];
439
59
            aiMesh *const mesh = pcSOut->mMeshes[iIndex];
440
441
59
            if (mesh->mColors[1] == nullptr) {
442
                // Transform the vertices back into their local space
443
                // fixme: consider computing normals after this, so we don't need to transform them
444
59
                const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices;
445
59
                aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals;
446
447
6.10k
                for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
448
6.04k
                    *pvCurrent = mInv * (*pvCurrent);
449
6.04k
                    *t2 = mInvTransposed * (*t2);
450
6.04k
                }
451
452
                // Handle negative transformation matrix determinant -> invert vertex x
453
59
                if (imesh->mMat.Determinant() < 0.0f) {
454
                    // we *must* have normals
455
0
                    for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
456
0
                        pvCurrent->x *= -1.f;
457
0
                        t2->x *= -1.f;
458
0
                    }
459
0
                    ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis");
460
0
                }
461
462
                // Handle pivot point
463
59
                if (pivot.x || pivot.y || pivot.z) {
464
0
                    for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) {
465
0
                        *pvCurrent -= pivot;
466
0
                    }
467
0
                }
468
469
59
                mesh->mColors[1] = (aiColor4D *)1;
470
59
            } else
471
0
                mesh->mColors[1] = (aiColor4D *)1;
472
473
            // Setup the mesh index
474
59
            pcOut->mMeshes[i] = iIndex;
475
59
        }
476
55
    }
477
478
    // Setup the name of the node
479
    // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number
480
83
    if (pcIn->mInstanceNumber > 1) {
481
0
        char tmp[12] = {'\0'};
482
0
        ASSIMP_itoa10(tmp, pcIn->mInstanceNumber);
483
0
        std::string tempStr = pcIn->mName + "_inst_";
484
0
        tempStr += tmp;
485
0
        pcOut->mName.Set(tempStr);
486
0
    } else
487
83
        pcOut->mName.Set(pcIn->mName);
488
489
    // Now build the transformation matrix of the node
490
    // ROTATION
491
83
    if (pcIn->aRotationKeys.size()) {
492
493
        // FIX to get to Assimp's quaternion conventions
494
294
        for (auto it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) {
495
238
            (*it).mValue.w *= -1.f;
496
238
        }
497
498
56
        pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix());
499
56
    } else if (pcIn->aCameraRollKeys.size()) {
500
4
        aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue),
501
4
                pcOut->mTransformation);
502
4
    }
503
504
    // SCALING
505
83
    aiMatrix4x4 &m = pcOut->mTransformation;
506
83
    if (pcIn->aScalingKeys.size()) {
507
56
        const aiVector3D &v = pcIn->aScalingKeys[0].mValue;
508
56
        m.a1 *= v.x;
509
56
        m.b1 *= v.x;
510
56
        m.c1 *= v.x;
511
56
        m.a2 *= v.y;
512
56
        m.b2 *= v.y;
513
56
        m.c2 *= v.y;
514
56
        m.a3 *= v.z;
515
56
        m.b3 *= v.z;
516
56
        m.c3 *= v.z;
517
56
    }
518
519
    // TRANSLATION
520
83
    if (pcIn->aPositionKeys.size()) {
521
60
        const aiVector3D &v = pcIn->aPositionKeys[0].mValue;
522
60
        m.a4 += v.x;
523
60
        m.b4 += v.y;
524
60
        m.c4 += v.z;
525
60
    }
526
527
    // Generate animation channels for the node
528
83
    if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 ||
529
80
            pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 ||
530
77
            pcIn->aTargetPositionKeys.size() > 1) {
531
6
        aiAnimation *anim = pcSOut->mAnimations[0];
532
6
        ai_assert(nullptr != anim);
533
534
6
        if (pcIn->aCameraRollKeys.size() > 1) {
535
3
            ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting camera roll track ...");
536
537
            // Camera roll keys - in fact they're just rotations
538
            // around the camera's z axis. The angles are given
539
            // in degrees (and they're clockwise).
540
3
            pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size());
541
9
            for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) {
542
6
                aiQuatKey &q = pcIn->aRotationKeys[i];
543
6
                aiFloatKey &f = pcIn->aCameraRollKeys[i];
544
545
6
                q.mTime = f.mTime;
546
547
                // FIX to get to Assimp quaternion conventions
548
6
                q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue));
549
6
            }
550
3
        }
551
#if 0
552
        if (pcIn->aTargetPositionKeys.size() > 1)
553
        {
554
            ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting target track ...");
555
556
            // Camera or spot light - need to convert the separate
557
            // target position channel to our representation
558
            TargetAnimationHelper helper;
559
560
            if (pcIn->aPositionKeys.empty())
561
            {
562
                // We can just pass zero here ...
563
                helper.SetFixedMainAnimationChannel(aiVector3D());
564
            }
565
            else  helper.SetMainAnimationChannel(&pcIn->aPositionKeys);
566
            helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys);
567
568
            // Do the conversion
569
            std::vector<aiVectorKey> distanceTrack;
570
            helper.Process(&distanceTrack);
571
572
            // Now add a new node as child, name it <ourName>.Target
573
            // and assign the distance track to it. This is that the
574
            // information where the target is and how it moves is
575
            // not lost
576
            D3DS::Node* nd = new D3DS::Node();
577
            pcIn->push_back(nd);
578
579
            nd->mName = pcIn->mName + ".Target";
580
581
            aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
582
            nda->mNodeName.Set(nd->mName);
583
584
            nda->mNumPositionKeys = (unsigned int)distanceTrack.size();
585
            nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
586
            ::memcpy(nda->mPositionKeys,&distanceTrack[0],
587
                sizeof(aiVectorKey)*nda->mNumPositionKeys);
588
        }
589
#endif
590
591
        // Cameras or lights define their transformation in their parent node and in the
592
        // corresponding light or camera chunks. However, we read and process the latter
593
        // to be able to return valid cameras/lights even if no scenegraph is given.
594
11
        for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) {
595
5
            if (pcSOut->mCameras[n]->mName == pcOut->mName) {
596
4
                pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f);
597
4
            }
598
5
        }
599
6
        for (unsigned int n = 0; n < pcSOut->mNumLights; ++n) {
600
0
            if (pcSOut->mLights[n]->mName == pcOut->mName) {
601
0
                pcSOut->mLights[n]->mDirection = aiVector3D(0.f, 0.f, 1.f);
602
0
            }
603
0
        }
604
605
        // Allocate a new node anim and setup its name
606
6
        auto *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
607
6
        nda->mNodeName.Set(pcIn->mName);
608
609
        // POSITION keys
610
6
        if (!pcIn->aPositionKeys.empty()) {
611
6
            nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
612
6
            nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
613
6
            ::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0],
614
6
                    sizeof(aiVectorKey) * nda->mNumPositionKeys);
615
6
        }
616
617
        // ROTATION keys
618
6
        if (!pcIn->aRotationKeys.empty()) {
619
5
            nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
620
5
            nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys];
621
622
            // Rotations are quaternion offsets
623
5
            aiQuaternion abs1;
624
195
            for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) {
625
190
                const aiQuatKey &q = pcIn->aRotationKeys[n];
626
627
190
                abs1 = (n ? abs1 * q.mValue : q.mValue);
628
190
                nda->mRotationKeys[n].mTime = q.mTime;
629
190
                nda->mRotationKeys[n].mValue = abs1.Normalize();
630
190
            }
631
5
        }
632
633
        // SCALING keys
634
6
        if (!pcIn->aScalingKeys.empty()) {
635
2
            nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
636
2
            nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys];
637
2
            ::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0],
638
2
                    sizeof(aiVectorKey) * nda->mNumScalingKeys);
639
2
        }
640
6
    }
641
642
    // Allocate storage for children
643
83
    const auto size = static_cast<unsigned int>(pcIn->mChildren.size());
644
645
83
    pcOut->mNumChildren = size;
646
83
    if (size == 0) {
647
59
        return;
648
59
    }
649
650
24
    pcOut->mChildren = new aiNode *[pcIn->mChildren.size()];
651
652
    // Recursively process all children
653
654
84
    for (unsigned int i = 0; i < size; ++i) {
655
60
        pcOut->mChildren[i] = new aiNode();
656
60
        pcOut->mChildren[i]->mParent = pcOut;
657
60
        AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs);
658
60
    }
659
24
}
660
661
// ------------------------------------------------------------------------------------------------
662
// Find out how many node animation channels we'll have finally
663
83
void CountTracks(D3DS::Node *node, unsigned int &cnt) {
664
    //////////////////////////////////////////////////////////////////////////////
665
    // We will never generate more than one channel for a node, so
666
    // this is rather easy here.
667
668
83
    if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 ||
669
80
            node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 ||
670
77
            node->aTargetPositionKeys.size() > 1) {
671
6
        ++cnt;
672
673
        // account for the additional channel for the camera/spotlight target position
674
6
        if (node->aTargetPositionKeys.size() > 1) ++cnt;
675
6
    }
676
677
    // Recursively process all children
678
143
    for (unsigned int i = 0; i < node->mChildren.size(); ++i)
679
60
        CountTracks(node->mChildren[i], cnt);
680
83
}
681
682
// ------------------------------------------------------------------------------------------------
683
// Generate the output node graph
684
24
void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) {
685
24
    pcOut->mRootNode = new aiNode();
686
24
    if (mRootNode->mChildren.empty()) {
687
        //////////////////////////////////////////////////////////////////////////////
688
        // It seems the file is so messed up that it has not even a hierarchy.
689
        // generate a flat hiearachy which looks like this:
690
        //
691
        //                ROOT_NODE
692
        //                   |
693
        //   ----------------------------------------
694
        //   |       |       |            |         |
695
        // MESH_0  MESH_1  MESH_2  ...  MESH_N    CAMERA_0 ....
696
        //
697
1
        ASSIMP_LOG_WARN("No hierarchy information has been found in the file. ");
698
699
1
        pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes +
700
1
                                         static_cast<unsigned int>(mScene->mCameras.size() + mScene->mLights.size());
701
702
1
        pcOut->mRootNode->mChildren = new aiNode *[pcOut->mRootNode->mNumChildren];
703
1
        pcOut->mRootNode->mName.Set("<3DSDummyRoot>");
704
705
        // Build dummy nodes for all meshes
706
1
        unsigned int a = 0;
707
2
        for (unsigned int i = 0; i < pcOut->mNumMeshes; ++i, ++a) {
708
1
            pcOut->mRootNode->mChildren[a] = new aiNode();  
709
1
            auto *pcNode = pcOut->mRootNode->mChildren[a];
710
1
            pcNode->mParent = pcOut->mRootNode;
711
1
            pcNode->mMeshes = new unsigned int[1];
712
1
            pcNode->mMeshes[0] = i;
713
1
            pcNode->mNumMeshes = 1;
714
715
            // Build a name for the node
716
1
            pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "3DSMesh_%u", i);
717
1
        }
718
719
        // Build dummy nodes for all cameras
720
1
        for (unsigned int i = 0; i < (unsigned int)mScene->mCameras.size(); ++i, ++a) {
721
0
            auto *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
722
0
            pcNode->mParent = pcOut->mRootNode;
723
724
            // Build a name for the node
725
0
            pcNode->mName = mScene->mCameras[i]->mName;
726
0
        }
727
728
        // Build dummy nodes for all lights
729
1
        for (unsigned int i = 0; i < (unsigned int)mScene->mLights.size(); ++i, ++a) {
730
0
            auto *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
731
0
            pcNode->mParent = pcOut->mRootNode;
732
733
            // Build a name for the node
734
0
            pcNode->mName = mScene->mLights[i]->mName;
735
0
        }
736
23
    } else {
737
        // First of all: find out how many scaling, rotation and translation
738
        // animation tracks we'll have afterwards
739
23
        unsigned int numChannel = 0;
740
23
        CountTracks(mRootNode, numChannel);
741
742
23
        if (numChannel) {
743
            // Allocate a primary animation channel
744
5
            pcOut->mNumAnimations = 1;
745
5
            pcOut->mAnimations = new aiAnimation *[1];
746
5
            auto *anim = pcOut->mAnimations[0] = new aiAnimation();
747
748
5
            anim->mName.Set("3DSMasterAnim");
749
750
            // Allocate enough storage for all node animation channels,
751
            // but don't set the mNumChannels member - we'll use it to
752
            // index into the array
753
5
            anim->mChannels = new aiNodeAnim *[numChannel];
754
5
        }
755
756
23
        aiMatrix4x4 m;
757
23
        AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m);
758
23
    }
759
760
    // We used the first and second vertex color set to store some temporary values so we need to cleanup here
761
85
    for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
762
61
        pcOut->mMeshes[a]->mColors[0] = nullptr;
763
61
        pcOut->mMeshes[a]->mColors[1] = nullptr;
764
61
    }
765
766
24
    pcOut->mRootNode->mTransformation = aiMatrix4x4(
767
24
                                                1.f, 0.f, 0.f, 0.f,
768
24
                                                0.f, 0.f, 1.f, 0.f,
769
24
                                                0.f, -1.f, 0.f, 0.f,
770
24
                                                0.f, 0.f, 0.f, 1.f) *
771
24
                                        pcOut->mRootNode->mTransformation;
772
773
    // If the root node is unnamed name it "<3DSRoot>"
774
24
    if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") ||
775
23
            (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) {
776
23
        pcOut->mRootNode->mName.Set("<3DSRoot>");
777
23
    }
778
24
}
779
780
// ------------------------------------------------------------------------------------------------
781
// Convert all meshes in the scene and generate the final output scene.
782
24
void Discreet3DSImporter::ConvertScene(aiScene *pcOut) {
783
    // Allocate enough storage for all output materials
784
24
    pcOut->mNumMaterials = static_cast<unsigned int>(mScene->mMaterials.size());
785
24
    pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials];
786
787
    //  ... and convert the 3DS materials to aiMaterial's
788
72
    for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) {
789
48
        auto *pcNew = new aiMaterial();
790
48
        ConvertMaterial(mScene->mMaterials[i], *pcNew);
791
48
        pcOut->mMaterials[i] = pcNew;
792
48
    }
793
794
    // Generate the output mesh list
795
24
    ConvertMeshes(pcOut);
796
797
    // Now copy all light sources to the output scene
798
24
    pcOut->mNumLights = static_cast<unsigned int>(mScene->mLights.size());
799
24
    if (pcOut->mNumLights) {
800
0
        pcOut->mLights = new aiLight *[pcOut->mNumLights];
801
0
        memcpy(pcOut->mLights, &mScene->mLights[0], sizeof(void *) * pcOut->mNumLights);
802
0
    }
803
804
    // Now copy all cameras to the output scene
805
24
    pcOut->mNumCameras = static_cast<unsigned int>(mScene->mCameras.size());
806
24
    if (pcOut->mNumCameras) {
807
4
        pcOut->mCameras = new aiCamera *[pcOut->mNumCameras];
808
4
        memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras);
809
4
    }
810
24
}
811
812
} // namespace Assimp
813
814
#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER