Coverage Report

Created: 2025-12-05 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/MD5/MD5Parser.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  MD5Parser.cpp
43
 *  @brief Implementation of the MD5 parser class
44
 */
45
46
// internal headers
47
#include "MD5Loader.h"
48
#include "Material/MaterialSystem.h"
49
50
#include <assimp/ParsingUtils.h>
51
#include <assimp/StringComparison.h>
52
#include <assimp/fast_atof.h>
53
#include <assimp/mesh.h>
54
#include <assimp/DefaultLogger.hpp>
55
56
using namespace Assimp;
57
using namespace Assimp::MD5;
58
59
// ------------------------------------------------------------------------------------------------
60
// Parse the segment structure for an MD5 file
61
65
MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) : buffer(_buffer), bufferEnd(nullptr), fileSize(_fileSize), lineNumber(0) {
62
65
    ai_assert(nullptr != _buffer);
63
65
    ai_assert(0 != _fileSize);
64
65
65
    bufferEnd = buffer + fileSize;
66
65
    ASSIMP_LOG_DEBUG("MD5Parser begin");
67
68
    // parse the file header
69
65
    ParseHeader();
70
71
    // and read all sections until we're finished
72
65
    bool running = true;
73
33.3k
    while (running) {
74
33.3k
        mSections.emplace_back();
75
33.3k
        Section &sec = mSections.back();
76
33.3k
        if (!ParseSection(sec)) {
77
65
            break;
78
65
        }
79
33.3k
    }
80
81
65
    if (!DefaultLogger::isNullLogger()) {
82
0
        char szBuffer[128]; // should be sufficiently large
83
0
        ::ai_snprintf(szBuffer, 128, "MD5Parser end. Parsed %i sections", (int)mSections.size());
84
0
        ASSIMP_LOG_DEBUG(szBuffer);
85
0
    }
86
65
}
87
88
// ------------------------------------------------------------------------------------------------
89
// Report error to the log stream
90
0
AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) {
91
0
    char szBuffer[1024];
92
0
    ::ai_snprintf(szBuffer, 1024, "[MD5] Line %u: %s", line, error);
93
0
    throw DeadlyImportError(szBuffer);
94
0
}
95
96
// ------------------------------------------------------------------------------------------------
97
// Report warning to the log stream
98
5.60k
void MD5Parser::ReportWarning(const char *warn, unsigned int line) {
99
5.60k
    char szBuffer[1024];
100
5.60k
    ::snprintf(szBuffer, sizeof(szBuffer), "[MD5] Line %u: %s", line, warn);
101
5.60k
    ASSIMP_LOG_WARN(szBuffer);
102
5.60k
}
103
104
// ------------------------------------------------------------------------------------------------
105
// Parse and validate the MD5 header
106
65
void MD5Parser::ParseHeader() {
107
    // parse and validate the file version
108
65
    SkipSpaces();
109
65
    if (!TokenMatch(buffer, "MD5Version", 10)) {
110
0
        ReportError("Invalid MD5 file: MD5Version tag has not been found");
111
0
    }
112
65
    SkipSpaces();
113
65
    unsigned int iVer = ::strtoul10(buffer, (const char **)&buffer);
114
65
    if (10 != iVer) {
115
0
        ReportError("MD5 version tag is unknown (10 is expected)");
116
0
    }
117
65
    SkipLine();
118
119
    // print the command line options to the console
120
65
    char *sz = buffer;
121
1.19k
    while (buffer < bufferEnd) {
122
1.19k
        if (IsLineEnd(*buffer++)) {
123
65
            break;
124
65
        }
125
1.19k
    }
126
127
65
    if (buffer == bufferEnd) {
128
0
        return;
129
0
    }
130
131
65
    ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz))));
132
65
    SkipSpacesAndLineEnd();
133
65
}
134
135
// ------------------------------------------------------------------------------------------------
136
// Recursive MD5 parsing function
137
33.3k
bool MD5Parser::ParseSection(Section &out) {
138
    // store the current line number for use in error messages
139
33.3k
    out.iLineNumber = lineNumber;
140
141
    // first parse the name of the section
142
33.3k
    char *sz = buffer;
143
497k
    while (!IsSpaceOrNewLine(*buffer)) {
144
464k
        ++buffer;
145
464k
        if (buffer == bufferEnd) {
146
18
            return false;
147
18
      }
148
464k
    }
149
33.3k
    out.mName = std::string(sz, (uintptr_t)(buffer - sz));
150
53.5k
    while (IsSpace(*buffer)) {
151
20.1k
        ++buffer;
152
20.1k
        if (buffer == bufferEnd) {
153
2
            return false;
154
2
      }
155
20.1k
    }
156
157
33.3k
    bool running = true;
158
75.5k
    while (running) {
159
75.5k
        if ('{' == *buffer) {
160
            // it is a normal section so read all lines
161
255
            ++buffer;
162
255
            if (buffer == bufferEnd) {
163
0
                return false;
164
0
          }
165
255
            bool run = true;
166
28.2k
            while (run) {
167
76.3k
                while (IsSpaceOrNewLine(*buffer)) {
168
48.1k
                    ++buffer;
169
48.1k
                    if (buffer == bufferEnd) {
170
8
                        return false;
171
8
                }
172
48.1k
                }
173
28.2k
                if ('\0' == *buffer) {
174
0
                    return false; // seems this was the last section
175
0
                }
176
28.2k
                if ('}' == *buffer) {
177
224
                    ++buffer;
178
224
                    break;
179
224
                }
180
181
27.9k
                out.mElements.emplace_back();
182
27.9k
                Element &elem = out.mElements.back();
183
184
27.9k
                elem.iLineNumber = lineNumber;
185
27.9k
                elem.szStart = buffer;
186
27.9k
                elem.end = bufferEnd;
187
188
                // terminate the line with zero
189
468k
                while (!IsLineEnd(*buffer)) {
190
440k
                    ++buffer;
191
440k
                    if (buffer == bufferEnd) {
192
23
                        return false;
193
23
                }
194
440k
                }
195
27.9k
                if (*buffer) {
196
15.1k
                    ++lineNumber;
197
15.1k
                    *buffer++ = '\0';
198
15.1k
                    if (buffer == bufferEnd) {
199
0
                        return false;
200
0
                }
201
15.1k
                }
202
27.9k
            }
203
224
            break;
204
75.2k
        } else if (!IsSpaceOrNewLine(*buffer)) {
205
            // it is an element at global scope. Parse its value and go on
206
42.1k
            sz = buffer;
207
562k
            while (!IsSpaceOrNewLine(*buffer++)) {
208
520k
                if (buffer == bufferEnd) {
209
8
                    return false;
210
8
            }
211
520k
            }
212
42.1k
            out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz));
213
42.1k
            continue;
214
42.1k
        }
215
33.1k
        break;
216
75.5k
    }
217
33.3k
    if (buffer == bufferEnd) {
218
2
        return false;
219
2
    }
220
95.8k
    while (IsSpaceOrNewLine(*buffer)) {
221
62.5k
        if (buffer == bufferEnd) {
222
4
            break;
223
4
      }
224
62.5k
        ++buffer;
225
62.5k
    }
226
33.3k
    return '\0' != *buffer;
227
33.3k
}
228
229
// skip all spaces ... handle EOL correctly
230
10.6k
inline void AI_MD5_SKIP_SPACES(const char **sz, const char *bufferEnd, int linenumber) {
231
10.6k
    if (!SkipSpaces(sz, bufferEnd)) {
232
5.49k
        MD5Parser::ReportWarning("Unexpected end of line", linenumber);
233
5.49k
    }
234
10.6k
}
235
236
// read a triple float in brackets: (1.0 1.0 1.0)
237
1
inline void AI_MD5_READ_TRIPLE(aiVector3D &vec, const char **sz, const char *bufferEnd, int linenumber) {
238
1
    AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber);
239
1
    if ('(' != **sz) {
240
1
        MD5Parser::ReportWarning("Unexpected token: ( was expected", linenumber);
241
1
        if (*sz == bufferEnd)
242
0
            return;
243
1
        ++*sz;
244
1
    }
245
1
    if (*sz == bufferEnd)
246
0
        return;
247
1
    ++*sz;
248
1
    AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber);
249
1
    *sz = fast_atoreal_move(*sz, vec.x);
250
1
    AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber);
251
1
    *sz = fast_atoreal_move(*sz, vec.y);
252
1
    AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber);
253
1
    *sz = fast_atoreal_move(*sz, vec.z);
254
1
    AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber);
255
1
    if (')' != **sz) {
256
0
        MD5Parser::ReportWarning("Unexpected token: ) was expected", linenumber);
257
0
    }
258
1
    if (*sz == bufferEnd)
259
0
        return;
260
1
    ++*sz;
261
1
}
262
263
// parse a string, enclosed in quotation marks or not
264
0
inline bool AI_MD5_PARSE_STRING(const char **sz, const char *bufferEnd, aiString &out, int linenumber) {
265
0
    bool bQuota = (**sz == '\"');
266
0
    const char *szStart = *sz;
267
0
    while (!IsSpaceOrNewLine(**sz)) {
268
0
        ++*sz;
269
0
        if (*sz == bufferEnd) break;
270
0
    }
271
0
    const char *szEnd = *sz;
272
0
    if (bQuota) {
273
0
        szStart++;
274
0
        if ('\"' != *(szEnd -= 1)) {
275
0
            MD5Parser::ReportWarning("Expected closing quotation marks in string", linenumber);
276
0
            ++*sz;
277
0
        }
278
0
    }
279
0
    out.length = (ai_uint32)(szEnd - szStart);
280
0
    if (out.length >= AI_MAXLEN)
281
0
        out.length = AI_MAXLEN - 1;
282
0
    ::memcpy(out.data, szStart, out.length);
283
0
    out.data[out.length] = '\0';
284
0
285
0
    return true;
286
0
}
287
288
// parse a string, enclosed in quotation marks
289
2.81k
inline void AI_MD5_PARSE_STRING_IN_QUOTATION(const char **sz, const char *bufferEnd, aiString &out) {
290
2.81k
    out.length = 0u;
291
38.3k
    while (('\"' != **sz && '\0' != **sz) && *sz != bufferEnd) {
292
35.5k
        ++*sz;
293
35.5k
    }
294
2.81k
    if ('\0' != **sz) {
295
1.01k
        const char *szStart = ++(*sz);
296
297
2.02k
        while (('\"' != **sz && '\0' != **sz) && *sz != bufferEnd) {
298
1.01k
            ++*sz;
299
1.01k
        }
300
1.01k
        if ('\0' != **sz) {
301
1.01k
            const char *szEnd = *sz;
302
1.01k
            ++*sz;
303
1.01k
            out.length = (ai_uint32)(szEnd - szStart);
304
1.01k
            if (out.length >= AI_MAXLEN)
305
0
                out.length = AI_MAXLEN - 1;
306
1.01k
            ::memcpy(out.data, szStart, out.length);
307
1.01k
        }
308
1.01k
    }
309
2.81k
    out.data[out.length] = '\0';
310
2.81k
}
311
312
// ------------------------------------------------------------------------------------------------
313
// .MD5MESH parsing function
314
33
MD5MeshParser::MD5MeshParser(SectionArray &mSections) {
315
33
    ASSIMP_LOG_DEBUG("MD5MeshParser begin");
316
317
    // now parse all sections
318
16.8k
    for (SectionArray::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) {
319
16.8k
        if ((*iter).mName == "numMeshes") {
320
0
            mMeshes.reserve(::strtoul10((*iter).mGlobalValue.c_str()));
321
16.8k
        } else if ((*iter).mName == "numJoints") {
322
685
            mJoints.reserve(::strtoul10((*iter).mGlobalValue.c_str()));
323
16.1k
        } else if ((*iter).mName == "joints") {
324
            // "origin" -1 ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000000 0.707107 )
325
63
            for (const auto &elem : (*iter).mElements) {
326
1
                mJoints.emplace_back();
327
1
                BoneDesc &desc = mJoints.back();
328
329
1
                const char *sz = elem.szStart;
330
1
                AI_MD5_PARSE_STRING_IN_QUOTATION(&sz, elem.end, desc.mName);
331
332
1
                AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
333
334
                // negative values, at least -1, is allowed here
335
1
                desc.mParentIndex = (int)strtol10(sz, &sz);
336
337
1
                AI_MD5_READ_TRIPLE(desc.mPositionXYZ, &sz, elem.end, elem.iLineNumber);
338
1
                AI_MD5_READ_TRIPLE(desc.mRotationQuat, &sz, elem.end, elem.iLineNumber); // normalized quaternion, so w is not there
339
1
            }
340
16.1k
        } else if ((*iter).mName == "mesh") {
341
415
            mMeshes.emplace_back();
342
415
            MeshDesc &desc = mMeshes.back();
343
344
11.0k
            for (const auto &elem : (*iter).mElements) {
345
11.0k
                const char *sz = elem.szStart;
346
347
                // shader attribute
348
11.0k
                if (TokenMatch(sz, "shader", 6)) {
349
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
350
1
                    AI_MD5_PARSE_STRING_IN_QUOTATION(&sz, elem.end, desc.mShader);
351
1
                }
352
                // numverts attribute
353
11.0k
                else if (TokenMatch(sz, "numverts", 8)) {
354
0
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
355
0
                    desc.mVertices.resize(strtoul10(sz));
356
0
                }
357
                // numtris attribute
358
11.0k
                else if (TokenMatch(sz, "numtris", 7)) {
359
204
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
360
204
                    desc.mFaces.resize(strtoul10(sz));
361
204
                }
362
                // numweights attribute
363
10.8k
                else if (TokenMatch(sz, "numweights", 10)) {
364
0
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
365
0
                    desc.mWeights.resize(strtoul10(sz));
366
0
                }
367
                // vert attribute
368
                // "vert 0 ( 0.394531 0.513672 ) 0 1"
369
10.8k
                else if (TokenMatch(sz, "vert", 4)) {
370
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
371
1
                    const unsigned int idx = ::strtoul10(sz, &sz);
372
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
373
1
                    if (idx >= desc.mVertices.size())
374
1
                        desc.mVertices.resize(idx + 1);
375
376
1
                    VertexDesc &vert = desc.mVertices[idx];
377
1
                    if ('(' != *sz++)
378
1
                        MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber);
379
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
380
1
                    sz = fast_atoreal_move(sz, vert.mUV.x);
381
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
382
1
                    sz = fast_atoreal_move(sz, vert.mUV.y);
383
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
384
1
                    if (')' != *sz++)
385
1
                        MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber);
386
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
387
1
                    vert.mFirstWeight = ::strtoul10(sz, &sz);
388
1
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
389
1
                    vert.mNumWeights = ::strtoul10(sz, &sz);
390
1
                }
391
                // tri attribute
392
                // "tri 0 15 13 12"
393
10.8k
                else if (TokenMatch(sz, "tri", 3)) {
394
494
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
395
494
                    const unsigned int idx = strtoul10(sz, &sz);
396
494
                    if (idx >= desc.mFaces.size()) {
397
128
                        desc.mFaces.resize(idx + 1);
398
128
          }
399
400
494
                    aiFace &face = desc.mFaces[idx];
401
494
                    if (face.mNumIndices != 3) {
402
158
            delete [] face.mIndices;
403
158
            face.mIndices = new unsigned int[face.mNumIndices = 3];
404
158
          }
405
1.97k
                    for (unsigned int i = 0; i < 3; ++i) {
406
1.48k
                        AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
407
1.48k
                        face.mIndices[i] = strtoul10(sz, &sz);
408
1.48k
                    }
409
494
                }
410
                // weight attribute
411
                // "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )"
412
10.3k
                else if (TokenMatch(sz, "weight", 6)) {
413
0
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
414
0
                    const unsigned int idx = strtoul10(sz, &sz);
415
0
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
416
0
                    if (idx >= desc.mWeights.size())
417
0
                        desc.mWeights.resize(idx + 1);
418
419
0
                    WeightDesc &weight = desc.mWeights[idx];
420
0
                    weight.mBone = strtoul10(sz, &sz);
421
0
                    AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
422
0
                    sz = fast_atoreal_move(sz, weight.mWeight);
423
0
                    AI_MD5_READ_TRIPLE(weight.vOffsetPosition, &sz, elem.end, elem.iLineNumber);
424
0
                }
425
11.0k
            }
426
415
        }
427
16.8k
    }
428
33
    ASSIMP_LOG_DEBUG("MD5MeshParser end");
429
33
}
430
431
// ------------------------------------------------------------------------------------------------
432
// .MD5ANIM parsing function
433
32
MD5AnimParser::MD5AnimParser(SectionArray &mSections) {
434
32
    ASSIMP_LOG_DEBUG("MD5AnimParser begin");
435
436
32
    fFrameRate = 24.0f;
437
32
    mNumAnimatedComponents = UINT_MAX;
438
16.5k
    for (SectionArray::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) {
439
16.5k
        if ((*iter).mName == "hierarchy") {
440
            // "sheath" 0 63 6
441
2.80k
            for (const auto &elem : (*iter).mElements) {
442
2.80k
                mAnimatedBones.emplace_back();
443
2.80k
                AnimBoneDesc &desc = mAnimatedBones.back();
444
445
2.80k
                const char *sz = elem.szStart;
446
2.80k
                AI_MD5_PARSE_STRING_IN_QUOTATION(&sz, elem.end, desc.mName);
447
2.80k
                AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
448
449
                // parent index - negative values are allowed (at least -1)
450
2.80k
                desc.mParentIndex = ::strtol10(sz, &sz);
451
452
                // flags (highest is 2^6-1)
453
2.80k
                AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
454
2.80k
                if (63 < (desc.iFlags = ::strtoul10(sz, &sz))) {
455
99
                    MD5Parser::ReportWarning("Invalid flag combination in hierarchy section", elem.iLineNumber);
456
99
                }
457
2.80k
                AI_MD5_SKIP_SPACES(&  sz, elem.end, elem.iLineNumber);
458
459
                // index of the first animation keyframe component for this joint
460
2.80k
                desc.iFirstKeyIndex = ::strtoul10(sz, &sz);
461
2.80k
            }
462
16.5k
        } else if ((*iter).mName == "baseframe") {
463
            // ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000242 0.707107 )
464
0
            for (const auto &elem : (*iter).mElements) {
465
0
                const char *sz = elem.szStart;
466
467
0
                mBaseFrames.emplace_back();
468
0
                BaseFrameDesc &desc = mBaseFrames.back();
469
470
0
                AI_MD5_READ_TRIPLE(desc.vPositionXYZ, &sz, elem.end, elem.iLineNumber);
471
0
                AI_MD5_READ_TRIPLE(desc.vRotationQuat, &sz, elem.end, elem.iLineNumber);
472
0
            }
473
16.5k
        } else if ((*iter).mName == "frame") {
474
1.34k
            if (!(*iter).mGlobalValue.length()) {
475
0
                MD5Parser::ReportWarning("A frame section must have a frame index", (*iter).iLineNumber);
476
0
                continue;
477
0
            }
478
479
1.34k
            mFrames.emplace_back();
480
1.34k
            FrameDesc &desc = mFrames.back();
481
1.34k
            desc.iIndex = strtoul10((*iter).mGlobalValue.c_str());
482
483
            // we do already know how much storage we will presumably need
484
1.34k
            if (UINT_MAX != mNumAnimatedComponents) {
485
1.10k
                desc.mValues.reserve(mNumAnimatedComponents);
486
1.10k
            }
487
488
            // now read all elements (continuous list of floats)
489
1.34k
            for (const auto &elem : (*iter).mElements) {
490
2
                const char *sz = elem.szStart;
491
4
                while (SkipSpacesAndLineEnd(&sz, elem.end)) {
492
2
                    float f;
493
2
                    sz = fast_atoreal_move(sz, f);
494
2
                    desc.mValues.push_back(f);
495
2
                }
496
2
            }
497
15.1k
        } else if ((*iter).mName == "numFrames") {
498
0
            mFrames.reserve(strtoul10((*iter).mGlobalValue.c_str()));
499
15.1k
        } else if ((*iter).mName == "numJoints") {
500
659
            const unsigned int num = strtoul10((*iter).mGlobalValue.c_str());
501
659
            mAnimatedBones.reserve(num);
502
503
            // try to guess the number of animated components if that element is not given
504
659
            if (UINT_MAX == mNumAnimatedComponents) {
505
5
                mNumAnimatedComponents = num * 6;
506
5
            }
507
14.4k
        } else if ((*iter).mName == "numAnimatedComponents") {
508
0
            mAnimatedBones.reserve(strtoul10((*iter).mGlobalValue.c_str()));
509
14.4k
        } else if ((*iter).mName == "frameRate") {
510
0
            fast_atoreal_move((*iter).mGlobalValue.c_str(), fFrameRate);
511
0
        }
512
16.5k
    }
513
32
    ASSIMP_LOG_DEBUG("MD5AnimParser end");
514
32
}
515
516
// ------------------------------------------------------------------------------------------------
517
// .MD5CAMERA parsing function
518
0
MD5CameraParser::MD5CameraParser(SectionArray &mSections) {
519
0
    ASSIMP_LOG_DEBUG("MD5CameraParser begin");
520
0
    fFrameRate = 24.0f;
521
522
0
    for (SectionArray::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) {
523
0
        if ((*iter).mName == "numFrames") {
524
0
            frames.reserve(strtoul10((*iter).mGlobalValue.c_str()));
525
0
        } else if ((*iter).mName == "frameRate") {
526
0
            fFrameRate = fast_atof((*iter).mGlobalValue.c_str());
527
0
        } else if ((*iter).mName == "numCuts") {
528
0
            cuts.reserve(strtoul10((*iter).mGlobalValue.c_str()));
529
0
        } else if ((*iter).mName == "cuts") {
530
0
            for (const auto &elem : (*iter).mElements) {
531
0
                cuts.push_back(strtoul10(elem.szStart) + 1);
532
0
            }
533
0
        } else if ((*iter).mName == "camera") {
534
0
            for (const auto &elem : (*iter).mElements) {
535
0
                const char *sz = elem.szStart;
536
537
0
                frames.emplace_back();
538
0
                CameraAnimFrameDesc &cur = frames.back();
539
0
                AI_MD5_READ_TRIPLE(cur.vPositionXYZ, &sz, elem.end, elem.iLineNumber);
540
0
                AI_MD5_READ_TRIPLE(cur.vRotationQuat, &sz, elem.end, elem.iLineNumber);
541
0
                AI_MD5_SKIP_SPACES(&sz, elem.end, elem.iLineNumber);
542
0
                cur.fFOV = fast_atof(sz);
543
0
            }
544
0
        }
545
0
    }
546
    ASSIMP_LOG_DEBUG("MD5CameraParser end");
547
0
}