#include "ofbx.h" #include "libdeflate.h" #include #include #include #include #include #include #include #include #include #include #include #if __cplusplus >= 202002L && defined(__cpp_lib_bit_cast) #include // for std::bit_cast (C++20 and later) #endif #include namespace ofbx { template static T read_value(const u8* value_ptr) { T value; memcpy(&value, value_ptr, sizeof(T)); return value; } static int decodeIndex(int idx) { return (idx < 0) ? (-idx - 1) : idx; } static int codeIndex(int idx, bool last) { return last ? (-idx - 1) : idx; } template static T& emplace_back(std::vector& vec) { vec.emplace_back(); return vec.back(); } struct Allocator { struct Page { struct { Page* next = nullptr; u32 offset = 0; } header; u8 data[4096 * 1024 - 12]; }; Page* first = nullptr; ~Allocator() { Page* p = first; while (p) { Page* n = p->header.next; delete p; p = n; } } template T* allocate(Args&&... args) { assert(sizeof(T) <= sizeof(first->data)); if (!first) { first = new Page; } Page* p = first; if (p->header.offset % alignof(T) != 0) { p->header.offset += alignof(T) - p->header.offset % alignof(T); } if (p->header.offset + sizeof(T) > sizeof(p->data)) { p = new Page; p->header.next = first; first = p; } T* res = new (p->data + p->header.offset) T(args...); p->header.offset += sizeof(T); return res; } }; struct Video { IElementProperty* base64_property = nullptr; DataView filename; DataView content; DataView media; bool is_base_64; }; struct Error { Error() {} Error(const char* msg) { s_message = msg; } // Format a message with printf-style arguments. template Error(const char* fmt, Args... args) { char buf[1024]; std::snprintf(buf, sizeof(buf), fmt, args...); s_message = buf; } static const char* s_message; }; const char* Error::s_message = ""; template struct OptionalError { OptionalError(Error error) : is_error(true) { } OptionalError(T _value) : value(_value) , is_error(false) { } T getValue() const { #ifdef _DEBUG assert(error_checked); #endif return value; } bool isError() { #ifdef _DEBUG error_checked = true; #endif return is_error; } private: T value; bool is_error; #ifdef _DEBUG bool error_checked = false; #endif }; #pragma pack(1) struct Header { u8 magic[21]; u8 reserved[2]; u32 version; }; #pragma pack() struct Cursor { const u8* current; const u8* begin; const u8* end; }; static void setTranslation(const DVec3& t, DMatrix* mtx) { mtx->m[12] = t.x; mtx->m[13] = t.y; mtx->m[14] = t.z; } static DVec3 operator-(const DVec3& v) { return {-v.x, -v.y, -v.z}; } static DMatrix operator*(const DMatrix& lhs, const DMatrix& rhs) { DMatrix res; for (int j = 0; j < 4; ++j) { for (int i = 0; i < 4; ++i) { double tmp = 0; for (int k = 0; k < 4; ++k) { tmp += lhs.m[i + k * 4] * rhs.m[k + j * 4]; } res.m[i + j * 4] = tmp; } } return res; } static DMatrix makeIdentity() { return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; } static DMatrix rotationX(double angle) { DMatrix m = makeIdentity(); double c = cos(angle); double s = sin(angle); m.m[5] = m.m[10] = c; m.m[9] = -s; m.m[6] = s; return m; } static DMatrix rotationY(double angle) { DMatrix m = makeIdentity(); double c = cos(angle); double s = sin(angle); m.m[0] = m.m[10] = c; m.m[8] = s; m.m[2] = -s; return m; } static DMatrix rotationZ(double angle) { DMatrix m = makeIdentity(); double c = cos(angle); double s = sin(angle); m.m[0] = m.m[5] = c; m.m[4] = -s; m.m[1] = s; return m; } static DMatrix getRotationMatrix(const DVec3& euler, RotationOrder order) { const double TO_RAD = 3.1415926535897932384626433832795028 / 180.0; DMatrix rx = rotationX(euler.x * TO_RAD); DMatrix ry = rotationY(euler.y * TO_RAD); DMatrix rz = rotationZ(euler.z * TO_RAD); switch (order) { default: case RotationOrder::EULER_XYZ: return rz * ry * rx; case RotationOrder::EULER_XZY: return ry * rz * rx; case RotationOrder::EULER_YXZ: return rz * rx * ry; case RotationOrder::EULER_YZX: return rx * rz * ry; case RotationOrder::EULER_ZXY: return ry * rx * rz; case RotationOrder::EULER_ZYX: return rx * ry * rz; case RotationOrder::SPHERIC_XYZ: assert(false); Error::s_message = "Unsupported rotation order."; return rx * ry * rz; } } double fbxTimeToSeconds(i64 value) { return double(value) / 46186158000L; } i64 secondsToFbxTime(double value) { return i64(value * 46186158000L); } static DVec3 operator*(const DVec3& v, float f) { return {v.x * f, v.y * f, v.z * f}; } static DVec3 operator+(const DVec3& a, const DVec3& b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; } static FVec3 operator+(const FVec3& a, const FVec3& b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; } template static bool copyString(char (&destination)[SIZE], const char* source) { const char* src = source; char* dest = destination; int length = SIZE; if (!src) return false; while (*src && length > 1) { *dest = *src; --length; ++dest; ++src; } *dest = 0; return *src == '\0'; } u64 DataView::toU64() const { if (is_binary) { assert(end - begin == sizeof(u64)); u64 result; memcpy(&result, begin, sizeof(u64)); return result; } static_assert(sizeof(unsigned long long) >= sizeof(u64), "can't use strtoull"); return strtoull((const char*)begin, nullptr, 10); } i64 DataView::toI64() const { if (is_binary) { assert(end - begin == sizeof(i64)); i64 result; memcpy(&result, begin, sizeof(i64)); return result; } static_assert(sizeof(long long) >= sizeof(i64), "can't use atoll"); return atoll((const char*)begin); } int DataView::toInt() const { if (is_binary) { assert(end - begin == sizeof(int)); int result; memcpy(&result, begin, sizeof(int)); return result; } return atoi((const char*)begin); } u32 DataView::toU32() const { if (is_binary) { assert(end - begin == sizeof(u32)); u32 result; memcpy(&result, begin, sizeof(u32)); return result; } return (u32)atoll((const char*)begin); } bool DataView::toBool() const { return toInt() != 0; } double DataView::toDouble() const { if (is_binary) { assert(end - begin == sizeof(double)); double result; memcpy(&result, begin, sizeof(double)); return result; } return atof((const char*)begin); } float DataView::toFloat() const { if (is_binary) { assert(end - begin == sizeof(float)); float result; memcpy(&result, begin, sizeof(float)); return result; } return (float)atof((const char*)begin); } bool DataView::operator==(const char* rhs) const { if (!begin) return !rhs[0]; const char* c = rhs; const char* c2 = (const char*)begin; while (*c && c2 != (const char*)end) { if (*c != *c2) return false; ++c; ++c2; } return (*c2 == '\0' || c2 == (const char*)end) && *c == '\0'; } struct Property; struct Element; template static bool parseMemory(const Property& property, T* out, int max_size_bytes); template static bool parseVecData(Property& property, std::vector* out_vec); template static bool parseVertexData(const Element& element, const char* name, const char* index_name, T& out, std::vector& jobs); static bool parseDouble(Property& property, double* out); struct ParseDataJob { using F = bool (*)(Property*, void*); Property* property = nullptr; void* data = nullptr; bool error = false; F f; }; template [[nodiscard]] bool pushJob(std::vector& jobs, Property& prop, std::vector& data) { ParseDataJob& job = emplace_back(jobs); job.property = ∝ job.data = (void*)&data; job.f = [](Property* prop, void* data){ return parseVecData(*prop, (std::vector*)data); }; return true; } struct Property : IElementProperty { Type getType() const override { return (Type)type; } IElementProperty* getNext() const override { return next; } DataView getValue() const override { return value; } int getCount() const override { assert(type == ARRAY_DOUBLE || type == ARRAY_INT || type == ARRAY_FLOAT || type == ARRAY_LONG); if (value.is_binary) { int i; memcpy(&i, value.begin, sizeof(i)); return i; } return count; } bool getValues(double* values, int max_size) const override { return parseMemory(*this, values, max_size); } bool getValues(float* values, int max_size) const override { return parseMemory(*this, values, max_size); } bool getValues(u64* values, int max_size) const override { return parseMemory(*this, values, max_size); } bool getValues(i64* values, int max_size) const override { return parseMemory(*this, values, max_size); } bool getValues(int* values, int max_size) const override { return parseMemory(*this, values, max_size); } int count = 0; u8 type = INTEGER; DataView value; Property* next = nullptr; }; struct Element : IElement { IElement* getFirstChild() const override { return child; } IElement* getSibling() const override { return sibling; } DataView getID() const override { return id; } IElementProperty* getFirstProperty() const override { return first_property; } IElementProperty* getProperty(int idx) const { IElementProperty* prop = first_property; for (int i = 0; i < idx; ++i) { if (prop == nullptr) return nullptr; prop = prop->getNext(); } return prop; } DataView id; Element* child = nullptr; Element* sibling = nullptr; Property* first_property = nullptr; }; static const Element* findChild(const Element& element, const char* id) { Element* const* iter = &element.child; while (*iter) { if ((*iter)->id == id) return *iter; iter = &(*iter)->sibling; } return nullptr; } static IElement* resolveProperty(const Object& obj, const char* name, bool* is_p60) { *is_p60 = false; const Element* props = findChild((const Element&)obj.element, "Properties70"); if (!props) { props = findChild((const Element&)obj.element, "Properties60"); *is_p60 = true; if (!props) return nullptr; } Element* prop = props->child; while (prop) { if (prop->first_property && prop->first_property->value == name) { return prop; } prop = prop->sibling; } return nullptr; } static int resolveEnumProperty(const Object& object, const char* name, int default_value) { bool is_p60; Element* element = (Element*)resolveProperty(object, name, &is_p60); if (!element) return default_value; Property* x = (Property*)element->getProperty(is_p60 ? 3 : 4); if (!x) return default_value; return x->value.toInt(); } static DVec3 resolveVec3Property(const Object& object, const char* name, const DVec3& default_value) { bool is_p60; Element* element = (Element*)resolveProperty(object, name, &is_p60); if (!element) return default_value; Property* x = (Property*)element->getProperty(is_p60 ? 3 : 4); if (!x || !x->next || !x->next->next) return default_value; return {x->value.toDouble(), x->next->value.toDouble(), x->next->next->value.toDouble()}; } static bool isString(const Property* prop) { if (!prop) return false; return prop->getType() == Property::STRING; } static bool isLong(const Property* prop) { if (!prop) return false; return prop->getType() == Property::LONG; } static bool decompress(const u8* in, size_t in_size, u8* out, size_t out_size) { auto dec = libdeflate_alloc_decompressor(); size_t dummy; bool res = libdeflate_deflate_decompress(dec, in + 2, in_size - 2, out, out_size, &dummy) == LIBDEFLATE_SUCCESS; libdeflate_free_decompressor(dec); return res; } template static OptionalError read(Cursor* cursor) { if (cursor->current + sizeof(T) > cursor->end) return Error("Reading past the end"); T value = read_value(cursor->current); cursor->current += sizeof(T); return value; } static OptionalError readShortString(Cursor* cursor) { DataView value; OptionalError length = read(cursor); if (length.isError()) return Error(); if (cursor->current + length.getValue() > cursor->end) return Error("Reading past the end"); value.begin = cursor->current; cursor->current += length.getValue(); value.end = cursor->current; return value; } static OptionalError readLongString(Cursor* cursor) { DataView value; OptionalError length = read(cursor); if (length.isError()) return Error(); if (cursor->current + length.getValue() > cursor->end) return Error("Reading past the end"); value.begin = cursor->current; cursor->current += length.getValue(); value.end = cursor->current; return value; } // Cheat sheet: // /* 'S': Long string 'Y': 16-bit signed integer 'C': 8-bit signed integer 'I': 32-bit signed integer 'F': Single precision floating-point number 'D': Double precision floating-point number 'L': 64-bit signed integer 'R': Binary data 'b', 'f', 'd', 'l', 'c' and 'i': Arrays of binary data Src: https://code.blender.org/2013/08/fbx-binary-file-format-specification/ */ static OptionalError readProperty(Cursor* cursor, Allocator& allocator) { if (cursor->current == cursor->end) return Error("Reading past the end"); Property* prop = allocator.allocate(); prop->next = nullptr; prop->type = *cursor->current; ++cursor->current; prop->value.begin = cursor->current; switch (prop->type) { case 'S': { OptionalError val = readLongString(cursor); if (val.isError()) return Error(); prop->value = val.getValue(); break; } case 'Y': cursor->current += 2; break; case 'C': cursor->current += 1; break; case 'I': cursor->current += 4; break; case 'F': cursor->current += 4; break; case 'D': cursor->current += 8; break; case 'L': cursor->current += 8; break; case 'R': { OptionalError len = read(cursor); if (len.isError()) return Error(); if (cursor->current + len.getValue() > cursor->end) return Error("Reading past the end"); cursor->current += len.getValue(); break; } case 'b': case 'c': case 'f': case 'd': case 'l': case 'i': { OptionalError length = read(cursor); OptionalError encoding = read(cursor); OptionalError comp_len = read(cursor); if (length.isError() || encoding.isError() || comp_len.isError()) return Error(); if (cursor->current + comp_len.getValue() > cursor->end) return Error("Reading past the end"); cursor->current += comp_len.getValue(); break; } default: { char str[32]; snprintf(str, sizeof(str), "Unknown property type: %c", prop->type); return Error(str); } } prop->value.end = cursor->current; return prop; } static OptionalError readElementOffset(Cursor* cursor, u32 version) { if (version >= 7500) { OptionalError tmp = read(cursor); if (tmp.isError()) return Error(); return tmp.getValue(); } OptionalError tmp = read(cursor); if (tmp.isError()) return Error(); return tmp.getValue(); } static OptionalError readElement(Cursor* cursor, u32 version, Allocator& allocator) { OptionalError end_offset = readElementOffset(cursor, version); if (end_offset.isError()) return Error(); if (end_offset.getValue() == 0) return nullptr; OptionalError prop_count = readElementOffset(cursor, version); OptionalError prop_length = readElementOffset(cursor, version); if (prop_count.isError() || prop_length.isError()) return Error(); OptionalError id = readShortString(cursor); if (id.isError()) return Error(); Element* element = allocator.allocate(); element->first_property = nullptr; element->id = id.getValue(); element->child = nullptr; element->sibling = nullptr; Property** prop_link = &element->first_property; for (u32 i = 0; i < prop_count.getValue(); ++i) { OptionalError prop = readProperty(cursor, allocator); if (prop.isError()) { return Error(); } *prop_link = prop.getValue(); prop_link = &(*prop_link)->next; } if (cursor->current - cursor->begin >= (ptrdiff_t)end_offset.getValue()) return element; int BLOCK_SENTINEL_LENGTH = version >= 7500 ? 25 : 13; Element** link = &element->child; while (cursor->current - cursor->begin < ((ptrdiff_t)end_offset.getValue() - BLOCK_SENTINEL_LENGTH)) { OptionalError child = readElement(cursor, version, allocator); if (child.isError()) { return Error(); } *link = child.getValue(); if (child.getValue() == 0) break; link = &(*link)->sibling; } if (cursor->current + BLOCK_SENTINEL_LENGTH > cursor->end) { return Error("Reading past the end"); } cursor->current += BLOCK_SENTINEL_LENGTH; return element; } static bool isEndLine(const Cursor& cursor) { return (*cursor.current == '\n') || (*cursor.current == '\r' && cursor.current + 1 < cursor.end && *(cursor.current + 1) != '\n'); } static void skipInsignificantWhitespaces(Cursor* cursor) { while (cursor->current < cursor->end && isspace(*cursor->current) && !isEndLine(*cursor)) { ++cursor->current; } } static void skipLine(Cursor* cursor) { while (cursor->current < cursor->end && !isEndLine(*cursor)) { ++cursor->current; } if (cursor->current < cursor->end) ++cursor->current; skipInsignificantWhitespaces(cursor); } static void skipWhitespaces(Cursor* cursor) { while (cursor->current < cursor->end && isspace(*cursor->current)) { ++cursor->current; } while (cursor->current < cursor->end && *cursor->current == ';') skipLine(cursor); } static bool isTextTokenChar(char c) { return isalnum(c) || c == '_' || c == '-'; } static DataView readTextToken(Cursor* cursor) { DataView ret; ret.begin = cursor->current; while (cursor->current < cursor->end && isTextTokenChar(*cursor->current)) { ++cursor->current; } ret.end = cursor->current; return ret; } static OptionalError readTextProperty(Cursor* cursor, Allocator& allocator) { Property* prop = allocator.allocate(); prop->value.is_binary = false; prop->next = nullptr; if (*cursor->current == '"') { prop->type = 'S'; ++cursor->current; prop->value.begin = cursor->current; while (cursor->current < cursor->end && *cursor->current != '"') { ++cursor->current; } prop->value.end = cursor->current; if (cursor->current < cursor->end) ++cursor->current; // skip '"' return prop; } if (isdigit(*cursor->current) || *cursor->current == '-') { prop->type = 'L'; prop->value.begin = cursor->current; if (*cursor->current == '-') ++cursor->current; while (cursor->current < cursor->end && isdigit(*cursor->current)) { ++cursor->current; } prop->value.end = cursor->current; if (cursor->current < cursor->end && *cursor->current == '.') { prop->type = 'D'; ++cursor->current; while (cursor->current < cursor->end && isdigit(*cursor->current)) { ++cursor->current; } if (cursor->current < cursor->end && (*cursor->current == 'e' || *cursor->current == 'E')) { // 10.5e-013 ++cursor->current; if (cursor->current < cursor->end && *cursor->current == '-') ++cursor->current; while (cursor->current < cursor->end && isdigit(*cursor->current)) ++cursor->current; } prop->value.end = cursor->current; } else if (cursor->current < cursor->end && (*cursor->current == 'e' || *cursor->current == 'E')) { prop->type = 'D'; // 10e-013 ++cursor->current; if (cursor->current < cursor->end && *cursor->current == '-') ++cursor->current; while (cursor->current < cursor->end && isdigit(*cursor->current)) ++cursor->current; prop->value.end = cursor->current; } return prop; } if (*cursor->current == 'T' || *cursor->current == 'Y' || *cursor->current == 'W' || *cursor->current == 'C') { // WTF is this prop->type = *cursor->current; prop->value.begin = cursor->current; ++cursor->current; prop->value.end = cursor->current; return prop; } if (*cursor->current == ',') { // https://github.com/nem0/OpenFBX/issues/85 prop->type = IElementProperty::NONE; prop->value.begin = cursor->current; prop->value.end = cursor->current; return prop; } if (*cursor->current == '*') { prop->type = 'l'; ++cursor->current; // Vertices: *10740 { a: 14.2760353088379,... } while (cursor->current < cursor->end && *cursor->current != ':') { ++cursor->current; } if (cursor->current < cursor->end) ++cursor->current; // skip ':' skipInsignificantWhitespaces(cursor); prop->value.begin = cursor->current; prop->count = 0; bool is_any = false; while (cursor->current < cursor->end && *cursor->current != '}') { if (*cursor->current == ',') { if (is_any) ++prop->count; is_any = false; } else if (!isspace(*cursor->current) && !isEndLine(*cursor)) is_any = true; if (*cursor->current == '.') prop->type = 'd'; ++cursor->current; } if (is_any) ++prop->count; prop->value.end = cursor->current; if (cursor->current < cursor->end) ++cursor->current; // skip '}' return prop; } assert(false); return Error("Unknown error"); } static OptionalError readTextElement(Cursor* cursor, Allocator& allocator) { DataView id = readTextToken(cursor); if (cursor->current == cursor->end) return Error("Unexpected end of file"); if (*cursor->current != ':') return Error("Unexpected character"); ++cursor->current; skipInsignificantWhitespaces(cursor); if (cursor->current == cursor->end) return Error("Unexpected end of file"); Element* element = allocator.allocate(); element->id = id; Property** prop_link = &element->first_property; while (cursor->current < cursor->end && !isEndLine(*cursor) && *cursor->current != '{') { OptionalError prop = readTextProperty(cursor, allocator); if (prop.isError()) { return Error(); } if (cursor->current < cursor->end && *cursor->current == ',') { ++cursor->current; skipWhitespaces(cursor); } skipInsignificantWhitespaces(cursor); *prop_link = prop.getValue(); prop_link = &(*prop_link)->next; } Element** link = &element->child; if (*cursor->current == '{') { ++cursor->current; skipWhitespaces(cursor); while (cursor->current < cursor->end && *cursor->current != '}') { OptionalError child = readTextElement(cursor, allocator); if (child.isError()) { return Error(); } skipWhitespaces(cursor); *link = child.getValue(); link = &(*link)->sibling; } if (cursor->current < cursor->end) ++cursor->current; // skip '}' } return element; } static OptionalError tokenizeText(const u8* data, size_t size, Allocator& allocator) { Cursor cursor; cursor.begin = data; cursor.current = data; cursor.end = data + size; Element* root = allocator.allocate(); root->first_property = nullptr; root->id.begin = nullptr; root->id.end = nullptr; root->child = nullptr; root->sibling = nullptr; Element** element = &root->child; while (cursor.current < cursor.end) { if (*cursor.current == ';' || *cursor.current == '\r' || *cursor.current == '\n') { skipLine(&cursor); } else { OptionalError child = readTextElement(&cursor, allocator); if (child.isError()) { return Error(); } *element = child.getValue(); if (!*element) return root; element = &(*element)->sibling; } } return root; } static OptionalError tokenize(const u8* data, size_t size, u32& version, Allocator& allocator) { if (size < sizeof(Header)) return Error("Invalid header"); Cursor cursor; cursor.begin = data; cursor.current = data; cursor.end = data + size; #if __cplusplus >= 202002L && defined(__cpp_lib_bit_cast) const Header* header = std::bit_cast(cursor.current); #else Header header_temp; memcpy(&header_temp, cursor.current, sizeof(Header)); const Header* header = &header_temp; #endif cursor.current += sizeof(Header); version = header->version; Element* root = allocator.allocate(); root->first_property = nullptr; root->id.begin = nullptr; root->id.end = nullptr; root->child = nullptr; root->sibling = nullptr; Element** element = &root->child; for (;;) { OptionalError child = readElement(&cursor, header->version, allocator); if (child.isError()) { return Error(); } *element = child.getValue(); if (!*element) return root; element = &(*element)->sibling; } } static void parseTemplates(const Element& root) { const Element* defs = findChild(root, "Definitions"); if (!defs) return; std::unordered_map templates; Element* def = defs->child; while (def) { if (def->id == "ObjectType") { Element* subdef = def->child; while (subdef) { if (subdef->id == "PropertyTemplate") { DataView prop1 = def->first_property->value; DataView prop2 = subdef->first_property->value; std::string key((const char*)prop1.begin, prop1.end - prop1.begin); key += std::string((const char*)prop1.begin, prop1.end - prop1.begin); templates[key] = subdef; } subdef = subdef->sibling; } } def = def->sibling; } // TODO } struct Scene; enum class VertexDataMapping { BY_POLYGON_VERTEX, BY_POLYGON, BY_VERTEX }; struct Vec2AttributesImpl { std::vector values; std::vector indices; VertexDataMapping mapping; operator Vec2Attributes() const { return { values.data(), indices.data(), int(indices.empty() ? values.size() : indices.size()) }; } }; struct Vec3AttributesImpl { std::vector values; std::vector indices; VertexDataMapping mapping; operator Vec3Attributes() const { return { values.data(), indices.data(), int(indices.empty() ? values.size() : indices.size()), int(values.size()) }; } }; struct Vec4AttributesImpl { std::vector values; std::vector indices; VertexDataMapping mapping; operator Vec4Attributes() const { return { values.data(), indices.data(), int(indices.empty() ? values.size() : indices.size()) }; } }; struct GeometryPartitionImpl { std::vector polygons; int max_polygon_triangles = 0; int triangles_count = 0; }; struct GeometryDataImpl : GeometryData { Vec3AttributesImpl positions; Vec3AttributesImpl normals; Vec3AttributesImpl tangents; Vec4AttributesImpl colors; Vec2AttributesImpl uvs[Geometry::s_uvs_max]; std::vector partitions; std::vector materials; template T patchAttributes(const S& attr) const { T res = attr; if (!attr.values.empty() && attr.mapping == VertexDataMapping::BY_VERTEX && attr.indices.empty()) { res.indices = positions.indices.data(); res.count = int(positions.indices.size()); } return res; } Vec3Attributes getPositions() const override { return positions; } Vec3Attributes getNormals() const override { return patchAttributes(normals); } Vec2Attributes getUVs(int index) const override { return patchAttributes(uvs[index]); } Vec4Attributes getColors() const override { return patchAttributes(colors); } Vec3Attributes getTangents() const override { return patchAttributes(tangents); } int getPartitionCount() const override { return (int)partitions.size(); } GeometryPartition getPartition(int index) const override { if (index >= partitions.size()) return {nullptr, 0, 0, 0}; return { partitions[index].polygons.data(), int(partitions[index].polygons.size()), partitions[index].max_polygon_triangles, partitions[index].triangles_count }; } template bool postprocess(T& attr) { if (attr.values.empty()) return true; if (attr.mapping == VertexDataMapping::BY_VERTEX && !attr.indices.empty()) { if (positions.indices.empty()) return false; // not supported std::vector remapped; attr.mapping = VertexDataMapping::BY_POLYGON_VERTEX; remapped.resize(positions.indices.size()); for (int i = 0; i < remapped.size(); ++i) { remapped[i] = attr.indices[decodeIndex(positions.indices[i])]; } attr.indices = remapped; } else if (attr.mapping == VertexDataMapping::BY_POLYGON) { if (!attr.indices.empty()) return false; // not supported if (partitions.size() != 1) return false; // not supported if (partitions[0].polygons.size() != attr.values.size()) return false; // invalid std::vector remapped; attr.mapping = VertexDataMapping::BY_POLYGON_VERTEX; remapped.resize(positions.indices.size()); for (int i = 0, c = (int)partitions[0].polygons.size(); i < c; ++i) { GeometryPartition::Polygon& polygon = partitions[0].polygons[i]; for (int j = polygon.from_vertex; j < polygon.from_vertex + polygon.vertex_count; ++j) { remapped[j] = i; } } attr.indices = remapped; } return true; } bool postprocess() { if (materials.empty()) { GeometryPartitionImpl& partition = emplace_back(partitions); int polygon_count = 0; for (int i : positions.indices) { if (i < 0) ++polygon_count; } partition.polygons.reserve(polygon_count); int polygon_start = 0; int max_polygon_triangles = 0; int total_triangles = 0; int* indices = positions.indices.data(); for (int i = 0, c = (int)positions.indices.size(); i < c; ++i) { if (indices[i] < 0) { int vertex_count = i - polygon_start + 1; if (vertex_count > 2) { partition.polygons.push_back({polygon_start, vertex_count}); indices[i] = -indices[i] - 1; int triangles = vertex_count - 2; total_triangles += triangles; if (triangles > max_polygon_triangles) max_polygon_triangles = triangles; } polygon_start = i + 1; } } partition.max_polygon_triangles = max_polygon_triangles; partition.triangles_count = total_triangles; } else { int max_partition = 0; for (int m : materials) { if (m > max_partition) max_partition = m; } partitions.resize(max_partition + 1); u32 polygon_idx = 0; int* indices = positions.indices.data(); int num_polygon_vertices = 0; int polygon_start = 0; for (int i = 0, c = (int)positions.indices.size(); i < c; ++i) { ++num_polygon_vertices; if (indices[i] < 0) { u32 material_index = materials[polygon_idx]; GeometryPartitionImpl& partition = partitions[material_index]; partition.polygons.push_back({polygon_start, num_polygon_vertices}); int triangles = num_polygon_vertices - 2; partition.triangles_count += triangles; if (triangles > partition.max_polygon_triangles) partition.max_polygon_triangles = triangles; indices[i] = -indices[i] - 1; polygon_start = i + 1; ++polygon_idx; num_polygon_vertices = 0; } } } postprocess(normals); postprocess(tangents); for (Vec2AttributesImpl& uv : uvs) postprocess(uv); postprocess(colors); return true; } }; Mesh::Mesh(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct GeometryImpl : Geometry, GeometryDataImpl { const Skin* skin = nullptr; const BlendShape* blendShape = nullptr; GeometryImpl(const Scene& _scene, const IElement& _element) : Geometry(_scene, _element) { } Type getType() const override { return Type::GEOMETRY; } const GeometryData& getGeometryData() const override { return *this; } const Skin* getSkin() const override { return skin; } const BlendShape* getBlendShape() const override { return blendShape; } }; struct MeshImpl : Mesh { MeshImpl(const Scene& _scene, const IElement& _element) : Mesh(_scene, _element) { is_node = true; } DMatrix getGeometricMatrix() const override { DVec3 translation = resolveVec3Property(*this, "GeometricTranslation", {0, 0, 0}); DVec3 rotation = resolveVec3Property(*this, "GeometricRotation", {0, 0, 0}); DVec3 scale = resolveVec3Property(*this, "GeometricScaling", {1, 1, 1}); DMatrix scale_mtx = makeIdentity(); scale_mtx.m[0] = (float)scale.x; scale_mtx.m[5] = (float)scale.y; scale_mtx.m[10] = (float)scale.z; DMatrix mtx = getRotationMatrix(rotation, RotationOrder::EULER_XYZ); setTranslation(translation, &mtx); return scale_mtx * mtx; } Type getType() const override { return Type::MESH; } const Pose* getPose() const override { return pose; } const Geometry* getGeometry() const override { return geometry; } const Material* getMaterial(int index) const override { return materials[index]; } int getMaterialCount() const override { return (int)materials.size(); } const GeometryData& getGeometryData() const override { return geometry ? static_cast(*geometry) : geometry_data; } const Skin* getSkin() const override { return geometry ? geometry->getSkin() : skin; } const BlendShape* getBlendShape() const override { return geometry ? geometry->getBlendShape() : blendShape; } const Pose* pose = nullptr; const GeometryImpl* geometry = nullptr; std::vector materials; const Skin* skin = nullptr; const BlendShape* blendShape = nullptr; // old formats do not use Geometry nodes but embed vertex data directly in Mesh GeometryDataImpl geometry_data; }; Material::Material(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct MaterialImpl : Material { MaterialImpl(const Scene& _scene, const IElement& _element) : Material(_scene, _element) { for (const Texture*& tex : textures) tex = nullptr; } Type getType() const override { return Type::MATERIAL; } const Texture* getTexture(Texture::TextureType type) const override { return textures[type]; } Color getDiffuseColor() const override { return diffuse_color; } Color getSpecularColor() const override { return specular_color; } Color getReflectionColor() const override { return reflection_color; }; Color getAmbientColor() const override { return ambient_color; }; Color getEmissiveColor() const override { return emissive_color; }; double getDiffuseFactor() const override { return diffuse_factor; }; double getSpecularFactor() const override { return specular_factor; }; double getReflectionFactor() const override { return reflection_factor; }; double getShininess() const override { return shininess; }; double getShininessExponent() const override { return shininess_exponent; }; double getAmbientFactor() const override { return ambient_factor; }; double getBumpFactor() const override { return bump_factor; }; double getEmissiveFactor() const override { return emissive_factor; }; const Texture* textures[Texture::TextureType::COUNT]; Color diffuse_color; Color specular_color; Color reflection_color; Color ambient_color; Color emissive_color; double diffuse_factor; double specular_factor; double reflection_factor; double shininess; double shininess_exponent; double ambient_factor; double bump_factor; double emissive_factor; }; struct LimbNodeImpl : Object { LimbNodeImpl(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { is_node = true; } Type getType() const override { return Type::LIMB_NODE; } }; struct NullImpl : Object { NullImpl(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { is_node = true; } Type getType() const override { return Type::NULL_NODE; } }; NodeAttribute::NodeAttribute(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct NodeAttributeImpl : NodeAttribute { NodeAttributeImpl(const Scene& _scene, const IElement& _element) : NodeAttribute(_scene, _element) { } Type getType() const override { return Type::NODE_ATTRIBUTE; } DataView getAttributeType() const override { return attribute_type; } DataView attribute_type; }; Geometry::Geometry(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } Shape::Shape(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct ShapeImpl : Shape { std::vector vertices; std::vector normals; std::vector indices; ShapeImpl(const Scene& _scene, const IElement& _element) : Shape(_scene, _element) {} bool postprocess(GeometryImpl& geom, Allocator& allocator); Type getType() const override { return Type::SHAPE; } int getVertexCount() const override { return (int)vertices.size(); } int getIndexCount() const override { return (int)indices.size(); } const Vec3* getVertices() const override { return &vertices[0]; } const Vec3* getNormals() const override { return normals.empty() ? nullptr : &normals[0]; } const int* getIndices() const override { return indices.empty() ? nullptr : &indices[0]; } }; Cluster::Cluster(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct ClusterImpl : Cluster { ClusterImpl(const Scene& _scene, const IElement& _element) : Cluster(_scene, _element) { } const int* getIndices() const override { return &indices[0]; } int getIndicesCount() const override { return (int)indices.size(); } const double* getWeights() const override { return &weights[0]; } int getWeightsCount() const override { return (int)weights.size(); } DMatrix getTransformMatrix() const override { return transform_matrix; } DMatrix getTransformLinkMatrix() const override { return transform_link_matrix; } Object* getLink() const override { return link; } bool postprocess() { assert(skin); GeometryDataImpl* geom = static_cast(static_cast(skin->resolveObjectLinkReverse(Object::Type::GEOMETRY))); if (!geom) { MeshImpl* mesh = (MeshImpl*)skin->resolveObjectLinkReverse(Object::Type::MESH); if(!mesh) return false; geom = &mesh->geometry_data; } const Element* indexes = findChild((const Element&)element, "Indexes"); if (indexes && indexes->first_property) { if (!parseVecData(*indexes->first_property, &indices)) return false; } const Element* weights_el = findChild((const Element&)element, "Weights"); if (weights_el && weights_el->first_property) { if (!parseVecData(*weights_el->first_property, &weights)) return false; } return true; } Object* link = nullptr; Skin* skin = nullptr; std::vector indices; std::vector weights; DMatrix transform_matrix; DMatrix transform_link_matrix; Type getType() const override { return Type::CLUSTER; } }; AnimationStack::AnimationStack(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } AnimationLayer::AnimationLayer(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } AnimationCurve::AnimationCurve(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } AnimationCurveNode::AnimationCurveNode(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct AnimationStackImpl : AnimationStack { AnimationStackImpl(const Scene& _scene, const IElement& _element) : AnimationStack(_scene, _element) { } const AnimationLayer* getLayer(int index) const override { return resolveObjectLink(index); } Type getType() const override { return Type::ANIMATION_STACK; } }; struct AnimationCurveImpl : AnimationCurve { AnimationCurveImpl(const Scene& _scene, const IElement& _element) : AnimationCurve(_scene, _element) { } int getKeyCount() const override { return (int)times.size(); } const i64* getKeyTime() const override { return ×[0]; } const float* getKeyValue() const override { return &values[0]; } std::vector times; std::vector values; Type getType() const override { return Type::ANIMATION_CURVE; } }; Skin::Skin(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct SkinImpl : Skin { SkinImpl(const Scene& _scene, const IElement& _element) : Skin(_scene, _element) { } int getClusterCount() const override { return (int)clusters.size(); } const Cluster* getCluster(int idx) const override { return clusters[idx]; } Type getType() const override { return Type::SKIN; } std::vector clusters; }; BlendShapeChannel::BlendShapeChannel(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct BlendShapeChannelImpl : BlendShapeChannel { BlendShapeChannelImpl(const Scene& _scene, const IElement& _element) : BlendShapeChannel(_scene, _element) { } double getDeformPercent() const override { return deformPercent; } int getShapeCount() const override { return (int)shapes.size(); } const Shape* getShape(int idx) const override { return shapes[idx]; } Type getType() const override { return Type::BLEND_SHAPE_CHANNEL; } bool postprocess(Allocator& allocator) { assert(blendShape); GeometryImpl* geom = (GeometryImpl*)blendShape->resolveObjectLinkReverse(Object::Type::GEOMETRY); if (!geom) return false; const Element* deform_percent_el = findChild((const Element&)element, "DeformPercent"); if (deform_percent_el && deform_percent_el->first_property) { if (!parseDouble(*deform_percent_el->first_property, &deformPercent)) return false; } const Element* full_weights_el = findChild((const Element&)element, "FullWeights"); if (full_weights_el && full_weights_el->first_property) { if (!parseVecData(*full_weights_el->first_property, &fullWeights)) return false; } for (int i = 0; i < (int)shapes.size(); i++) { auto shape = (ShapeImpl*)shapes[i]; if (!shape->postprocess(*geom, allocator)) return false; } return true; } BlendShape* blendShape = nullptr; double deformPercent = 0; std::vector fullWeights; std::vector shapes; }; BlendShape::BlendShape(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct BlendShapeImpl : BlendShape { BlendShapeImpl(const Scene& _scene, const IElement& _element) : BlendShape(_scene, _element) { } int getBlendShapeChannelCount() const override { return (int)blendShapeChannels.size(); } const BlendShapeChannel* getBlendShapeChannel(int idx) const override { return blendShapeChannels[idx]; } Type getType() const override { return Type::BLEND_SHAPE; } std::vector blendShapeChannels; }; Texture::Texture(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } Pose::Pose(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { } struct PoseImpl : Pose { PoseImpl(const Scene& _scene, const IElement& _element) : Pose(_scene, _element) {} bool postprocess(Scene& scene); DMatrix getMatrix() const override { return matrix; } const Object* getNode() const override { return node; } Type getType() const override { return Type::POSE; } DMatrix matrix; Object* node = nullptr; u64 node_id; }; struct TextureImpl : Texture { TextureImpl(const Scene& _scene, const IElement& _element) : Texture(_scene, _element) { } DataView getRelativeFileName() const override { return relative_filename; } DataView getFileName() const override { return filename; } DataView getEmbeddedData() const override; DataView media; DataView filename; DataView relative_filename; Type getType() const override { return Type::TEXTURE; } }; struct LightImpl : Light { LightImpl(const Scene& _scene, const IElement& _element) : Light(_scene, _element) { } Type getType() const override { return Type::LIGHT; } LightType getLightType() const override { return lightType; } bool doesCastLight() const override { return castLight; } bool doesDrawVolumetricLight() const override { // Return the draw volumetric light property based on the stored data (WIP) return false; } bool doesDrawGroundProjection() const override { // Return the draw ground projection property based on the stored data (WIP) return false; } bool doesDrawFrontFacingVolumetricLight() const override { // Return the draw front-facing volumetric light property based on the stored data (WIP) return false; } Color getColor() const override { return color; } double getIntensity() const override { return intensity; } double getInnerAngle() const override { return innerAngle; } double getOuterAngle() const override { return outerAngle; } double getFog() const override { return fog; } DecayType getDecayType() const override { return decayType; } double getDecayStart() const override { return decayStart; } // Near attenuation bool doesEnableNearAttenuation() const override { return enableNearAttenuation; } double getNearAttenuationStart() const override { return nearAttenuationStart; } double getNearAttenuationEnd() const override { return nearAttenuationEnd; } // Far attenuation bool doesEnableFarAttenuation() const override { return enableFarAttenuation; } double getFarAttenuationStart() const override { return farAttenuationStart; } double getFarAttenuationEnd() const override { return farAttenuationEnd; } // Shadows const Texture* getShadowTexture() const override { return shadowTexture; } bool doesCastShadows() const override { return castShadows; } Color getShadowColor() const override { return shadowColor; } // Member variables to store light properties //------------------------------------------------------------------------- LightType lightType = LightType::POINT; bool castLight = true; Color color = {1, 1, 1}; // Light color (RGB values) double intensity = 100.0; double innerAngle = 0.0; double outerAngle = 45.0; double fog = 50; DecayType decayType = DecayType::QUADRATIC; double decayStart = 1.0; bool enableNearAttenuation = false; double nearAttenuationStart = 0.0; double nearAttenuationEnd = 0.0; bool enableFarAttenuation = false; double farAttenuationStart = 0.0; double farAttenuationEnd = 0.0; const Texture* shadowTexture = nullptr; bool castShadows = true; Color shadowColor = {0, 0, 0}; }; static float OFBX_PI = 3.14159265358979323846f; struct CameraImpl : public Camera { CameraImpl(const Scene& _scene, const IElement& _element) : Camera(_scene, _element) { } ProjectionType projectionType = ProjectionType::PERSPECTIVE; ApertureMode apertureMode = ApertureMode::HORIZONTAL; // Used to determine the FOV double filmHeight = 36.0; double filmWidth = 24.0; double aspectHeight = 1.0; double aspectWidth = 1.0; double nearPlane = 0.1; double farPlane = 1000.0; bool autoComputeClipPanes = true; GateFit gateFit = GateFit::HORIZONTAL; double filmAspectRatio = 1.0; double focalLength = 50.0; double focusDistance = 50.0; DVec3 backgroundColor = {0, 0, 0}; DVec3 interestPosition = {0, 0, 0}; double fieldOfView = 60.0; Type getType() const override { return Type::CAMERA; } ProjectionType getProjectionType() const override { return projectionType; } ApertureMode getApertureMode() const override { return apertureMode; } double getFilmHeight() const override { return filmHeight; } double getFilmWidth() const override { return filmWidth; } double getAspectHeight() const override { return aspectHeight; } double getAspectWidth() const override { return aspectWidth; } double getNearPlane() const override { return nearPlane; } double getFarPlane() const override { return farPlane; } bool doesAutoComputeClipPanes() const override { return autoComputeClipPanes; } GateFit getGateFit() const override { return gateFit; } double getFilmAspectRatio() const override { return filmAspectRatio; } double getFocalLength() const override { return focalLength; } double getFocusDistance() const override { return focusDistance; } DVec3 getBackgroundColor() const override { return backgroundColor; } DVec3 getInterestPosition() const override { return interestPosition; } void CalculateFOV() { switch (apertureMode) { case Camera::ApertureMode::HORIZONTAL: fieldOfView = 2.0 * atan(filmWidth / (2.0 * focalLength)) * 180.0 / OFBX_PI; return; case Camera::ApertureMode::VERTICAL: fieldOfView = 2.0 * atan(filmHeight / (2.0 * focalLength)) * 180.0 / OFBX_PI; return; case Camera::ApertureMode::HORIZANDVERT: fieldOfView = 2.0 * atan(sqrt(filmWidth * filmWidth + filmHeight * filmHeight) / (2.0 * focalLength)) * 180.0 / OFBX_PI; return; case Camera::ApertureMode::FOCALLENGTH: fieldOfView = 2.0 * atan(filmHeight / (2.0 * focalLength)) * 180.0 / OFBX_PI; // Same as vertical ¯\_(ツ)_/¯ return; default: fieldOfView = 60.0; } } }; struct Root : Object { Root(const Scene& _scene, const IElement& _element) : Object(_scene, _element) { copyString(name, "RootNode"); is_node = true; } Type getType() const override { return Type::ROOT; } }; struct Scene : IScene { struct Connection { enum Type { OBJECT_OBJECT, OBJECT_PROPERTY, PROPERTY_OBJECT, PROPERTY_PROPERTY, }; Type type = OBJECT_OBJECT; u64 from_object = 0; u64 to_object = 0; DataView from_property; DataView to_property; }; struct ObjectPair { const Element* element; Object* object; }; int getAnimationStackCount() const override { return (int)m_animation_stacks.size(); } int getGeometryCount() const override { return (int)m_geometries.size(); } int getMeshCount() const override { return (int)m_meshes.size(); } float getSceneFrameRate() const override { return m_scene_frame_rate; } const GlobalInfo* getGlobalInfo() const override { return &m_info; } const GlobalSettings* getGlobalSettings() const override { return &m_settings; } const Object* const* getAllObjects() const override { return m_all_objects.empty() ? nullptr : &m_all_objects[0]; } int getAllObjectCount() const override { return (int)m_all_objects.size(); } int getEmbeddedDataCount() const override { return (int)m_videos.size(); } DataView getEmbeddedData(int index) const override { return m_videos[index].content; } bool isEmbeddedBase64(int index) const override { return m_videos[index].is_base_64; } const IElementProperty* getEmbeddedBase64Data(int index) const override { return m_videos[index].base64_property; } DataView getEmbeddedFilename(int index) const override { return m_videos[index].filename; } const AnimationStack* getAnimationStack(int index) const override { assert(index >= 0); assert(index < m_animation_stacks.size()); return m_animation_stacks[index]; } const Mesh* getMesh(int index) const override { assert(index >= 0); assert(index < m_meshes.size()); return m_meshes[index]; } const Geometry* getGeometry(int index) const override { assert(index >= 0); assert(index < m_geometries.size()); return m_geometries[index]; } const TakeInfo* getTakeInfo(const char* name) const override { for (const TakeInfo& info : m_take_infos) { if (info.name == name) return &info; } return nullptr; } const Camera* getCamera(int index) const override { assert(index >= 0); assert(index < m_cameras.size()); return m_cameras[index]; } int getCameraCount() const override { return (int)m_cameras.size(); } const Light* getLight(int index) const override { assert(index >= 0); assert(index < m_lights.size()); return m_lights[index]; } int getLightCount() const override { return (int)m_lights.size(); } const IElement* getRootElement() const override { return m_root_element; } const Object* getRoot() const override { return m_root; } void destroy() override { delete this; } ~Scene() override { for(Object* ptr : m_all_objects) { ptr->~Object(); } } bool finalize(); Element* m_root_element = nullptr; Root* m_root = nullptr; float m_scene_frame_rate = -1; GlobalInfo m_info; GlobalSettings m_settings; std::unordered_map m_fake_ids; std::unordered_map m_object_map; std::vector m_all_objects; std::vector m_meshes; std::vector m_geometries; std::vector m_animation_stacks; std::vector m_cameras; std::vector m_lights; std::vector m_connections; std::vector m_data; std::vector m_take_infos; std::vector