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