1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2021, assimp team
7 
8 
9 
10 All rights reserved.
11 
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15 
16 * Redistributions of source code must retain the above
17   copyright notice, this list of conditions and the
18   following disclaimer.
19 
20 * Redistributions in binary form must reproduce the above
21   copyright notice, this list of conditions and the
22   following disclaimer in the documentation and/or other
23   materials provided with the distribution.
24 
25 * Neither the name of the assimp team, nor the names of its
26   contributors may be used to endorse or promote products
27   derived from this software without specific prior
28   written permission of the assimp team.
29 
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 
44 /** @file  MD5Parser.cpp
45  *  @brief Implementation of the MD5 parser class
46  */
47 
48 // internal headers
49 #include "AssetLib/MD5/MD5Loader.h"
50 #include "Material/MaterialSystem.h"
51 
52 #include <assimp/ParsingUtils.h>
53 #include <assimp/StringComparison.h>
54 #include <assimp/fast_atof.h>
55 #include <assimp/mesh.h>
56 #include <assimp/DefaultLogger.hpp>
57 
58 using namespace Assimp;
59 using namespace Assimp::MD5;
60 
61 // ------------------------------------------------------------------------------------------------
62 // Parse the segment structure for an MD5 file
MD5Parser(char * _buffer,unsigned int _fileSize)63 MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) {
64     ai_assert(nullptr != _buffer);
65     ai_assert(0 != _fileSize);
66 
67     buffer = _buffer;
68     fileSize = _fileSize;
69     lineNumber = 0;
70 
71     ASSIMP_LOG_DEBUG("MD5Parser begin");
72 
73     // parse the file header
74     ParseHeader();
75 
76     // and read all sections until we're finished
77     bool running = true;
78     while (running) {
79         mSections.push_back(Section());
80         Section &sec = mSections.back();
81         if (!ParseSection(sec)) {
82             break;
83         }
84     }
85 
86     if (!DefaultLogger::isNullLogger()) {
87         char szBuffer[128]; // should be sufficiently large
88         ::ai_snprintf(szBuffer, 128, "MD5Parser end. Parsed %i sections", (int)mSections.size());
89         ASSIMP_LOG_DEBUG(szBuffer);
90     }
91 }
92 
93 // ------------------------------------------------------------------------------------------------
94 // Report error to the log stream
ReportError(const char * error,unsigned int line)95 /*static*/ AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) {
96     char szBuffer[1024];
97     ::ai_snprintf(szBuffer, 1024, "[MD5] Line %u: %s", line, error);
98     throw DeadlyImportError(szBuffer);
99 }
100 
101 // ------------------------------------------------------------------------------------------------
102 // Report warning to the log stream
ReportWarning(const char * warn,unsigned int line)103 /*static*/ void MD5Parser::ReportWarning(const char *warn, unsigned int line) {
104     char szBuffer[1024];
105     ::sprintf(szBuffer, "[MD5] Line %u: %s", line, warn);
106     ASSIMP_LOG_WARN(szBuffer);
107 }
108 
109 // ------------------------------------------------------------------------------------------------
110 // Parse and validate the MD5 header
ParseHeader()111 void MD5Parser::ParseHeader() {
112     // parse and validate the file version
113     SkipSpaces();
114     if (!TokenMatch(buffer, "MD5Version", 10)) {
115         ReportError("Invalid MD5 file: MD5Version tag has not been found");
116     }
117     SkipSpaces();
118     unsigned int iVer = ::strtoul10(buffer, (const char **)&buffer);
119     if (10 != iVer) {
120         ReportError("MD5 version tag is unknown (10 is expected)");
121     }
122     SkipLine();
123 
124     // print the command line options to the console
125     // FIX: can break the log length limit, so we need to be careful
126     char *sz = buffer;
127     while (!IsLineEnd(*buffer++))
128         ;
129     ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz))));
130     SkipSpacesAndLineEnd();
131 }
132 
133 // ------------------------------------------------------------------------------------------------
134 // Recursive MD5 parsing function
ParseSection(Section & out)135 bool MD5Parser::ParseSection(Section &out) {
136     // store the current line number for use in error messages
137     out.iLineNumber = lineNumber;
138 
139     // first parse the name of the section
140     char *sz = buffer;
141     while (!IsSpaceOrNewLine(*buffer))
142         buffer++;
143     out.mName = std::string(sz, (uintptr_t)(buffer - sz));
144     SkipSpaces();
145 
146     bool running = true;
147     while (running) {
148         if ('{' == *buffer) {
149             // it is a normal section so read all lines
150             buffer++;
151             bool run = true;
152             while (run) {
153                 if (!SkipSpacesAndLineEnd()) {
154                     return false; // seems this was the last section
155                 }
156                 if ('}' == *buffer) {
157                     buffer++;
158                     break;
159                 }
160 
161                 out.mElements.push_back(Element());
162                 Element &elem = out.mElements.back();
163 
164                 elem.iLineNumber = lineNumber;
165                 elem.szStart = buffer;
166 
167                 // terminate the line with zero
168                 while (!IsLineEnd(*buffer))
169                     buffer++;
170                 if (*buffer) {
171                     ++lineNumber;
172                     *buffer++ = '\0';
173                 }
174             }
175             break;
176         } else if (!IsSpaceOrNewLine(*buffer)) {
177             // it is an element at global scope. Parse its value and go on
178             sz = buffer;
179             while (!IsSpaceOrNewLine(*buffer++))
180                 ;
181             out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz));
182             continue;
183         }
184         break;
185     }
186     return SkipSpacesAndLineEnd();
187 }
188 
189 // ------------------------------------------------------------------------------------------------
190 // Some dirty macros just because they're so funny and easy to debug
191 
192 // skip all spaces ... handle EOL correctly
193 #define AI_MD5_SKIP_SPACES() \
194     if (!SkipSpaces(&sz))    \
195         MD5Parser::ReportWarning("Unexpected end of line", elem.iLineNumber);
196 
197 // read a triple float in brackets: (1.0 1.0 1.0)
198 #define AI_MD5_READ_TRIPLE(vec)                                                         \
199     AI_MD5_SKIP_SPACES();                                                               \
200     if ('(' != *sz++)                                                                   \
201         MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber); \
202     AI_MD5_SKIP_SPACES();                                                               \
203     sz = fast_atoreal_move<float>(sz, (float &)vec.x);                                  \
204     AI_MD5_SKIP_SPACES();                                                               \
205     sz = fast_atoreal_move<float>(sz, (float &)vec.y);                                  \
206     AI_MD5_SKIP_SPACES();                                                               \
207     sz = fast_atoreal_move<float>(sz, (float &)vec.z);                                  \
208     AI_MD5_SKIP_SPACES();                                                               \
209     if (')' != *sz++)                                                                   \
210         MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber);
211 
212 // parse a string, enclosed in quotation marks or not
213 #define AI_MD5_PARSE_STRING(out)                                                   \
214     bool bQuota = (*sz == '\"');                                                   \
215     const char *szStart = sz;                                                      \
216     while (!IsSpaceOrNewLine(*sz))                                                 \
217         ++sz;                                                                      \
218     const char *szEnd = sz;                                                        \
219     if (bQuota) {                                                                  \
220         szStart++;                                                                 \
221         if ('\"' != *(szEnd -= 1)) {                                               \
222             MD5Parser::ReportWarning("Expected closing quotation marks in string", \
223                     elem.iLineNumber);                                             \
224             continue;                                                              \
225         }                                                                          \
226     }                                                                              \
227     out.length = (size_t)(szEnd - szStart);                                        \
228     ::memcpy(out.data, szStart, out.length);                                       \
229     out.data[out.length] = '\0';
230 
231 // parse a string, enclosed in quotation marks
232 #define AI_MD5_PARSE_STRING_IN_QUOTATION(out)  \
233     while ('\"' != *sz)                        \
234         ++sz;                                  \
235     const char *szStart = ++sz;                \
236     while ('\"' != *sz)                        \
237         ++sz;                                  \
238     const char *szEnd = (sz++);                \
239     out.length = (ai_uint32)(szEnd - szStart); \
240     ::memcpy(out.data, szStart, out.length);   \
241     out.data[out.length] = '\0';
242 // ------------------------------------------------------------------------------------------------
243 // .MD5MESH parsing function
MD5MeshParser(SectionList & mSections)244 MD5MeshParser::MD5MeshParser(SectionList &mSections) {
245     ASSIMP_LOG_DEBUG("MD5MeshParser begin");
246 
247     // now parse all sections
248     for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) {
249         if ((*iter).mName == "numMeshes") {
250             mMeshes.reserve(::strtoul10((*iter).mGlobalValue.c_str()));
251         } else if ((*iter).mName == "numJoints") {
252             mJoints.reserve(::strtoul10((*iter).mGlobalValue.c_str()));
253         } else if ((*iter).mName == "joints") {
254             // "origin" -1 ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000000 0.707107 )
255             for (const auto &elem : (*iter).mElements) {
256                 mJoints.push_back(BoneDesc());
257                 BoneDesc &desc = mJoints.back();
258 
259                 const char *sz = elem.szStart;
260                 AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mName);
261                 AI_MD5_SKIP_SPACES();
262 
263                 // negative values, at least -1, is allowed here
264                 desc.mParentIndex = (int)strtol10(sz, &sz);
265 
266                 AI_MD5_READ_TRIPLE(desc.mPositionXYZ);
267                 AI_MD5_READ_TRIPLE(desc.mRotationQuat); // normalized quaternion, so w is not there
268             }
269         } else if ((*iter).mName == "mesh") {
270             mMeshes.push_back(MeshDesc());
271             MeshDesc &desc = mMeshes.back();
272 
273             for (const auto &elem : (*iter).mElements) {
274                 const char *sz = elem.szStart;
275 
276                 // shader attribute
277                 if (TokenMatch(sz, "shader", 6)) {
278                     AI_MD5_SKIP_SPACES();
279                     AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mShader);
280                 }
281                 // numverts attribute
282                 else if (TokenMatch(sz, "numverts", 8)) {
283                     AI_MD5_SKIP_SPACES();
284                     desc.mVertices.resize(strtoul10(sz));
285                 }
286                 // numtris attribute
287                 else if (TokenMatch(sz, "numtris", 7)) {
288                     AI_MD5_SKIP_SPACES();
289                     desc.mFaces.resize(strtoul10(sz));
290                 }
291                 // numweights attribute
292                 else if (TokenMatch(sz, "numweights", 10)) {
293                     AI_MD5_SKIP_SPACES();
294                     desc.mWeights.resize(strtoul10(sz));
295                 }
296                 // vert attribute
297                 // "vert 0 ( 0.394531 0.513672 ) 0 1"
298                 else if (TokenMatch(sz, "vert", 4)) {
299                     AI_MD5_SKIP_SPACES();
300                     const unsigned int idx = ::strtoul10(sz, &sz);
301                     AI_MD5_SKIP_SPACES();
302                     if (idx >= desc.mVertices.size())
303                         desc.mVertices.resize(idx + 1);
304 
305                     VertexDesc &vert = desc.mVertices[idx];
306                     if ('(' != *sz++)
307                         MD5Parser::ReportWarning("Unexpected token: ( was expected", elem.iLineNumber);
308                     AI_MD5_SKIP_SPACES();
309                     sz = fast_atoreal_move<float>(sz, (float &)vert.mUV.x);
310                     AI_MD5_SKIP_SPACES();
311                     sz = fast_atoreal_move<float>(sz, (float &)vert.mUV.y);
312                     AI_MD5_SKIP_SPACES();
313                     if (')' != *sz++)
314                         MD5Parser::ReportWarning("Unexpected token: ) was expected", elem.iLineNumber);
315                     AI_MD5_SKIP_SPACES();
316                     vert.mFirstWeight = ::strtoul10(sz, &sz);
317                     AI_MD5_SKIP_SPACES();
318                     vert.mNumWeights = ::strtoul10(sz, &sz);
319                 }
320                 // tri attribute
321                 // "tri 0 15 13 12"
322                 else if (TokenMatch(sz, "tri", 3)) {
323                     AI_MD5_SKIP_SPACES();
324                     const unsigned int idx = strtoul10(sz, &sz);
325                     if (idx >= desc.mFaces.size())
326                         desc.mFaces.resize(idx + 1);
327 
328                     aiFace &face = desc.mFaces[idx];
329                     face.mIndices = new unsigned int[face.mNumIndices = 3];
330                     for (unsigned int i = 0; i < 3; ++i) {
331                         AI_MD5_SKIP_SPACES();
332                         face.mIndices[i] = strtoul10(sz, &sz);
333                     }
334                 }
335                 // weight attribute
336                 // "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )"
337                 else if (TokenMatch(sz, "weight", 6)) {
338                     AI_MD5_SKIP_SPACES();
339                     const unsigned int idx = strtoul10(sz, &sz);
340                     AI_MD5_SKIP_SPACES();
341                     if (idx >= desc.mWeights.size())
342                         desc.mWeights.resize(idx + 1);
343 
344                     WeightDesc &weight = desc.mWeights[idx];
345                     weight.mBone = strtoul10(sz, &sz);
346                     AI_MD5_SKIP_SPACES();
347                     sz = fast_atoreal_move<float>(sz, weight.mWeight);
348                     AI_MD5_READ_TRIPLE(weight.vOffsetPosition);
349                 }
350             }
351         }
352     }
353     ASSIMP_LOG_DEBUG("MD5MeshParser end");
354 }
355 
356 // ------------------------------------------------------------------------------------------------
357 // .MD5ANIM parsing function
MD5AnimParser(SectionList & mSections)358 MD5AnimParser::MD5AnimParser(SectionList &mSections) {
359     ASSIMP_LOG_DEBUG("MD5AnimParser begin");
360 
361     fFrameRate = 24.0f;
362     mNumAnimatedComponents = UINT_MAX;
363     for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) {
364         if ((*iter).mName == "hierarchy") {
365             // "sheath" 0 63 6
366             for (const auto &elem : (*iter).mElements) {
367                 mAnimatedBones.push_back(AnimBoneDesc());
368                 AnimBoneDesc &desc = mAnimatedBones.back();
369 
370                 const char *sz = elem.szStart;
371                 AI_MD5_PARSE_STRING_IN_QUOTATION(desc.mName);
372                 AI_MD5_SKIP_SPACES();
373 
374                 // parent index - negative values are allowed (at least -1)
375                 desc.mParentIndex = ::strtol10(sz, &sz);
376 
377                 // flags (highest is 2^6-1)
378                 AI_MD5_SKIP_SPACES();
379                 if (63 < (desc.iFlags = ::strtoul10(sz, &sz))) {
380                     MD5Parser::ReportWarning("Invalid flag combination in hierarchy section", elem.iLineNumber);
381                 }
382                 AI_MD5_SKIP_SPACES();
383 
384                 // index of the first animation keyframe component for this joint
385                 desc.iFirstKeyIndex = ::strtoul10(sz, &sz);
386             }
387         } else if ((*iter).mName == "baseframe") {
388             // ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000242 0.707107 )
389             for (const auto &elem : (*iter).mElements) {
390                 const char *sz = elem.szStart;
391 
392                 mBaseFrames.push_back(BaseFrameDesc());
393                 BaseFrameDesc &desc = mBaseFrames.back();
394 
395                 AI_MD5_READ_TRIPLE(desc.vPositionXYZ);
396                 AI_MD5_READ_TRIPLE(desc.vRotationQuat);
397             }
398         } else if ((*iter).mName == "frame") {
399             if (!(*iter).mGlobalValue.length()) {
400                 MD5Parser::ReportWarning("A frame section must have a frame index", (*iter).iLineNumber);
401                 continue;
402             }
403 
404             mFrames.push_back(FrameDesc());
405             FrameDesc &desc = mFrames.back();
406             desc.iIndex = strtoul10((*iter).mGlobalValue.c_str());
407 
408             // we do already know how much storage we will presumably need
409             if (UINT_MAX != mNumAnimatedComponents) {
410                 desc.mValues.reserve(mNumAnimatedComponents);
411             }
412 
413             // now read all elements (continuous list of floats)
414             for (const auto &elem : (*iter).mElements) {
415                 const char *sz = elem.szStart;
416                 while (SkipSpacesAndLineEnd(&sz)) {
417                     float f;
418                     sz = fast_atoreal_move<float>(sz, f);
419                     desc.mValues.push_back(f);
420                 }
421             }
422         } else if ((*iter).mName == "numFrames") {
423             mFrames.reserve(strtoul10((*iter).mGlobalValue.c_str()));
424         } else if ((*iter).mName == "numJoints") {
425             const unsigned int num = strtoul10((*iter).mGlobalValue.c_str());
426             mAnimatedBones.reserve(num);
427 
428             // try to guess the number of animated components if that element is not given
429             if (UINT_MAX == mNumAnimatedComponents) {
430                 mNumAnimatedComponents = num * 6;
431             }
432         } else if ((*iter).mName == "numAnimatedComponents") {
433             mAnimatedBones.reserve(strtoul10((*iter).mGlobalValue.c_str()));
434         } else if ((*iter).mName == "frameRate") {
435             fast_atoreal_move<float>((*iter).mGlobalValue.c_str(), fFrameRate);
436         }
437     }
438     ASSIMP_LOG_DEBUG("MD5AnimParser end");
439 }
440 
441 // ------------------------------------------------------------------------------------------------
442 // .MD5CAMERA parsing function
MD5CameraParser(SectionList & mSections)443 MD5CameraParser::MD5CameraParser(SectionList &mSections) {
444     ASSIMP_LOG_DEBUG("MD5CameraParser begin");
445     fFrameRate = 24.0f;
446 
447     for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end(); iter != iterEnd; ++iter) {
448         if ((*iter).mName == "numFrames") {
449             frames.reserve(strtoul10((*iter).mGlobalValue.c_str()));
450         } else if ((*iter).mName == "frameRate") {
451             fFrameRate = fast_atof((*iter).mGlobalValue.c_str());
452         } else if ((*iter).mName == "numCuts") {
453             cuts.reserve(strtoul10((*iter).mGlobalValue.c_str()));
454         } else if ((*iter).mName == "cuts") {
455             for (const auto &elem : (*iter).mElements) {
456                 cuts.push_back(strtoul10(elem.szStart) + 1);
457             }
458         } else if ((*iter).mName == "camera") {
459             for (const auto &elem : (*iter).mElements) {
460                 const char *sz = elem.szStart;
461 
462                 frames.push_back(CameraAnimFrameDesc());
463                 CameraAnimFrameDesc &cur = frames.back();
464                 AI_MD5_READ_TRIPLE(cur.vPositionXYZ);
465                 AI_MD5_READ_TRIPLE(cur.vRotationQuat);
466                 AI_MD5_SKIP_SPACES();
467                 cur.fFOV = fast_atof(sz);
468             }
469         }
470     }
471     ASSIMP_LOG_DEBUG("MD5CameraParser end");
472 }
473