Coverage Report

Created: 2025-11-11 06:59

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-2025, 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
891
        mIOHandler(nullptr),
84
        mBuffer(),
85
        mFileSize(),
86
        mLineNumber(),
87
        mScene(),
88
        mHadMD5Mesh(),
89
        mHadMD5Anim(),
90
        mHadMD5Camera(),
91
891
        mCconfigNoAutoLoad(false) {
92
    // empty
93
891
}
94
95
// ------------------------------------------------------------------------------------------------
96
// Returns whether the class can handle the format of the given file.
97
507
bool MD5Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
98
507
    static const char *tokens[] = { "MD5Version" };
99
507
    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
100
507
}
101
102
// ------------------------------------------------------------------------------------------------
103
// Get list of all supported extensions
104
900
const aiImporterDesc *MD5Importer::GetInfo() const {
105
900
    return &desc;
106
900
}
107
108
// ------------------------------------------------------------------------------------------------
109
// Setup import properties
110
23
void MD5Importer::SetupProperties(const Importer *pImp) {
111
    // AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD
112
23
    mCconfigNoAutoLoad = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD, 0));
113
23
}
114
115
// ------------------------------------------------------------------------------------------------
116
// Imports the given file into the given scene structure.
117
23
void MD5Importer::InternReadFile(const std::string &pFile, aiScene *_pScene, IOSystem *pIOHandler) {
118
23
    mIOHandler = pIOHandler;
119
23
    mScene = _pScene;
120
23
    mHadMD5Mesh = mHadMD5Anim = mHadMD5Camera = false;
121
122
    // remove the file extension
123
23
    const std::string::size_type pos = pFile.find_last_of('.');
124
23
    mFile = (std::string::npos == pos ? pFile : pFile.substr(0, pos + 1));
125
126
23
    const std::string extension = GetExtension(pFile);
127
23
    try {
128
23
        if (extension == "md5camera") {
129
0
            LoadMD5CameraFile();
130
23
        } 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
23
        } else {
141
23
            LoadMD5MeshFile();
142
23
            LoadMD5AnimFile();
143
23
        }
144
23
    } catch (...) { // std::exception, Assimp::DeadlyImportError
145
8
        UnloadFileFromMemory();
146
8
        throw;
147
8
    }
148
149
    // make sure we have at least one file
150
15
    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
15
    mScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f,
156
15
            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
15
    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
15
    UnloadFileFromMemory();
165
15
}
166
167
// ------------------------------------------------------------------------------------------------
168
// Load a file into a memory buffer
169
44
void MD5Importer::LoadFileIntoMemory(IOStream *file) {
170
    // unload the previous buffer, if any
171
44
    UnloadFileFromMemory();
172
173
44
    ai_assert(nullptr != file);
174
44
    mFileSize = (unsigned int)file->FileSize();
175
44
    ai_assert(mFileSize);
176
177
    // allocate storage and copy the contents of the file to a memory buffer
178
44
    mBuffer = new char[mFileSize + 1];
179
44
    file->Read((void *)mBuffer, 1, mFileSize);
180
44
    mLineNumber = 1;
181
182
    // append a terminal 0
183
44
    mBuffer[mFileSize] = '\0';
184
185
    // now remove all line comments from the file
186
44
    CommentRemover::RemoveLineComments("//", mBuffer, ' ');
187
44
}
188
189
// ------------------------------------------------------------------------------------------------
190
// Unload the current memory buffer
191
67
void MD5Importer::UnloadFileFromMemory() {
192
    // delete the file buffer
193
67
    delete[] mBuffer;
194
67
    mBuffer = nullptr;
195
67
    mFileSize = 0;
196
67
}
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
21
void MD5Importer::AttachChilds_Mesh(int iParentID, aiNode *piParent, BoneArray &bones) {
235
21
    ai_assert(nullptr != piParent);
236
21
    ai_assert(!piParent->mNumChildren);
237
238
    // First find out how many children we'll have
239
21
    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
21
    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
21
}
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
23
void MD5Importer::LoadMD5MeshFile() {
326
23
    std::string filename = mFile + "md5mesh";
327
23
    std::unique_ptr<IOStream> file(mIOHandler->Open(filename, "rb"));
328
329
    // Check whether we can read from the file
330
23
    if (file == nullptr || !file->FileSize()) {
331
0
        ASSIMP_LOG_WARN("Failed to access MD5MESH file: ", filename);
332
0
        return;
333
0
    }
334
23
    mHadMD5Mesh = true;
335
23
    LoadFileIntoMemory(file.get());
336
337
    // now construct a parser and parse the file
338
23
    MD5::MD5Parser parser(mBuffer, mFileSize);
339
340
    // load the mesh information from it
341
23
    MD5::MD5MeshParser meshParser(parser.mSections);
342
343
    // create the bone hierarchy - first the root node and dummy nodes for all meshes
344
23
    mScene->mRootNode = new aiNode("<MD5_Root>");
345
23
    mScene->mRootNode->mNumChildren = 2;
346
23
    mScene->mRootNode->mChildren = new aiNode *[2];
347
348
    // build the hierarchy from the MD5MESH file
349
23
    aiNode *pcNode = mScene->mRootNode->mChildren[1] = new aiNode();
350
23
    pcNode->mName.Set("<MD5_Hierarchy>");
351
23
    pcNode->mParent = mScene->mRootNode;
352
23
    AttachChilds_Mesh(-1, pcNode, meshParser.mJoints);
353
354
23
    pcNode = mScene->mRootNode->mChildren[0] = new aiNode();
355
23
    pcNode->mName.Set("<MD5_Mesh>");
356
23
    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
34
    for (std::vector<MD5::MeshDesc>::const_iterator it = meshParser.mMeshes.begin(), end = meshParser.mMeshes.end(); it != end; ++it) {
365
11
        if (!(*it).mFaces.empty() && !(*it).mVertices.empty()) {
366
0
            ++mScene->mNumMaterials;
367
0
        }
368
11
    }
369
370
    // generate all meshes
371
23
    mScene->mNumMeshes = mScene->mNumMaterials;
372
23
    mScene->mMeshes = new aiMesh *[mScene->mNumMeshes];
373
23
    mScene->mMaterials = new aiMaterial *[mScene->mNumMeshes];
374
375
    //  storage for node mesh indices
376
23
    pcNode->mNumMeshes = mScene->mNumMeshes;
377
23
    pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
378
23
    for (unsigned int m = 0; m < pcNode->mNumMeshes; ++m) {
379
0
        pcNode->mMeshes[m] = m;
380
0
    }
381
382
23
    unsigned int n = 0;
383
34
    for (std::vector<MD5::MeshDesc>::iterator it = meshParser.mMeshes.begin(), end = meshParser.mMeshes.end(); it != end; ++it) {
384
11
        MD5::MeshDesc &meshSrc = *it;
385
11
        if (meshSrc.mFaces.empty() || meshSrc.mVertices.empty()) {
386
11
            continue;
387
11
        }
388
389
0
        aiMesh *mesh = mScene->mMeshes[n] = new aiMesh();
390
0
        mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
391
392
        // generate unique vertices in our internal verbose format
393
0
        MakeDataUnique(meshSrc);
394
395
0
        std::string name(meshSrc.mShader.C_Str());
396
0
        name += ".msh";
397
0
        mesh->mName = name;
398
0
        mesh->mNumVertices = (unsigned int)meshSrc.mVertices.size();
399
0
        mesh->mVertices = new aiVector3D[mesh->mNumVertices];
400
0
        mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
401
0
        mesh->mNumUVComponents[0] = 2;
402
403
        // copy texture coordinates
404
0
        aiVector3D *pv = mesh->mTextureCoords[0];
405
0
        for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) {
406
0
            pv->x = (*iter).mUV.x;
407
0
            pv->y = 1.0f - (*iter).mUV.y; // D3D to OpenGL
408
0
            pv->z = 0.0f;
409
0
        }
410
411
        // sort all bone weights - per bone
412
0
        unsigned int *piCount = new unsigned int[meshParser.mJoints.size()];
413
0
        ::memset(piCount, 0, sizeof(unsigned int) * meshParser.mJoints.size());
414
415
0
        for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) {
416
0
            for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) {
417
0
                MD5::WeightDesc &weightDesc = meshSrc.mWeights[w];
418
                /* FIX for some invalid exporters */
419
0
                if (!(weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON)) {
420
0
                    ++piCount[weightDesc.mBone];
421
0
                }
422
0
            }
423
0
        }
424
425
        // check how many we will need
426
0
        for (unsigned int p = 0; p < meshParser.mJoints.size(); ++p) {
427
0
            if (piCount[p]) mesh->mNumBones++;
428
0
        }
429
430
        // just for safety
431
0
        if (mesh->mNumBones) {
432
0
            mesh->mBones = new aiBone *[mesh->mNumBones];
433
0
            for (unsigned int q = 0, h = 0; q < meshParser.mJoints.size(); ++q) {
434
0
                if (!piCount[q]) continue;
435
0
                aiBone *p = mesh->mBones[h] = new aiBone();
436
0
                p->mNumWeights = piCount[q];
437
0
                p->mWeights = new aiVertexWeight[p->mNumWeights];
438
0
                p->mName = aiString(meshParser.mJoints[q].mName);
439
0
                p->mOffsetMatrix = meshParser.mJoints[q].mInvTransform;
440
441
                // store the index for later use
442
0
                MD5::BoneDesc &boneSrc = meshParser.mJoints[q];
443
0
                boneSrc.mMap = h++;
444
445
                // compute w-component of quaternion
446
0
                MD5::ConvertQuaternion(boneSrc.mRotationQuat, boneSrc.mRotationQuatConverted);
447
0
            }
448
449
0
            pv = mesh->mVertices;
450
0
            for (MD5::VertexArray::const_iterator iter = meshSrc.mVertices.begin(); iter != meshSrc.mVertices.end(); ++iter, ++pv) {
451
                // compute the final vertex position from all single weights
452
0
                *pv = aiVector3D();
453
454
                // there are models which have weights which don't sum to 1 ...
455
0
                ai_real fSum = 0.0;
456
0
                for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) {
457
0
                    fSum += meshSrc.mWeights[w].mWeight;
458
0
                }
459
0
                if (!fSum) {
460
0
                    ASSIMP_LOG_ERROR("MD5MESH: The sum of all vertex bone weights is 0");
461
0
                    continue;
462
0
                }
463
464
                // process bone weights
465
0
                for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights; ++w) {
466
0
                    if (w >= meshSrc.mWeights.size()) {
467
0
                        throw DeadlyImportError("MD5MESH: Invalid weight index");
468
0
                    }
469
470
0
                    MD5::WeightDesc &weightDesc = meshSrc.mWeights[w];
471
0
                    if (weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON) {
472
0
                        continue;
473
0
                    }
474
475
0
                    const ai_real fNewWeight = weightDesc.mWeight / fSum;
476
477
                    // transform the local position into worldspace
478
0
                    MD5::BoneDesc &boneSrc = meshParser.mJoints[weightDesc.mBone];
479
0
                    const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate(weightDesc.vOffsetPosition);
480
481
                    // use the original weight to compute the vertex position
482
                    // (some MD5s seem to depend on the invalid weight values ...)
483
0
                    *pv += ((boneSrc.mPositionXYZ + v) * (ai_real)weightDesc.mWeight);
484
485
0
                    aiBone *bone = mesh->mBones[boneSrc.mMap];
486
0
                    *bone->mWeights++ = aiVertexWeight((unsigned int)(pv - mesh->mVertices), fNewWeight);
487
0
                }
488
0
            }
489
490
            // undo our nice offset tricks ...
491
0
            for (unsigned int p = 0; p < mesh->mNumBones; ++p) {
492
0
                mesh->mBones[p]->mWeights -= mesh->mBones[p]->mNumWeights;
493
0
            }
494
0
        }
495
496
0
        delete[] piCount;
497
498
        // now setup all faces - we can directly copy the list
499
        // (however, take care that the aiFace destructor doesn't delete the mIndices array)
500
0
        mesh->mNumFaces = (unsigned int)meshSrc.mFaces.size();
501
0
        mesh->mFaces = new aiFace[mesh->mNumFaces];
502
0
        for (unsigned int c = 0; c < mesh->mNumFaces; ++c) {
503
0
            mesh->mFaces[c].mNumIndices = 3;
504
0
            mesh->mFaces[c].mIndices = meshSrc.mFaces[c].mIndices;
505
0
            meshSrc.mFaces[c].mIndices = nullptr;
506
0
        }
507
508
        // generate a material for the mesh
509
0
        aiMaterial *mat = new aiMaterial();
510
0
        mScene->mMaterials[n] = mat;
511
512
        // insert the typical doom3 textures:
513
        // nnn_local.tga  - normal map
514
        // nnn_h.tga      - height map
515
        // nnn_s.tga      - specular map
516
        // nnn_d.tga      - diffuse map
517
0
        if (meshSrc.mShader.length && !strchr(meshSrc.mShader.data, '.')) {
518
519
0
            aiString temp(meshSrc.mShader);
520
0
            temp.Append("_local.tga");
521
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_NORMALS(0));
522
523
0
            temp = aiString(meshSrc.mShader);
524
0
            temp.Append("_s.tga");
525
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_SPECULAR(0));
526
527
0
            temp = aiString(meshSrc.mShader);
528
0
            temp.Append("_d.tga");
529
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_DIFFUSE(0));
530
531
0
            temp = aiString(meshSrc.mShader);
532
0
            temp.Append("_h.tga");
533
0
            mat->AddProperty(&temp, AI_MATKEY_TEXTURE_HEIGHT(0));
534
535
            // set this also as material name
536
0
            mat->AddProperty(&meshSrc.mShader, AI_MATKEY_NAME);
537
0
        } else {
538
0
            mat->AddProperty(&meshSrc.mShader, AI_MATKEY_TEXTURE_DIFFUSE(0));
539
0
        }
540
0
        mesh->mMaterialIndex = n++;
541
0
    }
542
23
#endif
543
23
}
544
545
// ------------------------------------------------------------------------------------------------
546
// Load an MD5ANIM file
547
21
void MD5Importer::LoadMD5AnimFile() {
548
21
    std::string pFile = mFile + "md5anim";
549
21
    std::unique_ptr<IOStream> file(mIOHandler->Open(pFile, "rb"));
550
551
    // Check whether we can read from the file
552
21
    if (!file || !file->FileSize()) {
553
0
        ASSIMP_LOG_WARN("Failed to read MD5ANIM file: ", pFile);
554
0
        return;
555
0
    }
556
557
21
    LoadFileIntoMemory(file.get());
558
559
    // parse the basic file structure
560
21
    MD5::MD5Parser parser(mBuffer, mFileSize);
561
562
    // load the animation information from the parse tree
563
21
    MD5::MD5AnimParser animParser(parser.mSections);
564
565
    // generate and fill the output animation
566
21
    if (animParser.mAnimatedBones.empty() || animParser.mFrames.empty() ||
567
15
            animParser.mBaseFrames.size() != animParser.mAnimatedBones.size()) {
568
15
        ASSIMP_LOG_ERROR("MD5ANIM: No frames or animated bones loaded");
569
15
    } else {
570
6
        mHadMD5Anim = true;
571
572
6
        mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations = 1];
573
6
        aiAnimation *anim = mScene->mAnimations[0] = new aiAnimation();
574
6
        anim->mNumChannels = (unsigned int)animParser.mAnimatedBones.size();
575
6
        anim->mChannels = new aiNodeAnim *[anim->mNumChannels];
576
6
        for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
577
0
            aiNodeAnim *node = anim->mChannels[i] = new aiNodeAnim();
578
0
            node->mNodeName = aiString(animParser.mAnimatedBones[i].mName);
579
580
            // allocate storage for the keyframes
581
0
            node->mPositionKeys = new aiVectorKey[animParser.mFrames.size()];
582
0
            node->mRotationKeys = new aiQuatKey[animParser.mFrames.size()];
583
0
        }
584
585
        // 1 tick == 1 frame
586
6
        anim->mTicksPerSecond = animParser.fFrameRate;
587
588
6
        for (FrameArray::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end(); iter != iterEnd; ++iter) {
589
0
            double dTime = (double)(*iter).iIndex;
590
0
            aiNodeAnim **pcAnimNode = anim->mChannels;
591
0
            if (!(*iter).mValues.empty() || iter == animParser.mFrames.begin()) /* be sure we have at least one frame */
592
0
            {
593
                // now process all values in there ... read all joints
594
0
                MD5::BaseFrameDesc *pcBaseFrame = &animParser.mBaseFrames[0];
595
0
                for (AnimBoneArray::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end(); ++iter2,
596
0
                                                  ++pcAnimNode, ++pcBaseFrame) {
597
0
                    if ((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) {
598
599
                        // Allow for empty frames
600
0
                        if ((*iter2).iFlags != 0) {
601
0
                            throw DeadlyImportError("MD5: Keyframe index is out of range");
602
0
                        }
603
0
                        continue;
604
0
                    }
605
0
                    const float *fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex];
606
0
                    aiNodeAnim *pcCurAnimBone = *pcAnimNode;
607
608
0
                    aiVectorKey *vKey = &pcCurAnimBone->mPositionKeys[pcCurAnimBone->mNumPositionKeys++];
609
0
                    aiQuatKey *qKey = &pcCurAnimBone->mRotationKeys[pcCurAnimBone->mNumRotationKeys++];
610
0
                    aiVector3D vTemp;
611
612
                    // translational component
613
0
                    for (unsigned int i = 0; i < 3; ++i) {
614
0
                        if ((*iter2).iFlags & (1u << i)) {
615
0
                            vKey->mValue[i] = *fpCur++;
616
0
                        } else
617
0
                            vKey->mValue[i] = pcBaseFrame->vPositionXYZ[i];
618
0
                    }
619
620
                    // orientation component
621
0
                    for (unsigned int i = 0; i < 3; ++i) {
622
0
                        if ((*iter2).iFlags & (8u << i)) {
623
0
                            vTemp[i] = *fpCur++;
624
0
                        } else
625
0
                            vTemp[i] = pcBaseFrame->vRotationQuat[i];
626
0
                    }
627
628
0
                    MD5::ConvertQuaternion(vTemp, qKey->mValue);
629
0
                    qKey->mTime = vKey->mTime = dTime;
630
0
                }
631
0
            }
632
633
            // compute the duration of the animation
634
0
            anim->mDuration = std::max(dTime, anim->mDuration);
635
0
        }
636
637
        // If we didn't build the hierarchy yet (== we didn't load a MD5MESH),
638
        // construct it now from the data given in the MD5ANIM.
639
6
        if (!mScene->mRootNode) {
640
0
            mScene->mRootNode = new aiNode();
641
0
            mScene->mRootNode->mName.Set("<MD5_Hierarchy>");
642
643
0
            AttachChilds_Anim(-1, mScene->mRootNode, animParser.mAnimatedBones, (const aiNodeAnim **)anim->mChannels);
644
645
            // Call SkeletonMeshBuilder to construct a mesh to represent the shape
646
0
            if (mScene->mRootNode->mNumChildren) {
647
0
                SkeletonMeshBuilder skeleton_maker(mScene, mScene->mRootNode->mChildren[0]);
648
0
            }
649
0
        }
650
6
    }
651
21
}
652
653
// ------------------------------------------------------------------------------------------------
654
// Load an MD5CAMERA file
655
0
void MD5Importer::LoadMD5CameraFile() {
656
0
    std::string pFile = mFile + "md5camera";
657
0
    std::unique_ptr<IOStream> file(mIOHandler->Open(pFile, "rb"));
658
659
    // Check whether we can read from the file
660
0
    if (!file || !file->FileSize()) {
661
0
        throw DeadlyImportError("Failed to read MD5CAMERA file: ", pFile);
662
0
    }
663
0
    mHadMD5Camera = true;
664
0
    LoadFileIntoMemory(file.get());
665
666
    // parse the basic file structure
667
0
    MD5::MD5Parser parser(mBuffer, mFileSize);
668
669
    // load the camera animation data from the parse tree
670
0
    MD5::MD5CameraParser cameraParser(parser.mSections);
671
672
0
    if (cameraParser.frames.empty()) {
673
0
        throw DeadlyImportError("MD5CAMERA: No frames parsed");
674
0
    }
675
676
0
    std::vector<unsigned int> &cuts = cameraParser.cuts;
677
0
    std::vector<MD5::CameraAnimFrameDesc> &frames = cameraParser.frames;
678
679
    // Construct output graph - a simple root with a dummy child.
680
    // The root node performs the coordinate system conversion
681
0
    aiNode *root = mScene->mRootNode = new aiNode("<MD5CameraRoot>");
682
0
    root->mChildren = new aiNode *[root->mNumChildren = 1];
683
0
    root->mChildren[0] = new aiNode("<MD5Camera>");
684
0
    root->mChildren[0]->mParent = root;
685
686
    // ... but with one camera assigned to it
687
0
    mScene->mCameras = new aiCamera *[mScene->mNumCameras = 1];
688
0
    aiCamera *cam = mScene->mCameras[0] = new aiCamera();
689
0
    cam->mName = "<MD5Camera>";
690
691
    // FIXME: Fov is currently set to the first frame's value
692
0
    cam->mHorizontalFOV = AI_DEG_TO_RAD(frames.front().fFOV);
693
694
    // every cut is written to a separate aiAnimation
695
0
    if (!cuts.size()) {
696
0
        cuts.push_back(0);
697
0
        cuts.push_back(static_cast<unsigned int>(frames.size() - 1));
698
0
    } else {
699
0
        cuts.insert(cuts.begin(), 0);
700
701
0
        if (cuts.back() < frames.size() - 1)
702
0
            cuts.push_back(static_cast<unsigned int>(frames.size() - 1));
703
0
    }
704
705
0
    mScene->mNumAnimations = static_cast<unsigned int>(cuts.size() - 1);
706
0
    aiAnimation **tmp = mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations];
707
0
    for (std::vector<unsigned int>::const_iterator it = cuts.begin(); it != cuts.end() - 1; ++it) {
708
709
0
        aiAnimation *anim = *tmp++ = new aiAnimation();
710
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));
711
712
0
        anim->mTicksPerSecond = cameraParser.fFrameRate;
713
0
        anim->mChannels = new aiNodeAnim *[anim->mNumChannels = 1];
714
0
        aiNodeAnim *nd = anim->mChannels[0] = new aiNodeAnim();
715
0
        nd->mNodeName.Set("<MD5Camera>");
716
717
0
        nd->mNumPositionKeys = nd->mNumRotationKeys = *(it + 1) - (*it);
718
0
        nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
719
0
        nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys];
720
0
        for (unsigned int i = 0; i < nd->mNumPositionKeys; ++i) {
721
722
0
            nd->mPositionKeys[i].mValue = frames[*it + i].vPositionXYZ;
723
0
            MD5::ConvertQuaternion(frames[*it + i].vRotationQuat, nd->mRotationKeys[i].mValue);
724
0
            nd->mRotationKeys[i].mTime = nd->mPositionKeys[i].mTime = *it + i;
725
0
        }
726
0
    }
727
0
}
728
729
#endif // !! ASSIMP_BUILD_NO_MD5_IMPORTER