diff options
Diffstat (limited to 'modules/fbx/fbx_parser/FBXParser.cpp')
-rw-r--r-- | modules/fbx/fbx_parser/FBXParser.cpp | 1295 |
1 files changed, 1295 insertions, 0 deletions
diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp new file mode 100644 index 0000000000..208358f55e --- /dev/null +++ b/modules/fbx/fbx_parser/FBXParser.cpp @@ -0,0 +1,1295 @@ +/*************************************************************************/ +/* FBXParser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 + */ + +#include "thirdparty/zlib/zlib.h" +#include <stdlib.h> /* strtol */ + +#include "ByteSwapper.h" +#include "FBXParseTools.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" +#include "core/math/math_defs.h" +#include "core/math/transform.h" +#include "core/math/vector3.h" +#include "core/string/print_string.h" + +using namespace FBXDocParser; +namespace { + +// 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 + +namespace FBXDocParser { + +// ------------------------------------------------------------------------------------------------ +Element::Element(const TokenPtr key_token, Parser &parser) : + key_token(key_token) { + TokenPtr n = nullptr; + do { + n = parser.AdvanceToNextToken(); + if (n == nullptr) { + continue; + } + + if (!n) { + print_error("unexpected end of file, expected closing bracket" + String(parser.LastToken()->StringContents().c_str())); + } + + if (n && n->Type() == TokenType_DATA) { + tokens.push_back(n); + TokenPtr prev = n; + n = parser.AdvanceToNextToken(); + + if (n == nullptr) { + break; + } + + if (!n) { + print_error("unexpected end of file, expected bracket, comma or key" + String(parser.LastToken()->StringContents().c_str())); + } + + 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) { + print_error("unexpected token; expected bracket, comma or key" + String(n->StringContents().c_str())); + } + } + + if (n && n->Type() == TokenType_OPEN_BRACKET) { + compound = new_Scope(parser); + parser.scopes.push_back(compound); + + // current token should be a TOK_CLOSE_BRACKET + n = parser.CurrentToken(); + + if (n && n->Type() != TokenType_CLOSE_BRACKET) { + print_error("expected closing bracket" + String(n->StringContents().c_str())); + } + + parser.AdvanceToNextToken(); + return; + } + } while (n && n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET); +} + +// ------------------------------------------------------------------------------------------------ +Element::~Element() { +} + +// ------------------------------------------------------------------------------------------------ +Scope::Scope(Parser &parser, bool topLevel) { + if (!topLevel) { + TokenPtr t = parser.CurrentToken(); + if (t->Type() != TokenType_OPEN_BRACKET) { + print_error("expected open bracket" + String(t->StringContents().c_str())); + } + } + + TokenPtr n = parser.AdvanceToNextToken(); + if (n == nullptr) { + print_error("unexpected end of file"); + } + + // note: empty scopes are allowed + while (n && n->Type() != TokenType_CLOSE_BRACKET) { + if (n->Type() != TokenType_KEY) { + print_error("unexpected token, expected TOK_KEY" + String(n->StringContents().c_str())); + } + + const std::string str = n->StringContents(); + + // std::multimap<std::string, ElementPtr> (key and value) + 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 == nullptr) { + if (topLevel) { + return; + } + + //print_error("unexpected end of file" + String(parser.LastToken()->StringContents().c_str())); + } + } +} + +// ------------------------------------------------------------------------------------------------ +Scope::~Scope() { + for (ElementMap::value_type &v : elements) { + delete v.second; + v.second = nullptr; + } + + elements.clear(); +} + +// ------------------------------------------------------------------------------------------------ +Parser::Parser(const TokenList &tokens, bool is_binary) : + tokens(tokens), last(), current(), cursor(tokens.begin()), is_binary(is_binary) { + root = new_Scope(*this, true); + scopes.push_back(root); +} + +// ------------------------------------------------------------------------------------------------ +Parser::~Parser() { + for (ScopePtr scope : scopes) { + delete scope; + scope = nullptr; + } +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::AdvanceToNextToken() { + last = current; + if (cursor == tokens.end()) { + current = nullptr; + } else { + current = *cursor++; + } + return current; +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::CurrentToken() const { + return current; +} + +// ------------------------------------------------------------------------------------------------ +TokenPtr Parser::LastToken() const { + return last; +} + +// ------------------------------------------------------------------------------------------------ +uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out) { + ERR_FAIL_COND_V_MSG(t == nullptr, 0L, "Invalid token passed to ParseTokenAsID"); + err_out = nullptr; + + 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; + } + + uint64_t id = SafeParse<uint64_t>(data + 1, t->end()); + 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; + bool errored = false; + + const uint64_t id = strtoul10_64(t->begin(), errored, &out, &length); + if (errored || out > t->end()) { + err_out = "failed to parse ID (text)"; + return 0L; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsID() with print_error handling +uint64_t ParseTokenAsID(const TokenPtr t) { + const char *err = nullptr; + const uint64_t i = ParseTokenAsID(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out) { + // same as ID parsing, except there is a trailing asterisk + err_out = nullptr; + + 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; + } + + 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; + bool errored = false; + const size_t id = static_cast<size_t>(strtoul10_64(t->begin() + 1, errored, &out, &length)); + if (errored || out > t->end()) { + print_error("failed to parse id"); + err_out = "failed to parse ID"; + return 0; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +float ParseTokenAsFloat(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + 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 atof(temp); +} + +// ------------------------------------------------------------------------------------------------ +int ParseTokenAsInt(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + if (t->Type() != TokenType_DATA) { + err_out = "expected TOK_DATA token"; + return 0; + } + + // binary files are simple to parse + 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; + } + + int32_t ival = SafeParse<int32_t>(data + 1, t->end()); + AI_SWAP4(ival); + return static_cast<int>(ival); + } + + // ASCII files are unsafe. + const size_t length = static_cast<size_t>(t->end() - t->begin()); + if (length == 0) { + err_out = "expected valid integer number after asterisk"; + ERR_FAIL_V_MSG(0, "expected valid integer number after asterisk"); + } + + // must not be null for strtol to work + char *out = (char *)t->end(); + // string begin, end ptr ref, base 10 + const int value = strtol(t->begin(), &out, 10); + if (out == nullptr || out != t->end()) { + err_out = "failed to parse ID"; + ERR_FAIL_V_MSG(0, "failed to parse ID"); + } + + return value; +} + +// ------------------------------------------------------------------------------------------------ +int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + 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; + } + + 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); + + char *out = nullptr; + const int64_t id = strtol(t->begin(), &out, length); + if (out > t->end()) { + err_out = "failed to parse Int64 (text)"; + return 0L; + } + + return id; +} + +// ------------------------------------------------------------------------------------------------ +std::string ParseTokenAsString(const TokenPtr t, const char *&err_out) { + err_out = nullptr; + + 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 String, unexpected data type (binary)"; + return ""; + } + + // read string length + 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 ElementPtr el) { + TokenPtr token = el->KeyToken(); + if (static_cast<size_t>(end - data) < 5) { + print_error("binary data array is too short, need five (5) bytes for type signature and element count: " + String(token->StringContents().c_str())); + } + + // data type + type = *data; + + // read number of elements + 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 ElementPtr /*el*/) { + uint32_t encmode = SafeParse<uint32_t>(data, end); + AI_SWAP4(encmode); + data += 4; + + // next comes the compressed length + 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; + } + + 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)) { + print_error("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) { + print_error("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); +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// read an array of float3 tuples +void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el) { + out.resize(0); + + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + 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) { + print_error("number of floats is not a multiple of three (3) (binary)" + String(token->StringContents().c_str())); + } + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); + } + + 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(Vector3(static_cast<real_t>(d[0]), + static_cast<real_t>(d[1]), + static_cast<real_t>(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(Vector3(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 ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + if (a->Tokens().size() % 3 != 0) { + print_error("number of floats is not a multiple of three (3)" + String(token->StringContents().c_str())); + } else { + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + Vector3 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<Color> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + + TokenPtr token = el->KeyToken(); + + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + 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) { + print_error("number of floats is not a multiple of four (4) (binary)" + String(token->StringContents().c_str())); + } + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); + } + + 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(Color(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(Color(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 ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + if (a->Tokens().size() % 4 != 0) { + print_error("number of floats is not a multiple of four (4)" + String(token->StringContents().c_str())); + } + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + Color 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<Vector2> &out, const ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + 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) { + print_error("number of floats is not a multiple of two (2) (binary)" + String(token->StringContents().c_str())); + } + + if (!count) { + return; + } + + if (type != 'd' && type != 'f') { + print_error("expected float or double array (binary)" + String(token->StringContents().c_str())); + } + + 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(Vector2(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(Vector2(f[0], f[1])); + } + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() above + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + if (a->Tokens().size() % 2 != 0) { + print_error("number of floats is not a multiple of two (2)" + String(token->StringContents().c_str())); + } else { + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + Vector2 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 ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element" + String(token->StringContents().c_str())); + } + + 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') { + print_error("expected int array (binary)" + String(token->StringContents().c_str())); + } + + 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) { + 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 ScopePtr scope = GetRequiredScope(el); + const ElementPtr 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 ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + if (tok.empty()) { + print_error("unexpected empty element: " + String(token->StringContents().c_str())); + } + + 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') { + print_error("expected float or double array (binary) " + String(token->StringContents().c_str())); + } + + 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 ScopePtr scope = GetRequiredScope(el); + const ElementPtr 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 ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + const TokenPtr token = el->KeyToken(); + + ERR_FAIL_COND_MSG(!token, "invalid ParseVectorDataArrat token invalid"); + + if (tok.empty()) { + print_error("unexpected empty element: " + String(token->StringContents().c_str())); + } + + 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') { + print_error("expected (u)int array (binary)" + String(token->StringContents().c_str())); + } + + 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) { + int32_t val = *ip; + if (val < 0) { + print_error("encountered negative integer index (binary)"); + } + + out.push_back(val); + } + + return; + } + + const size_t dim = ParseTokenAsDim(tok[0]); + + // see notes in ParseVectorDataArray() + out.reserve(dim); + + const ScopePtr scope = GetRequiredScope(el); + const ElementPtr 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) { + print_error("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 ElementPtr el) { + out.resize(0); + + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND(!token); + + if (tok.empty()) { + print_error("unexpected empty element " + String(token->StringContents().c_str())); + } + + 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') { + print_error("expected long array (binary): " + String(token->StringContents().c_str())); + } + + 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) { + 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 ScopePtr scope = GetRequiredScope(el); + const ElementPtr 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 ElementPtr el) { + out.resize(0); + const TokenList &tok = el->Tokens(); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND(!token); + if (tok.empty()) { + print_error("unexpected empty element: " + String(token->StringContents().c_str())); + } + + 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') { + print_error("expected long array (binary) " + String(token->StringContents().c_str())); + } + + 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) { + 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 ScopePtr scope = GetRequiredScope(el); + const ElementPtr a = GetRequiredElement(scope, "a", el); + + for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) { + const int64_t val = ParseTokenAsInt64(*it++); + out.push_back(val); + } +} + +// ------------------------------------------------------------------------------------------------ +Transform ReadMatrix(const ElementPtr element) { + std::vector<float> values; + ParseVectorDataArray(values, element); + + if (values.size() != 16) { + print_error("expected 16 matrix elements"); + } + + // clean values to prevent any IBM damage on inverse() / affine_inverse() + for (float &value : values) { + if (::Math::is_equal_approx(0, value)) { + value = 0; + } + } + + Transform xform; + Basis basis; + + basis.set( + Vector3(values[0], values[1], values[2]), + Vector3(values[4], values[5], values[6]), + Vector3(values[8], values[9], values[10])); + + xform.basis = basis; + xform.origin = Vector3(values[12], values[13], values[14]); + // determine if we need to think about this with dynamic rotation order? + // for example: + // xform.basis = z_axis * y_axis * x_axis; + //xform.basis.transpose(); + + print_verbose("xform verbose basis: " + (xform.basis.get_euler() * (180 / Math_PI)) + " xform origin:" + xform.origin); + + return xform; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsString() with print_error handling +std::string ParseTokenAsString(const TokenPtr t) { + ERR_FAIL_COND_V(!t, ""); + const char *err; + const std::string &i = ParseTokenAsString(t, err); + if (err) { + print_error(String(err) + ", " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// extract a required element from a scope, abort if the element cannot be found +ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= NULL*/) { + const ElementPtr el = sc->GetElement(index); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND_V(!token, nullptr); + if (!el) { + print_error("did not find required element \"" + String(index.c_str()) + "\" " + String(token->StringContents().c_str())); + } + return el; +} + +bool HasElement(const ScopePtr sc, const std::string &index) { + const ElementPtr el = sc->GetElement(index); + if (nullptr == el) { + return false; + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +// extract a required element from a scope, abort if the element cannot be found +ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= NULL*/) { + const ElementPtr el = sc->GetElement(index); + return el; +} + +// ------------------------------------------------------------------------------------------------ +// extract required compound scope +ScopePtr GetRequiredScope(const ElementPtr el) { + if (el) { + ScopePtr s = el->Compound(); + TokenPtr token = el->KeyToken(); + ERR_FAIL_COND_V(!token, nullptr); + if (s) { + return s; + } + + ERR_FAIL_V_MSG(nullptr, "expected compound scope " + String(token->StringContents().c_str())); + } + + ERR_FAIL_V_MSG(nullptr, "Invalid element supplied to parser"); +} + +// ------------------------------------------------------------------------------------------------ +// get token at a particular index +TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index) { + if (el) { + const TokenList &x = el->Tokens(); + TokenPtr token = el->KeyToken(); + + ERR_FAIL_COND_V(!token, nullptr); + + if (index >= x.size()) { + ERR_FAIL_V_MSG(nullptr, "missing token at index: " + itos(index) + " " + String(token->StringContents().c_str())); + } + + return x[index]; + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsDim() with print_error handling +size_t ParseTokenAsDim(const TokenPtr t) { + const char *err; + const size_t i = ParseTokenAsDim(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsFloat() with print_error handling +float ParseTokenAsFloat(const TokenPtr t) { + const char *err; + const float i = ParseTokenAsFloat(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsInt() with print_error handling +int ParseTokenAsInt(const TokenPtr t) { + const char *err; + const int i = ParseTokenAsInt(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +// wrapper around ParseTokenAsInt64() with print_error handling +int64_t ParseTokenAsInt64(const TokenPtr t) { + const char *err; + const int64_t i = ParseTokenAsInt64(t, err); + if (err) { + print_error(String(err) + " " + String(t->StringContents().c_str())); + } + return i; +} +} // namespace FBXDocParser |