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