/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 |