Coverage Report

Created: 2026-04-29 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/MD5/MD5Loader.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  MD5Loader.cpp
43
 *  @brief Implementation of the MD5 importer class
44
 */
45
46
#ifndef ASSIMP_BUILD_NO_MD5_IMPORTER
47
48
// internal headers
49
#include "MD5Loader.h"
50
#include <assimp/MathFunctions.h>
51
#include <assimp/RemoveComments.h>
52
#include <assimp/SkeletonMeshBuilder.h>
53
#include <assimp/StringComparison.h>
54
#include <assimp/fast_atof.h>
55
#include <assimp/importerdesc.h>
56
#include <assimp/scene.h>
57
#include <assimp/DefaultLogger.hpp>
58
#include <assimp/IOSystem.hpp>
59
#include <assimp/Importer.hpp>
60
#include <memory>
61
62
using namespace Assimp;
63
64
// Minimum weight value. Weights inside [-n ... n] are ignored
65
0
#define AI_MD5_WEIGHT_EPSILON Math::getEpsilon<float>()
66
67
static constexpr aiImporterDesc desc = {
68
    "Doom 3 / MD5 Mesh Importer",
69
    "",
70
    "",
71
    "",
72
    aiImporterFlags_SupportBinaryFlavour,
73
    0,
74
    0,
75
    0,
76
    0,
77
    "md5mesh md5camera md5anim"
78
};
79
80
// ------------------------------------------------------------------------------------------------
81
// Constructor to be privately used by Importer
82
MD5Importer::MD5Importer() :
83
38.0k
        mIOHandler(nullptr),
84
        mBuffer(),
85
        mFileSize(),
86
        mLineNumber(),
87
        mScene(),
88
        mHadMD5Mesh(),
89
        mHadMD5Anim(),
90
        mHadMD5Camera(),
91
38.0k
        mCconfigNoAutoLoad(false) {
92
    // empty
93
38.0k
}
94
95
// ------------------------------------------------------------------------------------------------
96
// Returns whether the class can handle the format of the given file.
97
0
bool MD5Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
98
0
    static const char *tokens[] = { "MD5Version" };
99
0
    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
100
0
}
101
102
// ------------------------------------------------------------------------------------------------
103
// Get list of all supported extensions
104
38.0k
const aiImporterDesc *MD5Importer::GetInfo() const {
105
38.0k
    return &desc;
106
38.0k
}
107
108
// ------------------------------------------------------------------------------------------------
109
// Setup import properties
110
0
void MD5Importer::SetupProperties(const Importer *pImp) {
111
    // AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD
112
0
    mCconfigNoAutoLoad = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD, 0));
113
0
}
114
115
// ------------------------------------------------------------------------------------------------
116
// Imports the given file into the given scene structure.
117
0
void MD5Importer::InternReadFile(const std::string &pFile, aiScene *_pScene, IOSystem *pIOHandler) {
118
0
    mIOHandler = pIOHandler;
119
0
    mScene = _pScene;
120
0
    mHadMD5Mesh = mHadMD5Anim = mHadMD5Camera = false;
121
122
    // remove the file extension
123
0
    const std::string::size_type pos = pFile.find_last_of('.');
124
0
    mFile = (std::string::npos == pos ? pFile : pFile.substr(0, pos + 1));
125
126
0
    const std::string extension = GetExtension(pFile);
127
0
    try {
128
0
        if (extension == "md5camera") {
129
0
            LoadMD5CameraFile();
130
0
        } else if (mCconfigNoAutoLoad || extension == "md5anim") {
131
            // determine file extension and process just *one* file
132
0
            if (extension.length() == 0) {
133
0
                throw DeadlyImportError("Failure, need file extension to determine MD5 part type");
134
0
            }
135
0
            if (extension == "md5anim") {
136
0
                LoadMD5AnimFile();
137
0
            } else if (extension == "md5mesh") {
138
0
                LoadMD5MeshFile();
139
0
            }
140
0
        } else {
141
0
            LoadMD5MeshFile();
142
0
            LoadMD5AnimFile();
143
0
        }
144
0
    } catch (...) { // std::exception, Assimp::DeadlyImportError
145
0
        UnloadFileFromMemory();
146
0
        throw;
147
0
    }
148
149
    // make sure we have at least one file
150
0
    if (!mHadMD5Mesh && !mHadMD5Anim && !mHadMD5Camera) {
151
0
        throw DeadlyImportError("Failed to read valid contents out of this MD5* file");
152
0
    }
153
154
    // Now rotate the whole scene 90 degrees around the x axis to match our internal coordinate system
155
0
    mScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f,
156
0
            0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f);
157
158
    // the output scene wouldn't pass the validation without this flag
159
0
    if (!mHadMD5Mesh) {
160
0
        mScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
161
0
    }
162
163
    // clean the instance -- the BaseImporter instance may be reused later.
164
0
    UnloadFileFromMemory();
165
0
}
166
167
// ------------------------------------------------------------------------------------------------
168
// Load a file into a memory buffer
169
0
void MD5Importer::LoadFileIntoMemory(IOStream *file) {
170
    // unload the previous buffer, if any
171
0
    UnloadFileFromMemory();
172
173
0
    ai_assert(nullptr != file);
174
0
    mFileSize = (unsigned int)file->FileSize();
175
0
    ai_assert(mFileSize);
176
177
    // allocate storage and copy the contents of the file to a memory buffer
178
0
    mBuffer = new char[mFileSize + 1];
179
0
    file->Read((void *)mBuffer, 1, mFileSize);
180
0
    mLineNumber = 1;
181
182
    // append a terminal 0
183
0
    mBuffer[mFileSize] = '\0';
184
185
    // now remove all line comments from the file
186
0
    CommentRemover::RemoveLineComments("//", mBuffer, ' ');
187
0
}
188
189
// ------------------------------------------------------------------------------------------------
190
// Unload the current memory buffer
191
0
void MD5Importer::UnloadFileFromMemory() {
192
    // delete the file buffer
193
0
    delete[] mBuffer;
194
0
    mBuffer = nullptr;
195
0
    mFileSize = 0;
196
0
}
197
198
// ------------------------------------------------------------------------------------------------
199
// Build unique vertices
200
0
void MD5Importer::MakeDataUnique(MD5::MeshDesc &meshSrc) {
201
0
    std::vector<bool> abHad(meshSrc.mVertices.size(), false);
202
203
    // allocate enough storage to keep the output structures
204
0
    const unsigned int iNewNum = static_cast<unsigned int>(meshSrc.mFaces.size() * 3);
205
0
    unsigned int iNewIndex = static_cast<unsigned int>(meshSrc.mVertices.size());
206
0
    meshSrc.mVertices.resize(iNewNum);
207
208
    // try to guess how much storage we'll need for new weights
209
0
    const float fWeightsPerVert = meshSrc.mWeights.size() / (float)iNewIndex;
210
0
    const unsigned int guess = (unsigned int)(fWeightsPerVert * iNewNum);
211
0
    meshSrc.mWeights.reserve(guess + (guess >> 3)); // + 12.5% as buffer
212
213
0
    for (FaceArray::const_iterator iter = meshSrc.mFaces.begin(), iterEnd = meshSrc.mFaces.end(); iter != iterEnd; ++iter) {
214
0
        const aiFace &face = *iter;
215
0
        for (unsigned int i = 0; i < 3; ++i) {
216
0
            if (face.mIndices[0] >= meshSrc.mVertices.size()) {
217
0
                throw DeadlyImportError("MD5MESH: Invalid vertex index");
218
0
            }
219
220
0
            if (abHad[face.mIndices[i]]) {
221
                // generate a new vertex
222
0
                meshSrc.mVertices[iNewIndex] = meshSrc.mVertices[face.mIndices[i]];
223
0
                face.mIndices[i] = iNewIndex++;
224
0
            } else
225
0
                abHad[face.mIndices[i]] = true;
226
0
        }
227
        // swap face order
228
0
        std::swap(face.mIndices[0], face.mIndices[2]);
229
0
    }
230
0
}
231
232
// ------------------------------------------------------------------------------------------------
233
// Recursive node graph construction from a MD5MESH
234
0
void MD5Importer::AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneArray &bones) {
235
0
    ai_assert(nullptr != piParent);
236
0
    ai_assert(!piParent->mNumChildren);
237
238
    // First find out how many children we'll have
239
0
    for (int i = 0; i < (int)bones.size(); ++i) {
240
0
        if (iParentID != i && bones[i].mParentIndex == iParentID) {
241
0
            ++piParent->mNumChildren;
242
0
        }
243
0
    }
244
0
    if (piParent->mNumChildren) {
245
0
        piParent->mChildren = new aiNode *[piParent->mNumChildren];
246
0
        for (int i = 0; i < (int)bones.size(); ++i) {
247
            // (avoid infinite recursion)
248
0
            if (iParentID != i && bones[i].mParentIndex == iParentID) {
249
0
                aiNode *pc;
250
                // setup a new node
251
0
                *piParent->mChildren++ = pc = new aiNode();
252
0
                pc->mName = aiString(bones[i].mName);
253
0
                pc->mParent = piParent;
254
255
                // get the transformation matrix from rotation and translational components
256
0
                aiQuaternion quat;
257
0
                MD5::ConvertQuaternion(bones[i].mRotationQuat, quat);
258
259
0
                bones[i].mTransform = aiMatrix4x4(quat.GetMatrix());
260
0
                bones[i].mTransform.a4 = bones[i].mPositionXYZ.x;
261
0
                bones[i].mTransform.b4 = bones[i].mPositionXYZ.y;
262
0
                bones[i].mTransform.c4 = bones[i].mPositionXYZ.z;
263
264
                // store it for later use
265
0
                pc->mTransformation = bones[i].mInvTransform = bones[i].mTransform;
266
0
                bones[i].mInvTransform.Inverse();
267
268
                // the transformations for each bone are absolute, so we need to multiply them
269
                // with the inverse of the absolute matrix of the parent joint
270
0
                if (-1 != iParentID) {
271
0
                    pc->mTransformation = bones[iParentID].mInvTransform * pc->mTransformation;
272
0
                }
273
274
                // add children to this node, too
275
0
                AttachChilds_Mesh(i, pc, bones);
276
0
            }
277
0
        }
278
        // undo offset computations
279
0
        piParent->mChildren -= piParent->mNumChildren;
280
0
    }
281
0
}
282
283
// ------------------------------------------------------------------------------------------------
284
// Recursive node graph construction from a MD5ANIM
285
0
void MD5Importer::AttachChilds_Anim(int iParentID, aiNode *piParent, AnimBoneArray &bones, const aiNodeAnim **node_anims) {
286
0
    ai_assert(nullptr != piParent);
287
0
    ai_assert(!piParent->mNumChildren);
288
289
    // First find out how many children we'll have
290
0
    for (int i = 0; i < (int)bones.size(); ++i) {
291
0
        if (iParentID != i && bones[i].mParentIndex == iParentID) {
292
0
            ++piParent->mNumChildren;
293
0
        }
294
0
    }
295
0
    if (piParent->mNumChildren) {
296
0
        piParent->mChildren = new aiNode *[piParent->mNumChildren];
297
0
        for (int i = 0; i < (int)bones.size(); ++i) {
298
            // (avoid infinite recursion)
299
0
            if (iParentID != i && bones[i].mParentIndex == iParentID) {
300
0
                aiNode *pc;
301
                // setup a new node
302
0
                *piParent->mChildren++ = pc = new aiNode();
303
0
                pc->mName = aiString(bones[i].mName);
304
0
                pc->mParent = piParent;
305
306
                // get the corresponding animation channel and its first frame
307
0
                const aiNodeAnim **cur = node_anims;
308
0
                while ((**cur).mNodeName != pc->mName)
309
0
                    ++cur;
310
311
0
                aiMatrix4x4::Translation((**cur).mPositionKeys[0].mValue, pc->mTransformation);
312
0
                pc->mTransformation = pc->mTransformation * aiMatrix4x4((**cur).mRotationKeys[0].mValue.GetMatrix());
313
314
                // add children to this node, too
315
0
                AttachChilds_Anim(i, pc, bones, node_anims);
316
0
            }
317
0
        }
318
        // undo offset computations
319
0
        piParent->mChildren -= piParent->mNumChildren;
320
0
    }
321
0
}
322
323
// ------------------------------------------------------------------------------------------------
324
// Load a MD5MESH file
325
0
void MD5Importer::LoadMD5MeshFile() {
326
0
    std::string filename = mFile + "md5mesh";
327
0
    std::unique_ptr<IOStream> file(mIOHandler->Open(filename, "rb"));
328
329
    // Check whether we can read from the file
330
0
    if (file == nullptr || !file->FileSize()) {
331
0
        ASSIMP_LOG_WARN("Failed to access MD5MESH file: ", filename);
332
0
        return;
333
0
    }
334
0
    mHadMD5Mesh = true;
335
0
    LoadFileIntoMemory(file.get());
336
337
    // now construct a parser and parse the file
338
0
    MD5::MD5Parser parser(mBuffer, mFileSize);
339
340
    // load the mesh information from it
341
0
    MD5::MD5MeshParser meshParser(parser.mSections);
342
343
    // create the bone hierarchy - first the root node and dummy nodes for all meshes
344
0
    mScene->mRootNode = new aiNode("<MD5_Root>");
345
0
    mScene->mRootNode->mNumChildren = 2;
346
0
    mScene->mRootNode->mChildren = new aiNode *[2];
347
348
    // build the hierarchy from the MD5MESH file
349
0
    aiNode *pcNode = mScene->mRootNode->mChildren[1] = new aiNode();
350
0
    pcNode->mName.Set("<MD5_Hierarchy>");
351
0
    pcNode->mParent = mScene->mRootNode;
352
0
    AttachChilds_Mesh(-1, pcNode, meshParser.mJoints);
353
354
0
    pcNode = mScene->mRootNode->mChildren[0] = new aiNode();
355
0
    pcNode->mName.Set("<MD5_Mesh>");
356
0
    pcNode->mParent = mScene->mRootNode;
357
358
#if 0
359
    if (pScene->mRootNode->mChildren[1]->mNumChildren) /* start at the right hierarchy level */
360
        SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[1]->mChildren[0]);
361
#else
362
363
    // FIX: MD5 files exported from Blender can have empty meshes
364
0
    unsigned int numMaterials = 0;
365
0
    for (std::vector<MD5::MeshDesc>::const_iterator it = meshParser.mMeshes.begin(), end = meshParser.mMeshes.end(); it != end; ++it) {
366
0
        if (!(*it).mFaces.empty() && !(*it).mVertices.empty()) {
367
0
            ++numMaterials;
368
0
        }
369
0
    }
370
371
    // generate all meshes
372
0
    mScene->mMeshes = new aiMesh *[numMaterials];
373
0
    mScene->mMaterials = new aiMaterial *[numMaterials];
374
375
    //  storage for node mesh indices
376
0
    pcNode->mNumMeshes = numMaterials;
377
0
    pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
378
0
    for (unsigned int m = 0; m < pcNode->mNumMeshes; ++m) {
379
0
        pcNode->mMeshes[m] = m;
380
0
    }
381
382
0
    unsigned int n = 0;
383
0
    for (std::vector<MD5::MeshDesc>::iterator it = meshParser.mMeshes.begin(), end = meshParser.mMeshes.end(); it != end; ++it) {
384
0
        MD5::MeshDesc &meshSrc = *it;
385
0
        if (meshSrc.mFaces.empty() || meshSrc.mVertices.empty()) {
386
0
            continue;
387
0
        }
388
389
0
        aiMesh* mesh = new aiMesh();
390
0
        mScene->mMeshes[n] = mesh;
391
0
        ++mScene->mNumMeshes;
392
393
0
        mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
394
395
        // generate unique vertices in our internal verbose format
396
0
        MakeDataUnique(meshSrc);
397
398
0
        std::string name(meshSrc.mShader.C_Str());
399
0
        name += ".msh";
400
0
        mesh->mName = name;
401
0
        mesh->mNumVertices = (unsigned int)meshSrc.mVertices.size();
402
0
        mesh->mVertices = new aiVector3D[mesh->mNumVertices];
403
0
        mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
404
0
        mesh->mNumUVComponents[0] = 2;
405
406
        // copy texture coordinates
407
0
        aiVector3D *pv = mesh->mTextureCoords[0];
408
0
        for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) {
409
0
            pv->x = (*iter).mUV.x;
410
0
            pv->y = 1.0f - (*iter).mUV.y; // D3D to OpenGL
411
0
            pv->z = 0.0f;
412
0
        }
413
414
        // sort all bone weights - per bone
415
0
        unsigned int *piCount = new unsigned int[meshParser.mJoints.size()];
416
0
        ::memset(piCount, 0, sizeof(unsigned int) * meshParser.mJoints.size());
417
418
0
        for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) {
419
0
            for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) {
420
0
                MD5::WeightDesc &weightDesc = meshSrc.mWeights[w];
421
                /* FIX for some invalid exporters */
422
0
                if (!(weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON)) {
423
0
                    ++piCount[weightDesc.mBone];
424
0
                }
425
0
            }
426
0
        }
427
428
        // check how many we will need
429
0
        for (unsigned int p = 0; p < meshParser.mJoints.size(); ++p) {
430
0
            if (piCount[p]) mesh->mNumBones++;
431
0
        }
432
433
        // just for safety
434
0
        if (mesh->mNumBones) {
435
0
            mesh->mBones = new aiBone *[mesh->mNumBones];
436
0
            for (unsigned int q = 0, h = 0; q < meshParser.mJoints.size(); ++q) {
437
0
                if (!piCount[q]) continue;
438
0
                aiBone *p = mesh->mBones[h] = new aiBone();
439
0
                p->mNumWeights = piCount[q];
440
0
                p->mWeights = new aiVertexWeight[p->mNumWeights];
441
0
                p->mName = aiString(meshParser.mJoints[q].mName);
442
0
                p->mOffsetMatrix = meshParser.mJoints[q].mInvTransform;
443
444
                // store the index for later use
445
0
                MD5::BoneDesc &boneSrc = meshParser.mJoints[q];
446
0
                boneSrc.mMap = h++;
447
448
                // compute w-component of quaternion
449
0
                MD5::ConvertQuaternion(boneSrc.mRotationQuat, boneSrc.mRotationQuatConverted);
450
0
            }
451
452
0
            pv = mesh->mVertices;
453
0
            for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) {
454
                // compute the final vertex position from all single weights
455
0
                *pv = aiVector3D();
456
457
                // there are models which have weights which don't sum to 1 ...
458
0
                ai_real fSum = 0.0;
459
0
                for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) {
460
0
                    fSum += meshSrc.mWeights[w].mWeight;
461
0
                }
462
0
                if (!fSum) {
463
0
                    ASSIMP_LOG_ERROR("MD5MESH: The sum of all vertex bone weights is 0");
464
0
                    continue;
465
0
                }
466
467
                // process bone weights
468
0
                for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) {
469
0
                    if (w >= meshSrc.mWeights.size()) {
470
0
                        throw DeadlyImportError("MD5MESH: Invalid weight index");
471
0
                    }
472
473
0
                    MD5::WeightDesc &weightDesc = meshSrc.mWeights[w];
474
0
                    if (weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON) {
475
0
                        continue;
476
0
                    }
477
478
0
                    const ai_real fNewWeight = weightDesc.mWeight / fSum;
479
480
                    // transform the local position into worldspace
481
0
                    MD5::BoneDesc &boneSrc = meshParser.mJoints[weightDesc.mBone];
482
0
                    const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate(weightDesc.vOffsetPosition);
483
484
                    // use the original weight to compute the vertex position
485
                    // (some MD5s seem to depend on the invalid weight values ...)
486
0
                    *pv += ((boneSrc.mPositionXYZ + v) * (ai_real)weightDesc.mWeight);
487
488
0
                    aiBone *bone = mesh->mBones[boneSrc.mMap];
489
0
                    *bone->mWeights++ = aiVertexWeight((unsigned int)(pv - mesh->mVertices), fNewWeight);
490
0
                }
491
0
            }
492
493
            // undo our nice offset tricks ...
494
0
            for (unsigned int p = 0; p < mesh->mNumBones; ++p) {
495
0
                mesh->mBones[p]->mWeights -= mesh->mBones[p]->mNumWeights;
496
0
            }
497
0
        }
498
499
0
        delete[] piCount;
500
501
        // now setup all faces - we can directly copy the list
502
        // (however, take care that the aiFace destructor doesn't delete the mIndices array)
503
0
        mesh->mNumFaces = (unsigned int)meshSrc.mFaces.size();
504
0
        mesh->mFaces = new aiFace[mesh->mNumFaces];
505
0
        for (unsigned int c = 0; c < mesh->mNumFaces; ++c) {
506
0
            mesh->mFaces[c].mNumIndices = 3;
507
0
            mesh->mFaces[c].mIndices = meshSrc.mFaces[c].mIndices;
508
0
            meshSrc.mFaces[c].mIndices = nullptr;
509
0
        }
510
511
        // generate a material for the mesh
512
0
        aiMaterial *mat = new aiMaterial();
513
0
        mScene->mMaterials[n] = mat;
514
0
        ++mScene->mNumMaterials;
515
516
        // insert the typical doom3 textures:
517
        // nnn_local.tga  - normal map
518
        // nnn_h.tga      - height map
519
        // nnn_s.tga      - specular map
520
        // nnn_d.tga      - diffuse map
521
0
        if (meshSrc.mShader.length && !strchr(meshSrc.mShader.data, '.')) {
522
523
0
            aiString temp(meshSrc.mShader);
524
0
            temp.Append("_local.tga");
525
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_NORMALS(0));
526
527
0
            temp = aiString(meshSrc.mShader);
528
0
            temp.Append("_s.tga");
529
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_SPECULAR(0));
530
531
0
            temp = aiString(meshSrc.mShader);
532
0
            temp.Append("_d.tga");
533
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_DIFFUSE(0));
534
535
0
            temp = aiString(meshSrc.mShader);
536
0
            temp.Append("_h.tga");
537
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_HEIGHT(0));
538
539
            // set this also as material name
540
0
            mat->AddProperty(&meshSrc.mShader, AI_MATKEY_NAME);
541
0
        } else {
542
0
            mat->AddProperty(&meshSrc.mShader, AI_MATKEY_TEXTURE_DIFFUSE(0));
543
0
        }
544
0
        mesh->mMaterialIndex = n++;
545
0
    }
546
0
#endif
547
0
}
548
549
// ------------------------------------------------------------------------------------------------
550
// Load an MD5ANIM file
551
0
void MD5Importer::LoadMD5AnimFile() {
552
0
    std::string pFile = mFile + "md5anim";
553
0
    std::unique_ptr<IOStream> file(mIOHandler->Open(pFile, "rb"));
554
555
    // Check whether we can read from the file
556
0
    if (!file || !file->FileSize()) {
557
0
        ASSIMP_LOG_WARN("Failed to read MD5ANIM file: ", pFile);
558
0
        return;
559
0
    }
560
561
0
    LoadFileIntoMemory(file.get());
562
563
    // parse the basic file structure
564
0
    MD5::MD5Parser parser(mBuffer, mFileSize);
565
566
    // load the animation information from the parse tree
567
0
    MD5::MD5AnimParser animParser(parser.mSections);
568
569
    // generate and fill the output animation
570
0
    if (animParser.mAnimatedBones.empty() || animParser.mFrames.empty() ||
571
0
            animParser.mBaseFrames.size() != animParser.mAnimatedBones.size()) {
572
0
        ASSIMP_LOG_ERROR("MD5ANIM: No frames or animated bones loaded");
573
0
    } else {
574
0
        mHadMD5Anim = true;
575
576
0
        mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations = 1];
577
0
        aiAnimation *anim = mScene->mAnimations[0] = new aiAnimation();
578
0
        anim->mNumChannels = (unsigned int)animParser.mAnimatedBones.size();
579
0
        anim->mChannels = new aiNodeAnim *[anim->mNumChannels];
580
0
        for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
581
0
            aiNodeAnim *node = anim->mChannels[i] = new aiNodeAnim();
582
0
            node->mNodeName = aiString(animParser.mAnimatedBones[i].mName);
583
584
            // allocate storage for the keyframes
585
0
            node->mPositionKeys = new aiVectorKey[animParser.mFrames.size()];
586
0
            node->mRotationKeys = new aiQuatKey[animParser.mFrames.size()];
587
0
        }
588
589
        // 1 tick == 1 frame
590
0
        anim->mTicksPerSecond = animParser.fFrameRate;
591
592
0
        for (FrameArray::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end(); iter != iterEnd; ++iter) {
593
0
            double dTime = (double)(*iter).iIndex;
594
0
            aiNodeAnim **pcAnimNode = anim->mChannels;
595
0
            if (!(*iter).mValues.empty() || iter == animParser.mFrames.begin()) /* be sure we have at least one frame */
596
0
            {
597
                // now process all values in there ... read all joints
598
0
                MD5::BaseFrameDesc *pcBaseFrame = &animParser.mBaseFrames[0];
599
0
                for (AnimBoneArray::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end(); ++iter2,
600
0
                                                  ++pcAnimNode, ++pcBaseFrame) {
601
0
                    if ((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) {
602
603
                        // Allow for empty frames
604
0
                        if ((*iter2).iFlags != 0) {
605
0
                            throw DeadlyImportError("MD5: Keyframe index is out of range");
606
0
                        }
607
0
                        continue;
608
0
                    }
609
0
                    const float *fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex];
610
0
                    aiNodeAnim *pcCurAnimBone = *pcAnimNode;
611
612
0
                    aiVectorKey *vKey = &pcCurAnimBone->mPositionKeys[pcCurAnimBone->mNumPositionKeys++];
613
0
                    aiQuatKey *qKey = &pcCurAnimBone->mRotationKeys[pcCurAnimBone->mNumRotationKeys++];
614
0
                    aiVector3D vTemp;
615
616
                    // translational component
617
0
                    for (unsigned int i = 0; i < 3; ++i) {
618
0
                        if ((*iter2).iFlags & (1u << i)) {
619
0
                            vKey->mValue[i] = *fpCur++;
620
0
                        } else
621
0
                            vKey->mValue[i] = pcBaseFrame->vPositionXYZ[i];
622
0
                    }
623
624
                    // orientation component
625
0
                    for (unsigned int i = 0; i < 3; ++i) {
626
0
                        if ((*iter2).iFlags & (8u << i)) {
627
0
                            vTemp[i] = *fpCur++;
628
0
                        } else
629
0
                            vTemp[i] = pcBaseFrame->vRotationQuat[i];
630
0
                    }
631
632
0
                    MD5::ConvertQuaternion(vTemp, qKey->mValue);
633
0
                    qKey->mTime = vKey->mTime = dTime;
634
0
                }
635
0
            }
636
637
            // compute the duration of the animation
638
0
            anim->mDuration = std::max(dTime, anim->mDuration);
639
0
        }
640
641
        // If we didn't build the hierarchy yet (== we didn't load a MD5MESH),
642
        // construct it now from the data given in the MD5ANIM.
643
0
        if (!mScene->mRootNode) {
644
0
            mScene->mRootNode = new aiNode();
645
0
            mScene->mRootNode->mName.Set("<MD5_Hierarchy>");
646
647
0
            AttachChilds_Anim(-1, mScene->mRootNode, animParser.mAnimatedBones, (const aiNodeAnim **)anim->mChannels);
648
649
            // Call SkeletonMeshBuilder to construct a mesh to represent the shape
650
0
            if (mScene->mRootNode->mNumChildren) {
651
0
                SkeletonMeshBuilder skeleton_maker(mScene, mScene->mRootNode->mChildren[0]);
652
0
            }
653
0
        }
654
0
    }
655
0
}
656
657
// ------------------------------------------------------------------------------------------------
658
// Load an MD5CAMERA file
659
0
void MD5Importer::LoadMD5CameraFile() {
660
0
    std::string pFile = mFile + "md5camera";
661
0
    std::unique_ptr<IOStream> file(mIOHandler->Open(pFile, "rb"));
662
663
    // Check whether we can read from the file
664
0
    if (!file || !file->FileSize()) {
665
0
        throw DeadlyImportError("Failed to read MD5CAMERA file: ", pFile);
666
0
    }
667
0
    mHadMD5Camera = true;
668
0
    LoadFileIntoMemory(file.get());
669
670
    // parse the basic file structure
671
0
    MD5::MD5Parser parser(mBuffer, mFileSize);
672
673
    // load the camera animation data from the parse tree
674
0
    MD5::MD5CameraParser cameraParser(parser.mSections);
675
676
0
    if (cameraParser.frames.empty()) {
677
0
        throw DeadlyImportError("MD5CAMERA: No frames parsed");
678
0
    }
679
680
0
    std::vector<unsigned int> &cuts = cameraParser.cuts;
681
0
    std::vector<MD5::CameraAnimFrameDesc> &frames = cameraParser.frames;
682
683
    // Construct output graph - a simple root with a dummy child.
684
    // The root node performs the coordinate system conversion
685
0
    aiNode *root = mScene->mRootNode = new aiNode("<MD5CameraRoot>");
686
0
    root->mChildren = new aiNode *[root->mNumChildren = 1];
687
0
    root->mChildren[0] = new aiNode("<MD5Camera>");
688
0
    root->mChildren[0]->mParent = root;
689
690
    // ... but with one camera assigned to it
691
0
    mScene->mCameras = new aiCamera *[mScene->mNumCameras = 1];
692
0
    aiCamera *cam = mScene->mCameras[0] = new aiCamera();
693
0
    cam->mName = "<MD5Camera>";
694
695
    // FIXME: Fov is currently set to the first frame's value
696
0
    cam->mHorizontalFOV = AI_DEG_TO_RAD(frames.front().fFOV);
697
698
    // every cut is written to a separate aiAnimation
699
0
    if (!cuts.size()) {
700
0
        cuts.push_back(0);
701
0
        cuts.push_back(static_cast<unsigned int>(frames.size() - 1));
702
0
    } else {
703
0
        cuts.insert(cuts.begin(), 0);
704
705
0
        if (cuts.back() < frames.size() - 1)
706
0
            cuts.push_back(static_cast<unsigned int>(frames.size() - 1));
707
0
    }
708
709
0
    mScene->mNumAnimations = static_cast<unsigned int>(cuts.size() - 1);
710
0
    aiAnimation **tmp = mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations];
711
0
    for (std::vector<unsigned int>::const_iterator it = cuts.begin(); it != cuts.end() - 1; ++it) {
712
713
0
        aiAnimation *anim = *tmp++ = new aiAnimation();
714
0
        anim->mName.length = ::ai_snprintf(anim->mName.data, AI_MAXLEN, "anim%u_from_%u_to_%u", (unsigned int)(it - cuts.begin()), (*it), *(it + 1));
715
716
0
        anim->mTicksPerSecond = cameraParser.fFrameRate;
717
0
        anim->mChannels = new aiNodeAnim *[anim->mNumChannels = 1];
718
0
        aiNodeAnim *nd = anim->mChannels[0] = new aiNodeAnim();
719
0
        nd->mNodeName.Set("<MD5Camera>");
720
721
0
        nd->mNumPositionKeys = nd->mNumRotationKeys = *(it + 1) - (*it);
722
0
        nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
723
0
        nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys];
724
0
        for (unsigned int i = 0; i < nd->mNumPositionKeys; ++i) {
725
726
0
            nd->mPositionKeys[i].mValue = frames[*it + i].vPositionXYZ;
727
0
            MD5::ConvertQuaternion(frames[*it + i].vRotationQuat, nd->mRotationKeys[i].mValue);
728
0
            nd->mRotationKeys[i].mTime = nd->mPositionKeys[i].mTime = *it + i;
729
0
        }
730
0
    }
731
0
}
732
733
#endif // !! ASSIMP_BUILD_NO_MD5_IMPORTER