Coverage Report

Created: 2026-04-29 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/X/XFileImporter.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
/** @file  XFileImporter.cpp
42
 *  @brief Implementation of the XFile importer class
43
 */
44
45
#ifndef ASSIMP_BUILD_NO_X_IMPORTER
46
47
#include "XFileImporter.h"
48
#include "XFileParser.h"
49
#include "PostProcessing/ConvertToLHProcess.h"
50
51
#include <assimp/TinyFormatter.h>
52
#include <assimp/IOSystem.hpp>
53
#include <assimp/scene.h>
54
#include <assimp/DefaultLogger.hpp>
55
#include <assimp/importerdesc.h>
56
57
#include <cctype>
58
#include <memory>
59
60
namespace Assimp {
61
62
using namespace Assimp::Formatter;
63
64
static constexpr aiImporterDesc desc = {
65
    "Direct3D XFile Importer",
66
    "",
67
    "",
68
    "",
69
    aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour,
70
    1,
71
    3,
72
    1,
73
    5,
74
    "x"
75
};
76
77
// ------------------------------------------------------------------------------------------------
78
// Returns whether the class can handle the format of the given file.
79
0
bool XFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
80
0
    static constexpr uint32_t token[] = { AI_MAKE_MAGIC("xof ") };
81
0
    return CheckMagicToken(pIOHandler, pFile, token, AI_COUNT_OF(token));
82
0
}
83
84
// ------------------------------------------------------------------------------------------------
85
// Get file extension list
86
38.0k
const aiImporterDesc *XFileImporter::GetInfo() const {
87
38.0k
    return &desc;
88
38.0k
}
89
90
// ------------------------------------------------------------------------------------------------
91
// Imports the given file into the given scene structure.
92
0
void XFileImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
93
    // read file into memory
94
0
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
95
0
    if (file == nullptr) {
96
0
        throw DeadlyImportError("Failed to open file ", pFile, ".");
97
0
    }
98
99
0
    static const size_t MinSize = 16;
100
0
    size_t fileSize = file->FileSize();
101
0
    if (fileSize < MinSize) {
102
0
        throw DeadlyImportError("XFile is too small.");
103
0
    }
104
105
    // in the hope that binary files will never start with a BOM ...
106
0
    mBuffer.resize(fileSize + 1);
107
0
    file->Read(&mBuffer.front(), 1, fileSize);
108
0
    ConvertToUTF8(mBuffer);
109
110
    // parse the file into a temporary representation
111
0
    XFileParser parser(mBuffer);
112
113
    // and create the proper return structures out of it
114
0
    CreateDataRepresentationFromImport(pScene, parser.GetImportedData());
115
116
    // if nothing came from it, report it as error
117
0
    if (!pScene->mRootNode) {
118
0
        throw DeadlyImportError("XFile is ill-formatted - no content imported.");
119
0
    }
120
0
}
121
122
// ------------------------------------------------------------------------------------------------
123
// Constructs the return data structure out of the imported data.
124
0
void XFileImporter::CreateDataRepresentationFromImport(aiScene *pScene, XFile::Scene *pData) {
125
    // Read the global materials first so that meshes referring to them can find them later
126
0
    ConvertMaterials(pScene, pData->mGlobalMaterials);
127
128
    // copy nodes, extracting meshes and materials on the way
129
0
    pScene->mRootNode = CreateNodes(pScene, nullptr, pData->mRootNode);
130
131
    // extract animations
132
0
    CreateAnimations(pScene, pData);
133
134
    // read the global meshes that were stored outside of any node
135
0
    if (!pData->mGlobalMeshes.empty()) {
136
        // create a root node to hold them if there isn't any, yet
137
0
        if (pScene->mRootNode == nullptr) {
138
0
            pScene->mRootNode = new aiNode;
139
0
            pScene->mRootNode->mName.Set("$dummy_node");
140
0
        }
141
142
        // convert all global meshes and store them in the root node.
143
        // If there was one before, the global meshes now suddenly have its transformation matrix...
144
        // Don't know what to do there, I don't want to insert another node under the present root node
145
        // just to avoid this.
146
0
        CreateMeshes(pScene, pScene->mRootNode, pData->mGlobalMeshes);
147
0
    }
148
149
0
    if (!pScene->mRootNode) {
150
0
        throw DeadlyImportError("No root node");
151
0
    }
152
153
    // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly
154
0
    MakeLeftHandedProcess convertProcess;
155
0
    convertProcess.Execute(pScene);
156
157
0
    FlipWindingOrderProcess flipper;
158
0
    flipper.Execute(pScene);
159
160
    // finally: create a dummy material if not material was imported
161
0
    if (pScene->mNumMaterials == 0) {
162
0
        pScene->mNumMaterials = 1;
163
        // create the Material
164
0
        aiMaterial *mat = new aiMaterial;
165
0
        int shadeMode = (int)aiShadingMode_Gouraud;
166
0
        mat->AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL);
167
        // material colours
168
0
        int specExp = 1;
169
170
0
        aiColor3D clr = aiColor3D(0, 0, 0);
171
0
        mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_EMISSIVE);
172
0
        mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
173
174
0
        clr = aiColor3D(0.5f, 0.5f, 0.5f);
175
0
        mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
176
0
        mat->AddProperty(&specExp, 1, AI_MATKEY_SHININESS);
177
178
0
        pScene->mMaterials = new aiMaterial *[1];
179
0
        pScene->mMaterials[0] = mat;
180
0
    }
181
0
}
182
183
// ------------------------------------------------------------------------------------------------
184
// Recursively creates scene nodes from the imported hierarchy.
185
0
aiNode *XFileImporter::CreateNodes(aiScene *pScene, aiNode *pParent, const XFile::Node *pNode) {
186
0
    if (!pNode) {
187
0
        return nullptr;
188
0
    }
189
190
    // create node
191
0
    aiNode *node = new aiNode;
192
0
    node->mName.length = (ai_uint32)pNode->mName.length();
193
0
    node->mParent = pParent;
194
0
    memcpy(node->mName.data, pNode->mName.c_str(), pNode->mName.length());
195
0
    node->mName.data[node->mName.length] = 0;
196
0
    node->mTransformation = pNode->mTrafoMatrix;
197
198
    // convert meshes from the source node
199
0
    CreateMeshes(pScene, node, pNode->mMeshes);
200
201
    // handle children
202
0
    if (!pNode->mChildren.empty()) {
203
0
        node->mNumChildren = (unsigned int)pNode->mChildren.size();
204
0
        node->mChildren = new aiNode *[node->mNumChildren];
205
206
0
        for (unsigned int a = 0; a < pNode->mChildren.size(); ++a) {
207
0
            node->mChildren[a] = CreateNodes(pScene, node, pNode->mChildren[a]);
208
0
        }
209
0
    }
210
211
0
    return node;
212
0
}
213
214
// ------------------------------------------------------------------------------------------------
215
// Creates the meshes for the given node.
216
0
void XFileImporter::CreateMeshes(aiScene *pScene, aiNode *pNode, const std::vector<XFile::Mesh *> &pMeshes) {
217
0
    if (pMeshes.empty()) {
218
0
        return;
219
0
    }
220
221
    // create a mesh for each mesh-material combination in the source node
222
0
    std::vector<aiMesh *> meshes;
223
0
    for (unsigned int a = 0; a < pMeshes.size(); ++a) {
224
0
        XFile::Mesh *sourceMesh = pMeshes[a];
225
0
        if (nullptr == sourceMesh) {
226
0
            continue;
227
0
        }
228
229
        // first convert its materials so that we can find them with their index afterwards
230
0
        ConvertMaterials(pScene, sourceMesh->mMaterials);
231
232
0
        unsigned int numMaterials = std::max((unsigned int)sourceMesh->mMaterials.size(), 1u);
233
0
        for (unsigned int b = 0; b < numMaterials; ++b) {
234
            // collect the faces belonging to this material
235
0
            std::vector<unsigned int> faces;
236
0
            unsigned int numVertices = 0;
237
0
            if (!sourceMesh->mFaceMaterials.empty()) {
238
                // if there is a per-face material defined, select the faces with the corresponding material
239
0
                for (unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c) {
240
0
                    if (sourceMesh->mFaceMaterials[c] == b) {
241
0
                        faces.push_back(c);
242
0
                        numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size();
243
0
                    }
244
0
                }
245
0
            } else {
246
                // if there is no per-face material, place everything into one mesh
247
0
                for (unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c) {
248
0
                    faces.push_back(c);
249
0
                    numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size();
250
0
                }
251
0
            }
252
253
            // no faces/vertices using this material? strange...
254
0
            if (numVertices == 0) {
255
0
                continue;
256
0
            }
257
258
            // create a submesh using this material
259
0
            aiMesh *mesh = new aiMesh;
260
0
            meshes.push_back(mesh);
261
262
            // find the material in the scene's material list. Either own material
263
            // or referenced material, it should already have a valid index
264
0
            if (!sourceMesh->mFaceMaterials.empty()) {
265
0
                mesh->mMaterialIndex = static_cast<unsigned int>(sourceMesh->mMaterials[b].sceneIndex);
266
0
            } else {
267
0
                mesh->mMaterialIndex = 0;
268
0
            }
269
270
            // Create properly sized data arrays in the mesh. We store unique vertices per face,
271
            // as specified
272
0
            mesh->mNumVertices = numVertices;
273
0
            mesh->mVertices = new aiVector3D[numVertices];
274
0
            mesh->mNumFaces = (unsigned int)faces.size();
275
0
            mesh->mFaces = new aiFace[mesh->mNumFaces];
276
277
            // name
278
0
            mesh->mName.Set(sourceMesh->mName);
279
280
            // normals?
281
0
            if (sourceMesh->mNormals.size() > 0) {
282
0
                mesh->mNormals = new aiVector3D[numVertices];
283
0
            }
284
            // texture coords
285
0
            for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) {
286
0
                if (!sourceMesh->mTexCoords[c].empty()) {
287
0
                    mesh->mTextureCoords[c] = new aiVector3D[numVertices];
288
0
                }
289
0
            }
290
            // vertex colors
291
0
            for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) {
292
0
                if (!sourceMesh->mColors[c].empty()) {
293
0
                    mesh->mColors[c] = new aiColor4D[numVertices];
294
0
                }
295
0
            }
296
297
            // now collect the vertex data of all data streams present in the imported mesh
298
0
            unsigned int newIndex(0);
299
0
            std::vector<unsigned int> orgPoints; // from which original point each new vertex stems
300
0
            orgPoints.resize(numVertices, 0);
301
302
0
            for (unsigned int c = 0; c < faces.size(); ++c) {
303
0
                unsigned int f = faces[c]; // index of the source face
304
0
                const XFile::Face &pf = sourceMesh->mPosFaces[f]; // position source face
305
306
                // create face. either triangle or triangle fan depending on the index count
307
0
                aiFace &df = mesh->mFaces[c]; // destination face
308
0
                df.mNumIndices = (unsigned int)pf.mIndices.size();
309
0
                df.mIndices = new unsigned int[df.mNumIndices];
310
311
                // collect vertex data for indices of this face
312
0
                for (unsigned int d = 0; d < df.mNumIndices; ++d) {
313
0
                    df.mIndices[d] = newIndex;
314
0
                    const unsigned int newIdx = pf.mIndices[d];
315
0
                    if (newIdx >= sourceMesh->mPositions.size()) {
316
0
                        continue;
317
0
                    }
318
319
0
                    orgPoints[newIndex] = pf.mIndices[d];
320
321
                    // Position
322
0
                    mesh->mVertices[newIndex] = sourceMesh->mPositions[pf.mIndices[d]];
323
                    // Normal, if present
324
0
                    if (mesh->HasNormals()) {
325
0
                        if (sourceMesh->mNormFaces[f].mIndices.size() > d) {
326
0
                            const size_t idx(sourceMesh->mNormFaces[f].mIndices[d]);
327
0
                            if (idx < sourceMesh->mNormals.size()) {
328
0
                                mesh->mNormals[newIndex] = sourceMesh->mNormals[idx];
329
0
                            }
330
0
                        }
331
0
                    }
332
333
                    // texture coord sets
334
0
                    for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e) {
335
0
                        if (mesh->HasTextureCoords(e)) {
336
0
                            aiVector2D tex = sourceMesh->mTexCoords[e][pf.mIndices[d]];
337
0
                            mesh->mTextureCoords[e][newIndex] = aiVector3D(tex.x, 1.0f - tex.y, 0.0f);
338
0
                        }
339
0
                    }
340
                    // vertex color sets
341
0
                    for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e) {
342
0
                        if (mesh->HasVertexColors(e)) {
343
0
                            mesh->mColors[e][newIndex] = sourceMesh->mColors[e][pf.mIndices[d]];
344
0
                        }
345
0
                    }
346
347
0
                    newIndex++;
348
0
                }
349
0
            }
350
351
            // there should be as much new vertices as we calculated before
352
0
            ai_assert(newIndex == numVertices);
353
354
            // convert all bones of the source mesh which influence vertices in this newly created mesh
355
0
            const std::vector<XFile::Bone> &bones = sourceMesh->mBones;
356
0
            std::vector<aiBone *> newBones;
357
0
            for (unsigned int c = 0; c < bones.size(); ++c) {
358
0
                const XFile::Bone &obone = bones[c];
359
                // set up a vertex-linear array of the weights for quick searching if a bone influences a vertex
360
0
                std::vector<ai_real> oldWeights(sourceMesh->mPositions.size(), 0.0);
361
0
                for (unsigned int d = 0; d < obone.mWeights.size(); ++d) {
362
                    // TODO  The conditional against boneIdx which was added in commit f844c33
363
                    // TODO      (https://github.com/assimp/assimp/commit/f844c3397d7726477ab0fdca8efd3df56c18366b)
364
                    // TODO  causes massive breakage as detailed in:
365
                    // TODO      https://github.com/assimp/assimp/issues/5332
366
                    // TODO  In cases like this unit tests are less useful, since the model still has
367
                    // TODO  meshes, textures, animations etc. and asserts against these values may pass;
368
                    // TODO  when touching importer code, it is crucial that developers also run manual, visual
369
                    // TODO  checks to ensure there's no obvious breakage _before_ commiting to main branch
370
                    //const unsigned int boneIdx = obone.mWeights[d].mVertex;
371
                    //if (boneIdx < obone.mWeights.size()) {
372
0
                        oldWeights[obone.mWeights[d].mVertex] = obone.mWeights[d].mWeight;
373
                    //}
374
0
                }
375
376
                // collect all vertex weights that influence a vertex in the new mesh
377
0
                std::vector<aiVertexWeight> newWeights;
378
0
                newWeights.reserve(numVertices);
379
0
                for (unsigned int d = 0; d < orgPoints.size(); ++d) {
380
                    // does the new vertex stem from an old vertex which was influenced by this bone?
381
0
                    ai_real w = oldWeights[orgPoints[d]];
382
0
                    if (w > 0.0) {
383
0
                        newWeights.emplace_back(d, w);
384
0
                    }
385
0
                }
386
387
                // if the bone has no weights in the newly created mesh, ignore it
388
0
                if (newWeights.empty()) {
389
0
                    continue;
390
0
                }
391
392
                // create
393
0
                aiBone *nbone = new aiBone;
394
0
                newBones.push_back(nbone);
395
                // copy name and matrix
396
0
                nbone->mName.Set(obone.mName);
397
0
                nbone->mOffsetMatrix = obone.mOffsetMatrix;
398
0
                nbone->mNumWeights = (unsigned int)newWeights.size();
399
0
                nbone->mWeights = new aiVertexWeight[nbone->mNumWeights];
400
0
                for (unsigned int d = 0; d < newWeights.size(); ++d) {
401
0
                    nbone->mWeights[d] = newWeights[d];
402
0
                }
403
0
            }
404
405
            // store the bones in the mesh
406
0
            mesh->mNumBones = (unsigned int)newBones.size();
407
0
            if (!newBones.empty()) {
408
0
                mesh->mBones = new aiBone *[mesh->mNumBones];
409
0
                std::copy(newBones.begin(), newBones.end(), mesh->mBones);
410
0
            }
411
0
        }
412
0
    }
413
414
    // reallocate scene mesh array to be large enough
415
0
    aiMesh **prevArray = pScene->mMeshes;
416
0
    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes + meshes.size()];
417
0
    if (prevArray) {
418
0
        memcpy(pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof(aiMesh *));
419
0
        delete[] prevArray;
420
0
    }
421
422
    // allocate mesh index array in the node
423
0
    pNode->mNumMeshes = (unsigned int)meshes.size();
424
0
    pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
425
426
    // store all meshes in the mesh library of the scene and store their indices in the node
427
0
    for (unsigned int a = 0; a < meshes.size(); a++) {
428
0
        pScene->mMeshes[pScene->mNumMeshes] = meshes[a];
429
0
        pNode->mMeshes[a] = pScene->mNumMeshes;
430
0
        pScene->mNumMeshes++;
431
0
    }
432
0
}
433
434
// ------------------------------------------------------------------------------------------------
435
// Converts the animations from the given imported data and creates them in the scene.
436
0
void XFileImporter::CreateAnimations(aiScene *pScene, const XFile::Scene *pData) {
437
0
    std::vector<aiAnimation *> newAnims;
438
439
0
    for (unsigned int a = 0; a < pData->mAnims.size(); ++a) {
440
0
        const XFile::Animation *anim = pData->mAnims[a];
441
        // some exporters mock me with empty animation tags.
442
0
        if (anim->mAnims.empty()) {
443
0
            continue;
444
0
        }
445
446
        // create a new animation to hold the data
447
0
        aiAnimation *nanim = new aiAnimation;
448
0
        newAnims.push_back(nanim);
449
0
        nanim->mName.Set(anim->mName);
450
        // duration will be determined by the maximum length
451
0
        nanim->mDuration = 0;
452
0
        nanim->mTicksPerSecond = pData->mAnimTicksPerSecond;
453
0
        nanim->mNumChannels = (unsigned int)anim->mAnims.size();
454
0
        nanim->mChannels = new aiNodeAnim *[nanim->mNumChannels];
455
456
0
        for (unsigned int b = 0; b < anim->mAnims.size(); ++b) {
457
0
            const XFile::AnimBone *bone = anim->mAnims[b];
458
0
            aiNodeAnim *nbone = new aiNodeAnim;
459
0
            nbone->mNodeName.Set(bone->mBoneName);
460
0
            nanim->mChannels[b] = nbone;
461
462
            // key-frames are given as combined transformation matrix keys
463
0
            if (!bone->mTrafoKeys.empty()) {
464
0
                nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size();
465
0
                nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys];
466
0
                nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size();
467
0
                nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys];
468
0
                nbone->mNumScalingKeys = (unsigned int)bone->mTrafoKeys.size();
469
0
                nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys];
470
471
0
                for (unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c) {
472
                    // deconstruct each matrix into separate position, rotation and scaling
473
0
                    double time = bone->mTrafoKeys[c].mTime;
474
0
                    aiMatrix4x4 trafo = bone->mTrafoKeys[c].mMatrix;
475
476
                    // extract position
477
0
                    aiVector3D pos(trafo.a4, trafo.b4, trafo.c4);
478
479
0
                    nbone->mPositionKeys[c].mTime = time;
480
0
                    nbone->mPositionKeys[c].mValue = pos;
481
482
                    // extract scaling
483
0
                    aiVector3D scale;
484
0
                    scale.x = aiVector3D(trafo.a1, trafo.b1, trafo.c1).Length();
485
0
                    scale.y = aiVector3D(trafo.a2, trafo.b2, trafo.c2).Length();
486
0
                    scale.z = aiVector3D(trafo.a3, trafo.b3, trafo.c3).Length();
487
0
                    nbone->mScalingKeys[c].mTime = time;
488
0
                    nbone->mScalingKeys[c].mValue = scale;
489
490
                    // reconstruct rotation matrix without scaling
491
0
                    aiMatrix3x3 rotmat(
492
0
                            trafo.a1 / scale.x, trafo.a2 / scale.y, trafo.a3 / scale.z,
493
0
                            trafo.b1 / scale.x, trafo.b2 / scale.y, trafo.b3 / scale.z,
494
0
                            trafo.c1 / scale.x, trafo.c2 / scale.y, trafo.c3 / scale.z);
495
496
                    // and convert it into a quaternion
497
0
                    nbone->mRotationKeys[c].mTime = time;
498
0
                    nbone->mRotationKeys[c].mValue = aiQuaternion(rotmat);
499
0
                }
500
501
                // longest lasting key sequence determines duration
502
0
                nanim->mDuration = std::max(nanim->mDuration, bone->mTrafoKeys.back().mTime);
503
0
            } else {
504
                // separate key sequences for position, rotation, scaling
505
0
                nbone->mNumPositionKeys = (unsigned int)bone->mPosKeys.size();
506
0
                if (nbone->mNumPositionKeys != 0) {
507
0
                    nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys];
508
0
                    for (unsigned int c = 0; c < nbone->mNumPositionKeys; ++c) {
509
0
                        aiVector3D pos = bone->mPosKeys[c].mValue;
510
511
0
                        nbone->mPositionKeys[c].mTime = bone->mPosKeys[c].mTime;
512
0
                        nbone->mPositionKeys[c].mValue = pos;
513
0
                    }
514
0
                }
515
516
                // rotation
517
0
                nbone->mNumRotationKeys = (unsigned int)bone->mRotKeys.size();
518
0
                if (nbone->mNumRotationKeys != 0) {
519
0
                    nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys];
520
0
                    for (unsigned int c = 0; c < nbone->mNumRotationKeys; ++c) {
521
0
                        aiMatrix3x3 rotmat = bone->mRotKeys[c].mValue.GetMatrix();
522
523
0
                        nbone->mRotationKeys[c].mTime = bone->mRotKeys[c].mTime;
524
0
                        nbone->mRotationKeys[c].mValue = aiQuaternion(rotmat);
525
0
                        nbone->mRotationKeys[c].mValue.w *= -1.0f; // needs quat inversion
526
0
                    }
527
0
                }
528
529
                // scaling
530
0
                nbone->mNumScalingKeys = (unsigned int)bone->mScaleKeys.size();
531
0
                if (nbone->mNumScalingKeys != 0) {
532
0
                    nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys];
533
0
                    for (unsigned int c = 0; c < nbone->mNumScalingKeys; c++)
534
0
                        nbone->mScalingKeys[c] = bone->mScaleKeys[c];
535
0
                }
536
537
                // longest lasting key sequence determines duration
538
0
                if (bone->mPosKeys.size() > 0)
539
0
                    nanim->mDuration = std::max(nanim->mDuration, bone->mPosKeys.back().mTime);
540
0
                if (bone->mRotKeys.size() > 0)
541
0
                    nanim->mDuration = std::max(nanim->mDuration, bone->mRotKeys.back().mTime);
542
0
                if (bone->mScaleKeys.size() > 0)
543
0
                    nanim->mDuration = std::max(nanim->mDuration, bone->mScaleKeys.back().mTime);
544
0
            }
545
0
        }
546
0
    }
547
548
    // store all converted animations in the scene
549
0
    if (newAnims.size() > 0) {
550
0
        pScene->mNumAnimations = (unsigned int)newAnims.size();
551
0
        pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations];
552
0
        for (unsigned int a = 0; a < newAnims.size(); a++)
553
0
            pScene->mAnimations[a] = newAnims[a];
554
0
    }
555
0
}
556
557
// ------------------------------------------------------------------------------------------------
558
// Converts all materials in the given array and stores them in the scene's material list.
559
0
void XFileImporter::ConvertMaterials(aiScene *pScene, std::vector<XFile::Material> &pMaterials) {
560
    // count the non-referrer materials in the array
561
0
    unsigned int numNewMaterials(0);
562
0
    for (unsigned int a = 0; a < pMaterials.size(); ++a) {
563
0
        if (!pMaterials[a].mIsReference) {
564
0
            ++numNewMaterials;
565
0
        }
566
0
    }
567
568
    // resize the scene's material list to offer enough space for the new materials
569
0
    if (numNewMaterials > 0) {
570
0
        aiMaterial **prevMats = pScene->mMaterials;
571
0
        pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials + numNewMaterials];
572
0
        if (nullptr != prevMats) {
573
0
            ::memcpy(pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof(aiMaterial *));
574
0
            delete[] prevMats;
575
0
        }
576
0
    }
577
578
    // convert all the materials given in the array
579
0
    for (unsigned int a = 0; a < pMaterials.size(); ++a) {
580
0
        XFile::Material &oldMat = pMaterials[a];
581
0
        if (oldMat.mIsReference) {
582
            // find the material it refers to by name, and store its index
583
0
            for (size_t b = 0; b < pScene->mNumMaterials; ++b) {
584
0
                aiString name;
585
0
                pScene->mMaterials[b]->Get(AI_MATKEY_NAME, name);
586
0
                if (strcmp(name.C_Str(), oldMat.mName.data()) == 0) {
587
0
                    oldMat.sceneIndex = b;
588
0
                    break;
589
0
                }
590
0
            }
591
592
0
            if (oldMat.sceneIndex == SIZE_MAX) {
593
0
                ASSIMP_LOG_WARN("Could not resolve global material reference \"", oldMat.mName, "\"");
594
0
                oldMat.sceneIndex = 0;
595
0
            }
596
597
0
            continue;
598
0
        }
599
600
0
        aiMaterial *mat = new aiMaterial;
601
0
        aiString name;
602
0
        name.Set(oldMat.mName);
603
0
        mat->AddProperty(&name, AI_MATKEY_NAME);
604
605
        // Shading model: hard-coded to PHONG, there is no such information in an XFile
606
        // FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix
607
        // for some models in the SDK (e.g. good old tiny.x)
608
0
        int shadeMode = (int)oldMat.mSpecularExponent == 0.0f ? aiShadingMode_Gouraud : aiShadingMode_Phong;
609
610
0
        mat->AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL);
611
        // material colours
612
        // Unclear: there's no ambient colour, but emissive. What to put for ambient?
613
        // Probably nothing at all, let the user select a suitable default.
614
0
        mat->AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
615
0
        mat->AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
616
0
        mat->AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
617
0
        mat->AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
618
619
        // texture, if there is one
620
0
        if (1 == oldMat.mTextures.size()) {
621
0
            const XFile::TexEntry &otex = oldMat.mTextures.back();
622
0
            if (otex.mName.length()) {
623
                // if there is only one texture assume it contains the diffuse color
624
0
                aiString tex(otex.mName);
625
0
                if (otex.mIsNormalMap) {
626
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(0));
627
0
                } else {
628
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0));
629
0
                }
630
0
            }
631
0
        } else {
632
            // Otherwise ... try to search for typical strings in the
633
            // texture's file name like 'bump' or 'diffuse'
634
0
            unsigned int iHM = 0, iNM = 0, iDM = 0, iSM = 0, iAM = 0, iEM = 0;
635
0
            for (unsigned int b = 0; b < oldMat.mTextures.size(); ++b) {
636
0
                const XFile::TexEntry &otex = oldMat.mTextures[b];
637
0
                std::string sz = otex.mName;
638
0
                if (!sz.length()) {
639
0
                    continue;
640
0
                }
641
642
                // find the file name
643
0
                std::string::size_type s = sz.find_last_of("\\/");
644
0
                if (std::string::npos == s) {
645
0
                    s = 0;
646
0
                }
647
648
                // cut off the file extension
649
0
                std::string::size_type sExt = sz.find_last_of('.');
650
0
                if (std::string::npos != sExt) {
651
0
                    sz[sExt] = '\0';
652
0
                }
653
654
                // convert to lower case for easier comparison
655
0
                for (unsigned int c = 0; c < sz.length(); ++c) {
656
0
                    sz[c] = (char)tolower((unsigned char)sz[c]);
657
0
                }
658
659
                // Place texture filename property under the corresponding name
660
0
                aiString tex(oldMat.mTextures[b].mName);
661
662
                // bump map
663
0
                if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s)) {
664
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++));
665
0
                } else if (otex.mIsNormalMap || std::string::npos != sz.find("normal", s) || std::string::npos != sz.find("nm", s)) {
666
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(iNM++));
667
0
                } else if (std::string::npos != sz.find("spec", s) || std::string::npos != sz.find("glanz", s)) {
668
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++));
669
0
                } else if (std::string::npos != sz.find("ambi", s) || std::string::npos != sz.find("env", s)) {
670
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++));
671
0
                } else if (std::string::npos != sz.find("emissive", s) || std::string::npos != sz.find("self", s)) {
672
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++));
673
0
                } else {
674
                    // Assume it is a diffuse texture
675
0
                    mat->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++));
676
0
                }
677
0
            }
678
0
        }
679
680
0
        pScene->mMaterials[pScene->mNumMaterials] = mat;
681
0
        oldMat.sceneIndex = pScene->mNumMaterials;
682
0
        pScene->mNumMaterials++;
683
0
    }
684
0
}
685
686
} // namespace Assimp
687
688
#endif // !! ASSIMP_BUILD_NO_X_IMPORTER