Coverage Report

Created: 2026-01-25 07:15

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
2
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
2
    unsigned int idx(NotSet);
71
7
    for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) {
72
5
        auto s = mScene->mMaterials[i].mName;
73
51
        for (char &it : s) {
74
51
            it = static_cast<char>(::tolower(static_cast<unsigned char>(it)));
75
51
        }
76
77
5
        if (std::string::npos == s.find("default")) {
78
4
            continue;
79
4
        }
80
81
1
        if (mScene->mMaterials[i].mDiffuse.r !=
82
1
                        mScene->mMaterials[i].mDiffuse.g ||
83
1
                mScene->mMaterials[i].mDiffuse.r !=
84
1
                        mScene->mMaterials[i].mDiffuse.b) continue;
85
86
1
        if (ContainsTextures(i)) {
87
0
            continue;
88
0
        }
89
1
        idx = i;
90
1
    }
91
2
    if (NotSet == idx) {
92
1
        idx = static_cast<unsigned int>(mScene->mMaterials.size());
93
1
    }
94
95
    // now iterate through all meshes and through all faces and
96
    // find all faces that are using the default material
97
2
    unsigned int cnt = 0;
98
4
    for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
99
2.13k
        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.13k
            if (NotSet == *a) {
104
0
                *a = idx;
105
0
                ++cnt;
106
2.13k
            } 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.13k
        }
112
2
    }
113
2
    if (cnt && idx == mScene->mMaterials.size()) {
114
        // We need to create our own default material
115
0
        Material sMat("%%%DEFAULT");
116
0
        sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f);
117
0
        mScene->mMaterials.push_back(sMat);
118
119
0
        ASSIMP_LOG_INFO("3DS: Generating default material");
120
0
    }
121
2
}
122
123
// ------------------------------------------------------------------------------------------------
124
// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached
125
2
void Discreet3DSImporter::CheckIndices(Mesh &sMesh) {
126
2.13k
    for (auto i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) {
127
        // check whether all indices are in range
128
8.54k
        for (unsigned int a = 0; a < 3; ++a) {
129
6.40k
            if ((*i).mIndices[a] >= sMesh.mPositions.size()) {
130
0
                ASSIMP_LOG_WARN("3DS: Vertex index overflow)");
131
0
                (*i).mIndices[a] = static_cast<uint32_t>(sMesh.mPositions.size() - 1);
132
0
            }
133
6.40k
            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
6.40k
        }
138
2.13k
    }
139
2
}
140
141
// ------------------------------------------------------------------------------------------------
142
// Generate out unique verbose format representation
143
2
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
2
    std::vector<aiVector3D> vNew(sMesh.mFaces.size() * 3);
149
2
    std::vector<aiVector3D> vNew2;
150
2
    if (sMesh.mTexCoords.size())
151
0
        vNew2.resize(sMesh.mFaces.size() * 3);
152
153
2.13k
    for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) {
154
2.13k
        Face &face = sMesh.mFaces[i];
155
156
        // Positions
157
8.54k
        for (unsigned int a = 0; a < 3; ++a, ++base) {
158
6.40k
            vNew[base] = sMesh.mPositions[face.mIndices[a]];
159
6.40k
            if (sMesh.mTexCoords.size())
160
0
                vNew2[base] = sMesh.mTexCoords[face.mIndices[a]];
161
162
6.40k
            face.mIndices[a] = base;
163
6.40k
        }
164
2.13k
    }
165
2
    sMesh.mPositions = vNew;
166
2
    sMesh.mTexCoords = vNew2;
167
2
}
168
169
// ------------------------------------------------------------------------------------------------
170
// Convert a 3DS texture to texture keys in an aiMaterial
171
0
void CopyTexture(aiMaterial &mat, Texture &texture, aiTextureType type) {
172
    // Setup the texture name
173
0
    aiString tex(texture.mMapName);
174
0
    mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0));
175
176
    // Setup the texture blend factor
177
0
    if (is_not_qnan(texture.mTextureBlend))
178
0
        mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0));
179
180
    // Setup the texture mapping mode
181
0
    auto mapMode = static_cast<int>(texture.mMapMode);
182
0
    mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0));
183
0
    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
0
    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
0
    mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0));
196
0
}
197
198
// ------------------------------------------------------------------------------------------------
199
// Convert a 3DS material to an aiMaterial
200
5
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
5
    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
5
    oldMat.mAmbient.r += mClrAmbient.r;
213
5
    oldMat.mAmbient.g += mClrAmbient.g;
214
5
    oldMat.mAmbient.b += mClrAmbient.b;
215
216
5
    aiString name(oldMat.mName);
217
5
    mat.AddProperty(&name, AI_MATKEY_NAME);
218
219
    // Material colors
220
5
    mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
221
5
    mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
222
5
    mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
223
5
    mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
224
225
    // Phong shininess and shininess strength
226
5
    if (Discreet3DS::Phong == oldMat.mShading || Discreet3DS::Metal == oldMat.mShading) {
227
5
        if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) {
228
0
            oldMat.mShading = Discreet3DS::Gouraud;
229
5
        } else {
230
5
            mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
231
5
            mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH);
232
5
        }
233
5
    }
234
235
    // Opacity
236
5
    mat.AddProperty<ai_real>(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY);
237
238
    // Bump height scaling
239
5
    mat.AddProperty<ai_real>(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING);
240
241
    // Two sided rendering?
242
5
    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
5
    aiShadingMode eShading = aiShadingMode_NoShading;
249
5
    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
0
    case Discreet3DS::Gouraud:
264
0
        eShading = aiShadingMode_Gouraud;
265
0
        break;
266
267
    // assume cook-torrance shading for metals.
268
5
    case Discreet3DS::Phong:
269
5
        eShading = aiShadingMode_Phong;
270
5
        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
5
    }
283
284
5
    const int eShading_ = eShading;
285
5
    mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL);
286
287
    // DIFFUSE texture
288
5
    if (oldMat.sTexDiffuse.mMapName.length() > 0)
289
0
        CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE);
290
291
    // SPECULAR texture
292
5
    if (oldMat.sTexSpecular.mMapName.length() > 0)
293
0
        CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR);
294
295
    // OPACITY texture
296
5
    if (oldMat.sTexOpacity.mMapName.length() > 0)
297
0
        CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY);
298
299
    // EMISSIVE texture
300
5
    if (oldMat.sTexEmissive.mMapName.length() > 0)
301
0
        CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE);
302
303
    // BUMP texture
304
5
    if (oldMat.sTexBump.mMapName.length() > 0)
305
0
        CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT);
306
307
    // SHININESS texture
308
5
    if (oldMat.sTexShininess.mMapName.length() > 0)
309
0
        CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS);
310
311
    // REFLECTION texture
312
5
    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
5
    if (oldMat.mName.length()) {
317
5
        aiString tex;
318
5
        tex.Set(oldMat.mName);
319
5
        mat.AddProperty(&tex, AI_MATKEY_NAME);
320
5
    }
321
5
}
322
323
// ------------------------------------------------------------------------------------------------
324
// Split meshes by their materials and generate output aiMesh'es
325
2
void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) {
326
2
    std::vector<aiMesh *> avOutMeshes;
327
2
    avOutMeshes.reserve(mScene->mMeshes.size() * 2);
328
329
2
    unsigned int iFaceCnt = 0, num = 0;
330
2
    aiString name;
331
332
    // we need to split all meshes by their materials
333
4
    for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
334
2
        std::unique_ptr<std::vector<unsigned int>[]> aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]);
335
336
2
        name.length = ASSIMP_itoa10(name.data, num);
337
2
        ++num;
338
339
2
        unsigned int iNum = 0;
340
2
        for (std::vector<unsigned int>::const_iterator a = (*i).mFaceMaterials.begin();
341
2.13k
                a != (*i).mFaceMaterials.end(); ++a, ++iNum) {
342
2.13k
            aiSplit[*a].push_back(iNum);
343
2.13k
        }
344
        // now generate submeshes
345
7
        for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) {
346
5
            if (aiSplit[p].empty()) {
347
0
                continue;
348
0
            }
349
5
            auto *meshOut = new aiMesh();
350
5
            meshOut->mName = name;
351
5
            meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
352
353
            // be sure to setup the correct material index
354
5
            meshOut->mMaterialIndex = p;
355
356
            // use the color data as temporary storage
357
5
            meshOut->mColors[0] = (aiColor4D *)(&*i);
358
5
            avOutMeshes.push_back(meshOut);
359
360
            // convert vertices
361
5
            meshOut->mNumFaces = static_cast<unsigned int>(aiSplit[p].size());
362
5
            meshOut->mNumVertices = meshOut->mNumFaces * 3;
363
364
            // allocate enough storage for faces
365
5
            meshOut->mFaces = new aiFace[meshOut->mNumFaces];
366
5
            iFaceCnt += meshOut->mNumFaces;
367
368
5
            meshOut->mVertices = new aiVector3D[meshOut->mNumVertices];
369
5
            meshOut->mNormals = new aiVector3D[meshOut->mNumVertices];
370
5
            if ((*i).mTexCoords.size()) {
371
0
                meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices];
372
0
            }
373
2.14k
            for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) {
374
2.13k
                unsigned int index = aiSplit[p][q];
375
2.13k
                aiFace &face = meshOut->mFaces[q];
376
377
2.13k
                face.mIndices = new unsigned int[3];
378
2.13k
                face.mNumIndices = 3;
379
380
8.54k
                for (unsigned int a = 0; a < 3; ++a, ++base) {
381
6.40k
                    unsigned int idx = (*i).mFaces[index].mIndices[a];
382
6.40k
                    meshOut->mVertices[base] = (*i).mPositions[idx];
383
6.40k
                    meshOut->mNormals[base] = (*i).mNormals[idx];
384
385
6.40k
                    if ((*i).mTexCoords.size())
386
0
                        meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx];
387
388
6.40k
                    face.mIndices[a] = base;
389
6.40k
                }
390
2.13k
            }
391
5
        }
392
2
    }
393
394
    // Copy them to the output array
395
2
    pcOut->mNumMeshes = (unsigned int)avOutMeshes.size();
396
2
    pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes]();
397
7
    for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
398
5
        pcOut->mMeshes[a] = avOutMeshes[a];
399
5
    }
400
401
    // We should have at least one face here
402
2
    if (!iFaceCnt) {
403
0
        throw DeadlyImportError("No faces loaded. The mesh is empty");
404
0
    }
405
2
}
406
407
// ------------------------------------------------------------------------------------------------
408
// Add a node to the scenegraph and setup its final transformation
409
2
void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) {
410
2
    std::vector<unsigned int> iArray;
411
2
    iArray.reserve(3);
412
413
2
    aiMatrix4x4 abs;
414
415
    // Find all meshes with the same name as the node
416
10
    for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) {
417
8
        const auto *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0];
418
8
        ai_assert(nullptr != pcMesh);
419
420
8
        if (pcIn->mName == pcMesh->mName)
421
4
            iArray.push_back(a);
422
8
    }
423
2
    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
1
        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
1
        aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat;
431
1
        mInv.Inverse();
432
1
        mInvTransposed.Transpose();
433
1
        aiVector3D pivot = pcIn->vPivot;
434
435
1
        pcOut->mNumMeshes = static_cast<unsigned int>(iArray.size());
436
1
        pcOut->mMeshes = new unsigned int[iArray.size()];
437
5
        for (unsigned int i = 0; i < iArray.size(); ++i) {
438
4
            const unsigned int iIndex = iArray[i];
439
4
            aiMesh *const mesh = pcSOut->mMeshes[iIndex];
440
441
4
            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
4
                const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices;
445
4
                aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals;
446
447
4.10k
                for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
448
4.10k
                    *pvCurrent = mInv * (*pvCurrent);
449
4.10k
                    *t2 = mInvTransposed * (*t2);
450
4.10k
                }
451
452
                // Handle negative transformation matrix determinant -> invert vertex x
453
4
                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
4
                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
4
                mesh->mColors[1] = (aiColor4D *)1;
470
4
            } else
471
0
                mesh->mColors[1] = (aiColor4D *)1;
472
473
            // Setup the mesh index
474
4
            pcOut->mMeshes[i] = iIndex;
475
4
        }
476
1
    }
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
2
    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
2
        pcOut->mName.Set(pcIn->mName);
488
489
    // Now build the transformation matrix of the node
490
    // ROTATION
491
2
    if (pcIn->aRotationKeys.size()) {
492
493
        // FIX to get to Assimp's quaternion conventions
494
2
        for (auto it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) {
495
1
            (*it).mValue.w *= -1.f;
496
1
        }
497
498
1
        pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix());
499
1
    } else if (pcIn->aCameraRollKeys.size()) {
500
0
        aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue),
501
0
                pcOut->mTransformation);
502
0
    }
503
504
    // SCALING
505
2
    aiMatrix4x4 &m = pcOut->mTransformation;
506
2
    if (pcIn->aScalingKeys.size()) {
507
1
        const aiVector3D &v = pcIn->aScalingKeys[0].mValue;
508
1
        m.a1 *= v.x;
509
1
        m.b1 *= v.x;
510
1
        m.c1 *= v.x;
511
1
        m.a2 *= v.y;
512
1
        m.b2 *= v.y;
513
1
        m.c2 *= v.y;
514
1
        m.a3 *= v.z;
515
1
        m.b3 *= v.z;
516
1
        m.c3 *= v.z;
517
1
    }
518
519
    // TRANSLATION
520
2
    if (pcIn->aPositionKeys.size()) {
521
1
        const aiVector3D &v = pcIn->aPositionKeys[0].mValue;
522
1
        m.a4 += v.x;
523
1
        m.b4 += v.y;
524
1
        m.c4 += v.z;
525
1
    }
526
527
    // Generate animation channels for the node
528
2
    if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 ||
529
2
            pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 ||
530
2
            pcIn->aTargetPositionKeys.size() > 1) {
531
0
        aiAnimation *anim = pcSOut->mAnimations[0];
532
0
        ai_assert(nullptr != anim);
533
534
0
        if (pcIn->aCameraRollKeys.size() > 1) {
535
0
            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
0
            pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size());
541
0
            for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) {
542
0
                aiQuatKey &q = pcIn->aRotationKeys[i];
543
0
                aiFloatKey &f = pcIn->aCameraRollKeys[i];
544
545
0
                q.mTime = f.mTime;
546
547
                // FIX to get to Assimp quaternion conventions
548
0
                q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue));
549
0
            }
550
0
        }
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
0
        for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) {
595
0
            if (pcSOut->mCameras[n]->mName == pcOut->mName) {
596
0
                pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f);
597
0
            }
598
0
        }
599
0
        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
0
        auto *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
607
0
        nda->mNodeName.Set(pcIn->mName);
608
609
        // POSITION keys
610
0
        if (!pcIn->aPositionKeys.empty()) {
611
0
            nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
612
0
            nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
613
0
            ::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0],
614
0
                    sizeof(aiVectorKey) * nda->mNumPositionKeys);
615
0
        }
616
617
        // ROTATION keys
618
0
        if (!pcIn->aRotationKeys.empty()) {
619
0
            nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
620
0
            nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys];
621
622
            // Rotations are quaternion offsets
623
0
            aiQuaternion abs1;
624
0
            for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) {
625
0
                const aiQuatKey &q = pcIn->aRotationKeys[n];
626
627
0
                abs1 = (n ? abs1 * q.mValue : q.mValue);
628
0
                nda->mRotationKeys[n].mTime = q.mTime;
629
0
                nda->mRotationKeys[n].mValue = abs1.Normalize();
630
0
            }
631
0
        }
632
633
        // SCALING keys
634
0
        if (!pcIn->aScalingKeys.empty()) {
635
0
            nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
636
0
            nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys];
637
0
            ::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0],
638
0
                    sizeof(aiVectorKey) * nda->mNumScalingKeys);
639
0
        }
640
0
    }
641
642
    // Allocate storage for children
643
2
    const auto size = static_cast<unsigned int>(pcIn->mChildren.size());
644
645
2
    pcOut->mNumChildren = size;
646
2
    if (size == 0) {
647
1
        return;
648
1
    }
649
650
1
    pcOut->mChildren = new aiNode *[pcIn->mChildren.size()];
651
652
    // Recursively process all children
653
654
2
    for (unsigned int i = 0; i < size; ++i) {
655
1
        pcOut->mChildren[i] = new aiNode();
656
1
        pcOut->mChildren[i]->mParent = pcOut;
657
1
        AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs);
658
1
    }
659
1
}
660
661
// ------------------------------------------------------------------------------------------------
662
// Find out how many node animation channels we'll have finally
663
2
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
2
    if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 ||
669
2
            node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 ||
670
2
            node->aTargetPositionKeys.size() > 1) {
671
0
        ++cnt;
672
673
        // account for the additional channel for the camera/spotlight target position
674
0
        if (node->aTargetPositionKeys.size() > 1) ++cnt;
675
0
    }
676
677
    // Recursively process all children
678
3
    for (unsigned int i = 0; i < node->mChildren.size(); ++i)
679
1
        CountTracks(node->mChildren[i], cnt);
680
2
}
681
682
// ------------------------------------------------------------------------------------------------
683
// Generate the output node graph
684
2
void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) {
685
2
    pcOut->mRootNode = new aiNode();
686
2
    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
1
    } else {
737
        // First of all: find out how many scaling, rotation and translation
738
        // animation tracks we'll have afterwards
739
1
        unsigned int numChannel = 0;
740
1
        CountTracks(mRootNode, numChannel);
741
742
1
        if (numChannel) {
743
            // Allocate a primary animation channel
744
0
            pcOut->mNumAnimations = 1;
745
0
            pcOut->mAnimations = new aiAnimation *[1];
746
0
            auto *anim = pcOut->mAnimations[0] = new aiAnimation();
747
748
0
            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
0
            anim->mChannels = new aiNodeAnim *[numChannel];
754
0
        }
755
756
1
        aiMatrix4x4 m;
757
1
        AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m);
758
1
    }
759
760
    // We used the first and second vertex color set to store some temporary values so we need to cleanup here
761
7
    for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
762
5
        pcOut->mMeshes[a]->mColors[0] = nullptr;
763
5
        pcOut->mMeshes[a]->mColors[1] = nullptr;
764
5
    }
765
766
2
    pcOut->mRootNode->mTransformation = aiMatrix4x4(
767
2
                                                1.f, 0.f, 0.f, 0.f,
768
2
                                                0.f, 0.f, 1.f, 0.f,
769
2
                                                0.f, -1.f, 0.f, 0.f,
770
2
                                                0.f, 0.f, 0.f, 1.f) *
771
2
                                        pcOut->mRootNode->mTransformation;
772
773
    // If the root node is unnamed name it "<3DSRoot>"
774
2
    if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") ||
775
1
            (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) {
776
1
        pcOut->mRootNode->mName.Set("<3DSRoot>");
777
1
    }
778
2
}
779
780
// ------------------------------------------------------------------------------------------------
781
// Convert all meshes in the scene and generate the final output scene.
782
2
void Discreet3DSImporter::ConvertScene(aiScene *pcOut) {
783
    // Allocate enough storage for all output materials
784
2
    pcOut->mNumMaterials = static_cast<unsigned int>(mScene->mMaterials.size());
785
2
    pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials];
786
787
    //  ... and convert the 3DS materials to aiMaterial's
788
7
    for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) {
789
5
        auto *pcNew = new aiMaterial();
790
5
        ConvertMaterial(mScene->mMaterials[i], *pcNew);
791
5
        pcOut->mMaterials[i] = pcNew;
792
5
    }
793
794
    // Generate the output mesh list
795
2
    ConvertMeshes(pcOut);
796
797
    // Now copy all light sources to the output scene
798
2
    pcOut->mNumLights = static_cast<unsigned int>(mScene->mLights.size());
799
2
    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
2
    pcOut->mNumCameras = static_cast<unsigned int>(mScene->mCameras.size());
806
2
    if (pcOut->mNumCameras) {
807
0
        pcOut->mCameras = new aiCamera *[pcOut->mNumCameras];
808
0
        memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras);
809
0
    }
810
2
}
811
812
} // namespace Assimp
813
814
#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER