| /* |
| Open Asset Import Library (assimp) |
| ---------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| /** @file FBXParser.cpp |
| * @brief Implementation of the FBX parser and the rudimentary DOM that we use |
| */ |
| |
| #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER |
| |
| |
| #ifdef ASSIMP_BUILD_NO_OWN_ZLIB |
| # include <zlib.h> |
| #else |
| # include "../contrib/zlib/zlib.h" |
| #endif |
| |
| #include "FBXTokenizer.h" |
| #include "FBXParser.h" |
| #include "FBXUtil.h" |
| |
| #include "ParsingUtils.h" |
| #include "fast_atof.h" |
| #include "ByteSwapper.h" |
| |
| #include <iostream> |
| |
| using namespace Assimp; |
| using namespace Assimp::FBX; |
| |
| namespace { |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // signal parse error, this is always unrecoverable. Throws DeadlyImportError. |
| AI_WONT_RETURN void ParseError(const std::string& message, const Token& token) AI_WONT_RETURN_SUFFIX; |
| AI_WONT_RETURN void ParseError(const std::string& message, const Token& token) |
| { |
| throw DeadlyImportError(Util::AddTokenText("FBX-Parser",message,&token)); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| AI_WONT_RETURN void ParseError(const std::string& message, const Element* element = NULL) AI_WONT_RETURN_SUFFIX; |
| AI_WONT_RETURN void ParseError(const std::string& message, const Element* element) |
| { |
| if(element) { |
| ParseError(message,element->KeyToken()); |
| } |
| throw DeadlyImportError("FBX-Parser " + message); |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| void ParseError(const std::string& message, TokenPtr token) |
| { |
| if(token) { |
| ParseError(message, *token); |
| } |
| ParseError(message); |
| } |
| |
| // Initially, we did reinterpret_cast, breaking strict aliasing rules. |
| // This actually caused trouble on Android, so let's be safe this time. |
| // https://github.com/assimp/assimp/issues/24 |
| template <typename T> |
| T SafeParse(const char* data, const char* end) { |
| // Actual size validation happens during Tokenization so |
| // this is valid as an assertion. |
| (void)(end); |
| ai_assert(static_cast<size_t>(end - data) >= sizeof(T)); |
| T result = static_cast<T>(0); |
| ::memcpy(&result, data, sizeof(T)); |
| return result; |
| } |
| } |
| |
| namespace Assimp { |
| namespace FBX { |
| |
| // ------------------------------------------------------------------------------------------------ |
| Element::Element(const Token& key_token, Parser& parser) |
| : key_token(key_token) |
| { |
| TokenPtr n = NULL; |
| do { |
| n = parser.AdvanceToNextToken(); |
| if(!n) { |
| ParseError("unexpected end of file, expected closing bracket",parser.LastToken()); |
| } |
| |
| if (n->Type() == TokenType_DATA) { |
| tokens.push_back(n); |
| TokenPtr prev = n; |
| n = parser.AdvanceToNextToken(); |
| if(!n) { |
| ParseError("unexpected end of file, expected bracket, comma or key",parser.LastToken()); |
| } |
| |
| const TokenType ty = n->Type(); |
| |
| // some exporters are missing a comma on the next line |
| if (ty == TokenType_DATA && prev->Type() == TokenType_DATA && (n->Line() == prev->Line() + 1)) { |
| tokens.push_back(n); |
| continue; |
| } |
| |
| if (ty != TokenType_OPEN_BRACKET && ty != TokenType_CLOSE_BRACKET && ty != TokenType_COMMA && ty != TokenType_KEY) { |
| ParseError("unexpected token; expected bracket, comma or key",n); |
| } |
| } |
| |
| if (n->Type() == TokenType_OPEN_BRACKET) { |
| compound.reset(new Scope(parser)); |
| |
| // current token should be a TOK_CLOSE_BRACKET |
| n = parser.CurrentToken(); |
| ai_assert(n); |
| |
| if (n->Type() != TokenType_CLOSE_BRACKET) { |
| ParseError("expected closing bracket",n); |
| } |
| |
| parser.AdvanceToNextToken(); |
| return; |
| } |
| } |
| while(n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Element::~Element() |
| { |
| // no need to delete tokens, they are owned by the parser |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Scope::Scope(Parser& parser,bool topLevel) |
| { |
| if(!topLevel) { |
| TokenPtr t = parser.CurrentToken(); |
| if (t->Type() != TokenType_OPEN_BRACKET) { |
| ParseError("expected open bracket",t); |
| } |
| } |
| |
| TokenPtr n = parser.AdvanceToNextToken(); |
| if(n == NULL) { |
| ParseError("unexpected end of file"); |
| } |
| |
| // note: empty scopes are allowed |
| while(n->Type() != TokenType_CLOSE_BRACKET) { |
| if (n->Type() != TokenType_KEY) { |
| ParseError("unexpected token, expected TOK_KEY",n); |
| } |
| |
| const std::string& str = n->StringContents(); |
| elements.insert(ElementMap::value_type(str,new_Element(*n,parser))); |
| |
| // Element() should stop at the next Key token (or right after a Close token) |
| n = parser.CurrentToken(); |
| if(n == NULL) { |
| if (topLevel) { |
| return; |
| } |
| ParseError("unexpected end of file",parser.LastToken()); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Scope::~Scope() |
| { |
| for(ElementMap::value_type& v : elements) { |
| delete v.second; |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| Parser::Parser (const TokenList& tokens, bool is_binary) |
| : tokens(tokens) |
| , last() |
| , current() |
| , cursor(tokens.begin()) |
| , is_binary(is_binary) |
| { |
| root.reset(new Scope(*this,true)); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Parser::~Parser() |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| TokenPtr Parser::AdvanceToNextToken() |
| { |
| last = current; |
| if (cursor == tokens.end()) { |
| current = NULL; |
| } else { |
| current = *cursor++; |
| } |
| return current; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| TokenPtr Parser::CurrentToken() const |
| { |
| return current; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| TokenPtr Parser::LastToken() const |
| { |
| return last; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| uint64_t ParseTokenAsID(const Token& t, const char*& err_out) |
| { |
| err_out = NULL; |
| |
| if (t.Type() != TokenType_DATA) { |
| err_out = "expected TOK_DATA token"; |
| return 0L; |
| } |
| |
| if(t.IsBinary()) |
| { |
| const char* data = t.begin(); |
| if (data[0] != 'L') { |
| err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; |
| return 0L; |
| } |
| |
| BE_NCONST uint64_t id = SafeParse<uint64_t>(data+1, t.end()); |
| AI_SWAP8(id); |
| return id; |
| } |
| |
| // XXX: should use size_t here |
| unsigned int length = static_cast<unsigned int>(t.end() - t.begin()); |
| ai_assert(length > 0); |
| |
| const char* out = nullptr; |
| const uint64_t id = strtoul10_64(t.begin(),&out,&length); |
| if (out > t.end()) { |
| err_out = "failed to parse ID (text)"; |
| return 0L; |
| } |
| |
| return id; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| size_t ParseTokenAsDim(const Token& t, const char*& err_out) |
| { |
| // same as ID parsing, except there is a trailing asterisk |
| err_out = NULL; |
| |
| if (t.Type() != TokenType_DATA) { |
| err_out = "expected TOK_DATA token"; |
| return 0; |
| } |
| |
| if(t.IsBinary()) |
| { |
| const char* data = t.begin(); |
| if (data[0] != 'L') { |
| err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)"; |
| return 0; |
| } |
| |
| BE_NCONST uint64_t id = SafeParse<uint64_t>(data+1, t.end()); |
| AI_SWAP8(id); |
| return static_cast<size_t>(id); |
| } |
| |
| if(*t.begin() != '*') { |
| err_out = "expected asterisk before array dimension"; |
| return 0; |
| } |
| |
| // XXX: should use size_t here |
| unsigned int length = static_cast<unsigned int>(t.end() - t.begin()); |
| if(length == 0) { |
| err_out = "expected valid integer number after asterisk"; |
| return 0; |
| } |
| |
| const char* out = nullptr; |
| const size_t id = static_cast<size_t>(strtoul10_64(t.begin() + 1,&out,&length)); |
| if (out > t.end()) { |
| err_out = "failed to parse ID"; |
| return 0; |
| } |
| |
| return id; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| float ParseTokenAsFloat(const Token& t, const char*& err_out) |
| { |
| err_out = NULL; |
| |
| if (t.Type() != TokenType_DATA) { |
| err_out = "expected TOK_DATA token"; |
| return 0.0f; |
| } |
| |
| if(t.IsBinary()) |
| { |
| const char* data = t.begin(); |
| if (data[0] != 'F' && data[0] != 'D') { |
| err_out = "failed to parse F(loat) or D(ouble), unexpected data type (binary)"; |
| return 0.0f; |
| } |
| |
| if (data[0] == 'F') { |
| return SafeParse<float>(data+1, t.end()); |
| } |
| else { |
| return static_cast<float>( SafeParse<double>(data+1, t.end()) ); |
| } |
| } |
| |
| // need to copy the input string to a temporary buffer |
| // first - next in the fbx token stream comes ',', |
| // which fast_atof could interpret as decimal point. |
| #define MAX_FLOAT_LENGTH 31 |
| char temp[MAX_FLOAT_LENGTH + 1]; |
| const size_t length = static_cast<size_t>(t.end()-t.begin()); |
| std::copy(t.begin(),t.end(),temp); |
| temp[std::min(static_cast<size_t>(MAX_FLOAT_LENGTH),length)] = '\0'; |
| |
| return fast_atof(temp); |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| int ParseTokenAsInt(const Token& t, const char*& err_out) |
| { |
| err_out = NULL; |
| |
| if (t.Type() != TokenType_DATA) { |
| err_out = "expected TOK_DATA token"; |
| return 0; |
| } |
| |
| if(t.IsBinary()) |
| { |
| const char* data = t.begin(); |
| if (data[0] != 'I') { |
| err_out = "failed to parse I(nt), unexpected data type (binary)"; |
| return 0; |
| } |
| |
| BE_NCONST int32_t ival = SafeParse<int32_t>(data+1, t.end()); |
| AI_SWAP4(ival); |
| return static_cast<int>(ival); |
| } |
| |
| ai_assert(static_cast<size_t>(t.end() - t.begin()) > 0); |
| |
| const char* out; |
| const int intval = strtol10(t.begin(),&out); |
| if (out != t.end()) { |
| err_out = "failed to parse ID"; |
| return 0; |
| } |
| |
| return intval; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| int64_t ParseTokenAsInt64(const Token& t, const char*& err_out) |
| { |
| err_out = NULL; |
| |
| if (t.Type() != TokenType_DATA) { |
| err_out = "expected TOK_DATA token"; |
| return 0L; |
| } |
| |
| if (t.IsBinary()) |
| { |
| const char* data = t.begin(); |
| if (data[0] != 'L') { |
| err_out = "failed to parse Int64, unexpected data type"; |
| return 0L; |
| } |
| |
| BE_NCONST int64_t id = SafeParse<int64_t>(data + 1, t.end()); |
| AI_SWAP8(id); |
| return id; |
| } |
| |
| // XXX: should use size_t here |
| unsigned int length = static_cast<unsigned int>(t.end() - t.begin()); |
| ai_assert(length > 0); |
| |
| const char* out = nullptr; |
| const int64_t id = strtol10_64(t.begin(), &out, &length); |
| if (out > t.end()) { |
| err_out = "failed to parse Int64 (text)"; |
| return 0L; |
| } |
| |
| return id; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::string ParseTokenAsString(const Token& t, const char*& err_out) |
| { |
| err_out = NULL; |
| |
| if (t.Type() != TokenType_DATA) { |
| err_out = "expected TOK_DATA token"; |
| return ""; |
| } |
| |
| if(t.IsBinary()) |
| { |
| const char* data = t.begin(); |
| if (data[0] != 'S') { |
| err_out = "failed to parse S(tring), unexpected data type (binary)"; |
| return ""; |
| } |
| |
| // read string length |
| BE_NCONST int32_t len = SafeParse<int32_t>(data+1, t.end()); |
| AI_SWAP4(len); |
| |
| ai_assert(t.end() - data == 5 + len); |
| return std::string(data + 5, len); |
| } |
| |
| const size_t length = static_cast<size_t>(t.end() - t.begin()); |
| if(length < 2) { |
| err_out = "token is too short to hold a string"; |
| return ""; |
| } |
| |
| const char* s = t.begin(), *e = t.end() - 1; |
| if (*s != '\"' || *e != '\"') { |
| err_out = "expected double quoted string"; |
| return ""; |
| } |
| |
| return std::string(s+1,length-2); |
| } |
| |
| |
| namespace { |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read the type code and element count of a binary data array and stop there |
| void ReadBinaryDataArrayHead(const char*& data, const char* end, char& type, uint32_t& count, |
| const Element& el) |
| { |
| if (static_cast<size_t>(end-data) < 5) { |
| ParseError("binary data array is too short, need five (5) bytes for type signature and element count",&el); |
| } |
| |
| // data type |
| type = *data; |
| |
| // read number of elements |
| BE_NCONST uint32_t len = SafeParse<uint32_t>(data+1, end); |
| AI_SWAP4(len); |
| |
| count = len; |
| data += 5; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read binary data array, assume cursor points to the 'compression mode' field (i.e. behind the header) |
| void ReadBinaryDataArray(char type, uint32_t count, const char*& data, const char* end, |
| std::vector<char>& buff, |
| const Element& /*el*/) |
| { |
| BE_NCONST uint32_t encmode = SafeParse<uint32_t>(data, end); |
| AI_SWAP4(encmode); |
| data += 4; |
| |
| // next comes the compressed length |
| BE_NCONST uint32_t comp_len = SafeParse<uint32_t>(data, end); |
| AI_SWAP4(comp_len); |
| data += 4; |
| |
| ai_assert(data + comp_len == end); |
| |
| // determine the length of the uncompressed data by looking at the type signature |
| uint32_t stride = 0; |
| switch(type) |
| { |
| case 'f': |
| case 'i': |
| stride = 4; |
| break; |
| |
| case 'd': |
| case 'l': |
| stride = 8; |
| break; |
| |
| default: |
| ai_assert(false); |
| }; |
| |
| const uint32_t full_length = stride * count; |
| buff.resize(full_length); |
| |
| if(encmode == 0) { |
| ai_assert(full_length == comp_len); |
| |
| // plain data, no compression |
| std::copy(data, end, buff.begin()); |
| } |
| else if(encmode == 1) { |
| // zlib/deflate, next comes ZIP head (0x78 0x01) |
| // see http://www.ietf.org/rfc/rfc1950.txt |
| |
| z_stream zstream; |
| zstream.opaque = Z_NULL; |
| zstream.zalloc = Z_NULL; |
| zstream.zfree = Z_NULL; |
| zstream.data_type = Z_BINARY; |
| |
| // http://hewgill.com/journal/entries/349-how-to-decompress-gzip-stream-with-zlib |
| if(Z_OK != inflateInit(&zstream)) { |
| ParseError("failure initializing zlib"); |
| } |
| |
| zstream.next_in = reinterpret_cast<Bytef*>( const_cast<char*>(data) ); |
| zstream.avail_in = comp_len; |
| |
| zstream.avail_out = static_cast<uInt>(buff.size()); |
| zstream.next_out = reinterpret_cast<Bytef*>(&*buff.begin()); |
| const int ret = inflate(&zstream, Z_FINISH); |
| |
| if (ret != Z_STREAM_END && ret != Z_OK) { |
| ParseError("failure decompressing compressed data section"); |
| } |
| |
| // terminate zlib |
| inflateEnd(&zstream); |
| } |
| #ifdef ASSIMP_BUILD_DEBUG |
| else { |
| // runtime check for this happens at tokenization stage |
| ai_assert(false); |
| } |
| #endif |
| |
| data += comp_len; |
| ai_assert(data == end); |
| } |
| |
| } // !anon |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of float3 tuples |
| void ParseVectorDataArray(std::vector<aiVector3D>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(count % 3 != 0) { |
| ParseError("number of floats is not a multiple of three (3) (binary)",&el); |
| } |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'd' && type != 'f') { |
| ParseError("expected float or double array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); |
| |
| const uint32_t count3 = count / 3; |
| out.reserve(count3); |
| |
| if (type == 'd') { |
| const double* d = reinterpret_cast<const double*>(&buff[0]); |
| for (unsigned int i = 0; i < count3; ++i, d += 3) { |
| out.push_back(aiVector3D(static_cast<float>(d[0]), |
| static_cast<float>(d[1]), |
| static_cast<float>(d[2]))); |
| } |
| // for debugging |
| /*for ( size_t i = 0; i < out.size(); i++ ) { |
| aiVector3D vec3( out[ i ] ); |
| std::stringstream stream; |
| stream << " vec3.x = " << vec3.x << " vec3.y = " << vec3.y << " vec3.z = " << vec3.z << std::endl; |
| DefaultLogger::get()->info( stream.str() ); |
| }*/ |
| } |
| else if (type == 'f') { |
| const float* f = reinterpret_cast<const float*>(&buff[0]); |
| for (unsigned int i = 0; i < count3; ++i, f += 3) { |
| out.push_back(aiVector3D(f[0],f[1],f[2])); |
| } |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // may throw bad_alloc if the input is rubbish, but this need |
| // not to be prevented - importing would fail but we wouldn't |
| // crash since assimp handles this case properly. |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| if (a.Tokens().size() % 3 != 0) { |
| ParseError("number of floats is not a multiple of three (3)",&el); |
| } |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| aiVector3D v; |
| v.x = ParseTokenAsFloat(**it++); |
| v.y = ParseTokenAsFloat(**it++); |
| v.z = ParseTokenAsFloat(**it++); |
| |
| out.push_back(v); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of color4 tuples |
| void ParseVectorDataArray(std::vector<aiColor4D>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(count % 4 != 0) { |
| ParseError("number of floats is not a multiple of four (4) (binary)",&el); |
| } |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'd' && type != 'f') { |
| ParseError("expected float or double array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); |
| |
| const uint32_t count4 = count / 4; |
| out.reserve(count4); |
| |
| if (type == 'd') { |
| const double* d = reinterpret_cast<const double*>(&buff[0]); |
| for (unsigned int i = 0; i < count4; ++i, d += 4) { |
| out.push_back(aiColor4D(static_cast<float>(d[0]), |
| static_cast<float>(d[1]), |
| static_cast<float>(d[2]), |
| static_cast<float>(d[3]))); |
| } |
| } |
| else if (type == 'f') { |
| const float* f = reinterpret_cast<const float*>(&buff[0]); |
| for (unsigned int i = 0; i < count4; ++i, f += 4) { |
| out.push_back(aiColor4D(f[0],f[1],f[2],f[3])); |
| } |
| } |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() above |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| if (a.Tokens().size() % 4 != 0) { |
| ParseError("number of floats is not a multiple of four (4)",&el); |
| } |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| aiColor4D v; |
| v.r = ParseTokenAsFloat(**it++); |
| v.g = ParseTokenAsFloat(**it++); |
| v.b = ParseTokenAsFloat(**it++); |
| v.a = ParseTokenAsFloat(**it++); |
| |
| out.push_back(v); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of float2 tuples |
| void ParseVectorDataArray(std::vector<aiVector2D>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(count % 2 != 0) { |
| ParseError("number of floats is not a multiple of two (2) (binary)",&el); |
| } |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'd' && type != 'f') { |
| ParseError("expected float or double array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); |
| |
| const uint32_t count2 = count / 2; |
| out.reserve(count2); |
| |
| if (type == 'd') { |
| const double* d = reinterpret_cast<const double*>(&buff[0]); |
| for (unsigned int i = 0; i < count2; ++i, d += 2) { |
| out.push_back(aiVector2D(static_cast<float>(d[0]), |
| static_cast<float>(d[1]))); |
| } |
| } |
| else if (type == 'f') { |
| const float* f = reinterpret_cast<const float*>(&buff[0]); |
| for (unsigned int i = 0; i < count2; ++i, f += 2) { |
| out.push_back(aiVector2D(f[0],f[1])); |
| } |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() above |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| if (a.Tokens().size() % 2 != 0) { |
| ParseError("number of floats is not a multiple of two (2)",&el); |
| } |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| aiVector2D v; |
| v.x = ParseTokenAsFloat(**it++); |
| v.y = ParseTokenAsFloat(**it++); |
| |
| out.push_back(v); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of ints |
| void ParseVectorDataArray(std::vector<int>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'i') { |
| ParseError("expected int array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * 4); |
| |
| out.reserve(count); |
| |
| const int32_t* ip = reinterpret_cast<const int32_t*>(&buff[0]); |
| for (unsigned int i = 0; i < count; ++i, ++ip) { |
| BE_NCONST int32_t val = *ip; |
| AI_SWAP4(val); |
| out.push_back(val); |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| const int ival = ParseTokenAsInt(**it++); |
| out.push_back(ival); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of floats |
| void ParseVectorDataArray(std::vector<float>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'd' && type != 'f') { |
| ParseError("expected float or double array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * (type == 'd' ? 8 : 4)); |
| |
| if (type == 'd') { |
| const double* d = reinterpret_cast<const double*>(&buff[0]); |
| for (unsigned int i = 0; i < count; ++i, ++d) { |
| out.push_back(static_cast<float>(*d)); |
| } |
| } |
| else if (type == 'f') { |
| const float* f = reinterpret_cast<const float*>(&buff[0]); |
| for (unsigned int i = 0; i < count; ++i, ++f) { |
| out.push_back(*f); |
| } |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| const float ival = ParseTokenAsFloat(**it++); |
| out.push_back(ival); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of uints |
| void ParseVectorDataArray(std::vector<unsigned int>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'i') { |
| ParseError("expected (u)int array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * 4); |
| |
| out.reserve(count); |
| |
| const int32_t* ip = reinterpret_cast<const int32_t*>(&buff[0]); |
| for (unsigned int i = 0; i < count; ++i, ++ip) { |
| BE_NCONST int32_t val = *ip; |
| if(val < 0) { |
| ParseError("encountered negative integer index (binary)"); |
| } |
| |
| AI_SWAP4(val); |
| out.push_back(val); |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| const int ival = ParseTokenAsInt(**it++); |
| if(ival < 0) { |
| ParseError("encountered negative integer index"); |
| } |
| out.push_back(static_cast<unsigned int>(ival)); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of uint64_ts |
| void ParseVectorDataArray(std::vector<uint64_t>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| ParseError("unexpected empty element",&el); |
| } |
| |
| if(tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if(!count) { |
| return; |
| } |
| |
| if (type != 'l') { |
| ParseError("expected long array (binary)",&el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * 8); |
| |
| out.reserve(count); |
| |
| const uint64_t* ip = reinterpret_cast<const uint64_t*>(&buff[0]); |
| for (unsigned int i = 0; i < count; ++i, ++ip) { |
| BE_NCONST uint64_t val = *ip; |
| AI_SWAP8(val); |
| out.push_back(val); |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope,"a",&el); |
| |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) { |
| const uint64_t ival = ParseTokenAsID(**it++); |
| |
| out.push_back(ival); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // read an array of int64_ts |
| void ParseVectorDataArray(std::vector<int64_t>& out, const Element& el) |
| { |
| out.resize( 0 ); |
| const TokenList& tok = el.Tokens(); |
| if (tok.empty()) { |
| ParseError("unexpected empty element", &el); |
| } |
| |
| if (tok[0]->IsBinary()) { |
| const char* data = tok[0]->begin(), *end = tok[0]->end(); |
| |
| char type; |
| uint32_t count; |
| ReadBinaryDataArrayHead(data, end, type, count, el); |
| |
| if (!count) { |
| return; |
| } |
| |
| if (type != 'l') { |
| ParseError("expected long array (binary)", &el); |
| } |
| |
| std::vector<char> buff; |
| ReadBinaryDataArray(type, count, data, end, buff, el); |
| |
| ai_assert(data == end); |
| ai_assert(buff.size() == count * 8); |
| |
| out.reserve(count); |
| |
| const int64_t* ip = reinterpret_cast<const int64_t*>(&buff[0]); |
| for (unsigned int i = 0; i < count; ++i, ++ip) { |
| BE_NCONST int64_t val = *ip; |
| AI_SWAP8(val); |
| out.push_back(val); |
| } |
| |
| return; |
| } |
| |
| const size_t dim = ParseTokenAsDim(*tok[0]); |
| |
| // see notes in ParseVectorDataArray() |
| out.reserve(dim); |
| |
| const Scope& scope = GetRequiredScope(el); |
| const Element& a = GetRequiredElement(scope, "a", &el); |
| |
| for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end;) { |
| const int64_t ival = ParseTokenAsInt64(**it++); |
| |
| out.push_back(ival); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| aiMatrix4x4 ReadMatrix(const Element& element) |
| { |
| std::vector<float> values; |
| ParseVectorDataArray(values,element); |
| |
| if(values.size() != 16) { |
| ParseError("expected 16 matrix elements"); |
| } |
| |
| aiMatrix4x4 result; |
| |
| |
| result.a1 = values[0]; |
| result.a2 = values[1]; |
| result.a3 = values[2]; |
| result.a4 = values[3]; |
| |
| result.b1 = values[4]; |
| result.b2 = values[5]; |
| result.b3 = values[6]; |
| result.b4 = values[7]; |
| |
| result.c1 = values[8]; |
| result.c2 = values[9]; |
| result.c3 = values[10]; |
| result.c4 = values[11]; |
| |
| result.d1 = values[12]; |
| result.d2 = values[13]; |
| result.d3 = values[14]; |
| result.d4 = values[15]; |
| |
| result.Transpose(); |
| return result; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // wrapper around ParseTokenAsString() with ParseError handling |
| std::string ParseTokenAsString(const Token& t) |
| { |
| const char* err; |
| const std::string& i = ParseTokenAsString(t,err); |
| if(err) { |
| ParseError(err,t); |
| } |
| return i; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // extract a required element from a scope, abort if the element cannot be found |
| const Element& GetRequiredElement(const Scope& sc, const std::string& index, const Element* element /*= NULL*/) |
| { |
| const Element* el = sc[index]; |
| if(!el) { |
| ParseError("did not find required element \"" + index + "\"",element); |
| } |
| return *el; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // extract required compound scope |
| const Scope& GetRequiredScope(const Element& el) |
| { |
| const Scope* const s = el.Compound(); |
| if(!s) { |
| ParseError("expected compound scope",&el); |
| } |
| |
| return *s; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // get token at a particular index |
| const Token& GetRequiredToken(const Element& el, unsigned int index) |
| { |
| const TokenList& t = el.Tokens(); |
| if(index >= t.size()) { |
| ParseError(Formatter::format( "missing token at index " ) << index,&el); |
| } |
| |
| return *t[index]; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // wrapper around ParseTokenAsID() with ParseError handling |
| uint64_t ParseTokenAsID(const Token& t) |
| { |
| const char* err; |
| const uint64_t i = ParseTokenAsID(t,err); |
| if(err) { |
| ParseError(err,t); |
| } |
| return i; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // wrapper around ParseTokenAsDim() with ParseError handling |
| size_t ParseTokenAsDim(const Token& t) |
| { |
| const char* err; |
| const size_t i = ParseTokenAsDim(t,err); |
| if(err) { |
| ParseError(err,t); |
| } |
| return i; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // wrapper around ParseTokenAsFloat() with ParseError handling |
| float ParseTokenAsFloat(const Token& t) |
| { |
| const char* err; |
| const float i = ParseTokenAsFloat(t,err); |
| if(err) { |
| ParseError(err,t); |
| } |
| return i; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // wrapper around ParseTokenAsInt() with ParseError handling |
| int ParseTokenAsInt(const Token& t) |
| { |
| const char* err; |
| const int i = ParseTokenAsInt(t,err); |
| if(err) { |
| ParseError(err,t); |
| } |
| return i; |
| } |
| |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // wrapper around ParseTokenAsInt64() with ParseError handling |
| int64_t ParseTokenAsInt64(const Token& t) |
| { |
| const char* err; |
| const int64_t i = ParseTokenAsInt64(t, err); |
| if (err) { |
| ParseError(err, t); |
| } |
| return i; |
| } |
| |
| } // !FBX |
| } // !Assimp |
| |
| #endif |