nuclear@0: /* nuclear@0: --------------------------------------------------------------------------- nuclear@0: Open Asset Import Library (assimp) nuclear@0: --------------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the following nuclear@0: conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: --------------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: /** @file MD5Parser.cpp nuclear@0: * @brief Implementation of the MD5 parser class nuclear@0: */ nuclear@0: #include "AssimpPCH.h" nuclear@0: nuclear@0: // internal headers nuclear@0: #include "MD5Loader.h" nuclear@0: #include "MaterialSystem.h" nuclear@0: #include "fast_atof.h" nuclear@0: #include "ParsingUtils.h" nuclear@0: #include "StringComparison.h" nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: using namespace Assimp::MD5; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Parse the segment structure fo a MD5 file nuclear@0: MD5Parser::MD5Parser(char* _buffer, unsigned int _fileSize ) nuclear@0: { nuclear@0: ai_assert(NULL != _buffer && 0 != _fileSize); nuclear@0: nuclear@0: buffer = _buffer; nuclear@0: fileSize = _fileSize; nuclear@0: lineNumber = 0; nuclear@0: nuclear@0: DefaultLogger::get()->debug("MD5Parser begin"); nuclear@0: nuclear@0: // parse the file header nuclear@0: ParseHeader(); nuclear@0: nuclear@0: // and read all sections until we're finished nuclear@0: bool running = true; nuclear@0: while (running) { nuclear@0: mSections.push_back(Section()); nuclear@0: Section& sec = mSections.back(); nuclear@0: if(!ParseSection(sec)) { nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if ( !DefaultLogger::isNullLogger()) { nuclear@0: char szBuffer[128]; // should be sufficiently large nuclear@0: ::sprintf(szBuffer,"MD5Parser end. Parsed %i sections",(int)mSections.size()); nuclear@0: DefaultLogger::get()->debug(szBuffer); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Report error to the log stream nuclear@0: /*static*/ void MD5Parser::ReportError (const char* error, unsigned int line) nuclear@0: { nuclear@0: char szBuffer[1024]; nuclear@0: ::sprintf(szBuffer,"[MD5] Line %i: %s",line,error); nuclear@0: throw DeadlyImportError(szBuffer); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Report warning to the log stream nuclear@0: /*static*/ void MD5Parser::ReportWarning (const char* warn, unsigned int line) nuclear@0: { nuclear@0: char szBuffer[1024]; nuclear@0: ::sprintf(szBuffer,"[MD5] Line %i: %s",line,warn); nuclear@0: DefaultLogger::get()->warn(szBuffer); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Parse and validate the MD5 header nuclear@0: void MD5Parser::ParseHeader() nuclear@0: { nuclear@0: // parse and validate the file version nuclear@0: SkipSpaces(); nuclear@0: if (!TokenMatch(buffer,"MD5Version",10)) { nuclear@0: ReportError("Invalid MD5 file: MD5Version tag has not been found"); nuclear@0: } nuclear@0: SkipSpaces(); nuclear@0: unsigned int iVer = ::strtoul10(buffer,(const char**)&buffer); nuclear@0: if (10 != iVer) { nuclear@0: ReportError("MD5 version tag is unknown (10 is expected)"); nuclear@0: } nuclear@0: SkipLine(); nuclear@0: nuclear@0: // print the command line options to the console nuclear@0: // FIX: can break the log length limit, so we need to be careful nuclear@0: char* sz = buffer; nuclear@0: while (!IsLineEnd( *buffer++)); nuclear@0: DefaultLogger::get()->info(std::string(sz,std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer-sz)))); nuclear@0: SkipSpacesAndLineEnd(); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Recursive MD5 parsing function nuclear@0: bool MD5Parser::ParseSection(Section& out) nuclear@0: { nuclear@0: // store the current line number for use in error messages nuclear@0: out.iLineNumber = lineNumber; nuclear@0: nuclear@0: // first parse the name of the section nuclear@0: char* sz = buffer; nuclear@0: while (!IsSpaceOrNewLine( *buffer))buffer++; nuclear@0: out.mName = std::string(sz,(uintptr_t)(buffer-sz)); nuclear@0: SkipSpaces(); nuclear@0: nuclear@0: bool running = true; nuclear@0: while (running) { nuclear@0: if ('{' == *buffer) { nuclear@0: // it is a normal section so read all lines nuclear@0: buffer++; nuclear@0: bool run = true; nuclear@0: while (run) nuclear@0: { nuclear@0: if (!SkipSpacesAndLineEnd()) { nuclear@0: return false; // seems this was the last section nuclear@0: } nuclear@0: if ('}' == *buffer) { nuclear@0: buffer++; nuclear@0: break; nuclear@0: } nuclear@0: nuclear@0: out.mElements.push_back(Element()); nuclear@0: Element& elem = out.mElements.back(); nuclear@0: nuclear@0: elem.iLineNumber = lineNumber; nuclear@0: elem.szStart = buffer; nuclear@0: nuclear@0: // terminate the line with zero nuclear@0: while (!IsLineEnd( *buffer))buffer++; nuclear@0: if (*buffer) { nuclear@0: ++lineNumber; nuclear@0: *buffer++ = '\0'; nuclear@0: } nuclear@0: } nuclear@0: break; nuclear@0: } nuclear@0: else if (!IsSpaceOrNewLine(*buffer)) { nuclear@0: // it is an element at global scope. Parse its value and go on nuclear@0: sz = buffer; nuclear@0: while (!IsSpaceOrNewLine( *buffer++)); nuclear@0: out.mGlobalValue = std::string(sz,(uintptr_t)(buffer-sz)); nuclear@0: continue; nuclear@0: } nuclear@0: break; nuclear@0: } nuclear@0: return SkipSpacesAndLineEnd(); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Some dirty macros just because they're so funny and easy to debug nuclear@0: nuclear@0: // skip all spaces ... handle EOL correctly nuclear@0: #define AI_MD5_SKIP_SPACES() if(!SkipSpaces(&sz)) \ nuclear@0: MD5Parser::ReportWarning("Unexpected end of line",(*eit).iLineNumber); nuclear@0: nuclear@0: // read a triple float in brackets: (1.0 1.0 1.0) nuclear@0: #define AI_MD5_READ_TRIPLE(vec) \ nuclear@0: AI_MD5_SKIP_SPACES(); \ nuclear@0: if ('(' != *sz++) \ nuclear@0: MD5Parser::ReportWarning("Unexpected token: ( was expected",(*eit).iLineNumber); \ nuclear@0: AI_MD5_SKIP_SPACES(); \ nuclear@0: sz = fast_atoreal_move(sz,(float&)vec.x); \ nuclear@0: AI_MD5_SKIP_SPACES(); \ nuclear@0: sz = fast_atoreal_move(sz,(float&)vec.y); \ nuclear@0: AI_MD5_SKIP_SPACES(); \ nuclear@0: sz = fast_atoreal_move(sz,(float&)vec.z); \ nuclear@0: AI_MD5_SKIP_SPACES(); \ nuclear@0: if (')' != *sz++) \ nuclear@0: MD5Parser::ReportWarning("Unexpected token: ) was expected",(*eit).iLineNumber); nuclear@0: nuclear@0: // parse a string, enclosed in quotation marks or not nuclear@0: #define AI_MD5_PARSE_STRING(out) \ nuclear@0: bool bQuota = (*sz == '\"'); \ nuclear@0: const char* szStart = sz; \ nuclear@0: while (!IsSpaceOrNewLine(*sz))++sz; \ nuclear@0: const char* szEnd = sz; \ nuclear@0: if (bQuota) { \ nuclear@0: szStart++; \ nuclear@0: if ('\"' != *(szEnd-=1)) { \ nuclear@0: MD5Parser::ReportWarning("Expected closing quotation marks in string", \ nuclear@0: (*eit).iLineNumber); \ nuclear@0: continue; \ nuclear@0: } \ nuclear@0: } \ nuclear@0: out.length = (size_t)(szEnd - szStart); \ nuclear@0: ::memcpy(out.data,szStart,out.length); \ nuclear@0: out.data[out.length] = '\0'; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // .MD5MESH parsing function nuclear@0: MD5MeshParser::MD5MeshParser(SectionList& mSections) nuclear@0: { nuclear@0: DefaultLogger::get()->debug("MD5MeshParser begin"); nuclear@0: nuclear@0: // now parse all sections nuclear@0: for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end();iter != iterEnd;++iter){ nuclear@0: if ( (*iter).mName == "numMeshes") { nuclear@0: mMeshes.reserve(::strtoul10((*iter).mGlobalValue.c_str())); nuclear@0: } nuclear@0: else if ( (*iter).mName == "numJoints") { nuclear@0: mJoints.reserve(::strtoul10((*iter).mGlobalValue.c_str())); nuclear@0: } nuclear@0: else if ((*iter).mName == "joints") { nuclear@0: // "origin" -1 ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000000 0.707107 ) nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end();eit != eitEnd; ++eit){ nuclear@0: mJoints.push_back(BoneDesc()); nuclear@0: BoneDesc& desc = mJoints.back(); nuclear@0: nuclear@0: const char* sz = (*eit).szStart; nuclear@0: AI_MD5_PARSE_STRING(desc.mName); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: nuclear@0: // negative values, at least -1, is allowed here nuclear@0: desc.mParentIndex = (int)strtol10(sz,&sz); nuclear@0: nuclear@0: AI_MD5_READ_TRIPLE(desc.mPositionXYZ); nuclear@0: AI_MD5_READ_TRIPLE(desc.mRotationQuat); // normalized quaternion, so w is not there nuclear@0: } nuclear@0: } nuclear@0: else if ((*iter).mName == "mesh") { nuclear@0: mMeshes.push_back(MeshDesc()); nuclear@0: MeshDesc& desc = mMeshes.back(); nuclear@0: nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end();eit != eitEnd; ++eit){ nuclear@0: const char* sz = (*eit).szStart; nuclear@0: nuclear@0: // shader attribute nuclear@0: if (TokenMatch(sz,"shader",6)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: AI_MD5_PARSE_STRING(desc.mShader); nuclear@0: } nuclear@0: // numverts attribute nuclear@0: else if (TokenMatch(sz,"numverts",8)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: desc.mVertices.resize(strtoul10(sz)); nuclear@0: } nuclear@0: // numtris attribute nuclear@0: else if (TokenMatch(sz,"numtris",7)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: desc.mFaces.resize(strtoul10(sz)); nuclear@0: } nuclear@0: // numweights attribute nuclear@0: else if (TokenMatch(sz,"numweights",10)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: desc.mWeights.resize(strtoul10(sz)); nuclear@0: } nuclear@0: // vert attribute nuclear@0: // "vert 0 ( 0.394531 0.513672 ) 0 1" nuclear@0: else if (TokenMatch(sz,"vert",4)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: const unsigned int idx = ::strtoul10(sz,&sz); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: if (idx >= desc.mVertices.size()) nuclear@0: desc.mVertices.resize(idx+1); nuclear@0: nuclear@0: VertexDesc& vert = desc.mVertices[idx]; nuclear@0: if ('(' != *sz++) nuclear@0: MD5Parser::ReportWarning("Unexpected token: ( was expected",(*eit).iLineNumber); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: sz = fast_atoreal_move(sz,(float&)vert.mUV.x); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: sz = fast_atoreal_move(sz,(float&)vert.mUV.y); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: if (')' != *sz++) nuclear@0: MD5Parser::ReportWarning("Unexpected token: ) was expected",(*eit).iLineNumber); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: vert.mFirstWeight = ::strtoul10(sz,&sz); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: vert.mNumWeights = ::strtoul10(sz,&sz); nuclear@0: } nuclear@0: // tri attribute nuclear@0: // "tri 0 15 13 12" nuclear@0: else if (TokenMatch(sz,"tri",3)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: const unsigned int idx = strtoul10(sz,&sz); nuclear@0: if (idx >= desc.mFaces.size()) nuclear@0: desc.mFaces.resize(idx+1); nuclear@0: nuclear@0: aiFace& face = desc.mFaces[idx]; nuclear@0: face.mIndices = new unsigned int[face.mNumIndices = 3]; nuclear@0: for (unsigned int i = 0; i < 3;++i) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: face.mIndices[i] = strtoul10(sz,&sz); nuclear@0: } nuclear@0: } nuclear@0: // weight attribute nuclear@0: // "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )" nuclear@0: else if (TokenMatch(sz,"weight",6)) { nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: const unsigned int idx = strtoul10(sz,&sz); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: if (idx >= desc.mWeights.size()) nuclear@0: desc.mWeights.resize(idx+1); nuclear@0: nuclear@0: WeightDesc& weight = desc.mWeights[idx]; nuclear@0: weight.mBone = strtoul10(sz,&sz); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: sz = fast_atoreal_move(sz,weight.mWeight); nuclear@0: AI_MD5_READ_TRIPLE(weight.vOffsetPosition); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: DefaultLogger::get()->debug("MD5MeshParser end"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // .MD5ANIM parsing function nuclear@0: MD5AnimParser::MD5AnimParser(SectionList& mSections) nuclear@0: { nuclear@0: DefaultLogger::get()->debug("MD5AnimParser begin"); nuclear@0: nuclear@0: fFrameRate = 24.0f; nuclear@0: mNumAnimatedComponents = UINT_MAX; nuclear@0: for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end();iter != iterEnd;++iter) { nuclear@0: if ((*iter).mName == "hierarchy") { nuclear@0: // "sheath" 0 63 6 nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end();eit != eitEnd; ++eit) { nuclear@0: mAnimatedBones.push_back ( AnimBoneDesc () ); nuclear@0: AnimBoneDesc& desc = mAnimatedBones.back(); nuclear@0: nuclear@0: const char* sz = (*eit).szStart; nuclear@0: AI_MD5_PARSE_STRING(desc.mName); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: nuclear@0: // parent index - negative values are allowed (at least -1) nuclear@0: desc.mParentIndex = ::strtol10(sz,&sz); nuclear@0: nuclear@0: // flags (highest is 2^6-1) nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: if(63 < (desc.iFlags = ::strtoul10(sz,&sz))){ nuclear@0: MD5Parser::ReportWarning("Invalid flag combination in hierarchy section",(*eit).iLineNumber); nuclear@0: } nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: nuclear@0: // index of the first animation keyframe component for this joint nuclear@0: desc.iFirstKeyIndex = ::strtoul10(sz,&sz); nuclear@0: } nuclear@0: } nuclear@0: else if((*iter).mName == "baseframe") { nuclear@0: // ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000242 0.707107 ) nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); eit != eitEnd; ++eit) { nuclear@0: const char* sz = (*eit).szStart; nuclear@0: nuclear@0: mBaseFrames.push_back ( BaseFrameDesc () ); nuclear@0: BaseFrameDesc& desc = mBaseFrames.back(); nuclear@0: nuclear@0: AI_MD5_READ_TRIPLE(desc.vPositionXYZ); nuclear@0: AI_MD5_READ_TRIPLE(desc.vRotationQuat); nuclear@0: } nuclear@0: } nuclear@0: else if((*iter).mName == "frame") { nuclear@0: if (!(*iter).mGlobalValue.length()) { nuclear@0: MD5Parser::ReportWarning("A frame section must have a frame index",(*iter).iLineNumber); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: mFrames.push_back ( FrameDesc () ); nuclear@0: FrameDesc& desc = mFrames.back(); nuclear@0: desc.iIndex = strtoul10((*iter).mGlobalValue.c_str()); nuclear@0: nuclear@0: // we do already know how much storage we will presumably need nuclear@0: if (UINT_MAX != mNumAnimatedComponents) { nuclear@0: desc.mValues.reserve(mNumAnimatedComponents); nuclear@0: } nuclear@0: nuclear@0: // now read all elements (continous list of floats) nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); eit != eitEnd; ++eit){ nuclear@0: const char* sz = (*eit).szStart; nuclear@0: while (SkipSpacesAndLineEnd(&sz)) { nuclear@0: float f;sz = fast_atoreal_move(sz,f); nuclear@0: desc.mValues.push_back(f); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if((*iter).mName == "numFrames") { nuclear@0: mFrames.reserve(strtoul10((*iter).mGlobalValue.c_str())); nuclear@0: } nuclear@0: else if((*iter).mName == "numJoints") { nuclear@0: const unsigned int num = strtoul10((*iter).mGlobalValue.c_str()); nuclear@0: mAnimatedBones.reserve(num); nuclear@0: nuclear@0: // try to guess the number of animated components if that element is not given nuclear@0: if (UINT_MAX == mNumAnimatedComponents) { nuclear@0: mNumAnimatedComponents = num * 6; nuclear@0: } nuclear@0: } nuclear@0: else if((*iter).mName == "numAnimatedComponents") { nuclear@0: mAnimatedBones.reserve( strtoul10((*iter).mGlobalValue.c_str())); nuclear@0: } nuclear@0: else if((*iter).mName == "frameRate") { nuclear@0: fast_atoreal_move((*iter).mGlobalValue.c_str(),fFrameRate); nuclear@0: } nuclear@0: } nuclear@0: DefaultLogger::get()->debug("MD5AnimParser end"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // .MD5CAMERA parsing function nuclear@0: MD5CameraParser::MD5CameraParser(SectionList& mSections) nuclear@0: { nuclear@0: DefaultLogger::get()->debug("MD5CameraParser begin"); nuclear@0: fFrameRate = 24.0f; nuclear@0: nuclear@0: for (SectionList::const_iterator iter = mSections.begin(), iterEnd = mSections.end();iter != iterEnd;++iter) { nuclear@0: if ((*iter).mName == "numFrames") { nuclear@0: frames.reserve(strtoul10((*iter).mGlobalValue.c_str())); nuclear@0: } nuclear@0: else if ((*iter).mName == "frameRate") { nuclear@0: fFrameRate = fast_atof ((*iter).mGlobalValue.c_str()); nuclear@0: } nuclear@0: else if ((*iter).mName == "numCuts") { nuclear@0: cuts.reserve(strtoul10((*iter).mGlobalValue.c_str())); nuclear@0: } nuclear@0: else if ((*iter).mName == "cuts") { nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); eit != eitEnd; ++eit){ nuclear@0: cuts.push_back(strtoul10((*eit).szStart)+1); nuclear@0: } nuclear@0: } nuclear@0: else if ((*iter).mName == "camera") { nuclear@0: for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); eit != eitEnd; ++eit){ nuclear@0: const char* sz = (*eit).szStart; nuclear@0: nuclear@0: frames.push_back(CameraAnimFrameDesc()); nuclear@0: CameraAnimFrameDesc& cur = frames.back(); nuclear@0: AI_MD5_READ_TRIPLE(cur.vPositionXYZ); nuclear@0: AI_MD5_READ_TRIPLE(cur.vRotationQuat); nuclear@0: AI_MD5_SKIP_SPACES(); nuclear@0: cur.fFOV = fast_atof(sz); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: DefaultLogger::get()->debug("MD5CameraParser end"); nuclear@0: } nuclear@0: