Coverage Report

Created: 2026-07-30 07:21

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