Skip to content

Instantly share code, notes, and snippets.

@bqqbarbhg
Created April 29, 2024 22:57
Show Gist options
  • Save bqqbarbhg/c86d8264480f9cdd3570de6eb8ee3902 to your computer and use it in GitHub Desktop.
Save bqqbarbhg/c86d8264480f9cdd3570de6eb8ee3902 to your computer and use it in GitHub Desktop.
This file has been truncated, but you can view the full file.
#ifndef UFBX_UFBX_C_INCLUDED
#define UFBX_UFBX_C_INCLUDED
#if defined(UFBX_HEADER_PATH)
#include UFBX_HEADER_PATH
#else
#include "ufbx.h"
#endif
// -- User configuration
// User configuration:
// UFBX_REGRESSION Enable regression mode for development
// UFBX_UBSAN Explicitly enable undefined behavior sanitizer workarounds
// UFBX_NO_UNALIGNED_LOADS Do not use unaligned loads even when they are supported
// UFBX_USE_UNALIGNED_LOADS Forcibly use unaligned loads on unknown platforms
// UFBX_USE_SSE Explicitly enable SSE2 support (for x86)
// UFBX_HAS_FTELLO Allow ufbx to use `ftello()` to measure file size
// UFBX_WASM_32BIT Optimize WASM for 32-bit architectures
// UFBX_TRACE Log calls of `ufbxi_check()` for tracing execution
// UFBX_LITTLE_ENDIAN=0/1 Explicitly define little/big endian architecture
// UFBX_PATH_SEPARATOR='' Specify default platform path separator
// Mostly internal for debugging:
// UFBX_STATIC_ANALYSIS Enable static analysis augmentation
// UFBX_DEBUG_BINARY_SEARCH Force using binary search for debugging
// UFBX_EXTENSIVE_THREADING Use threads for small inputs
#if defined(UFBX_CONFIG_SOURCE)
#include UFBX_CONFIG_SOURCE
#endif
// -- Configuration
#define UFBXI_MAX_NON_ARRAY_VALUES 8
#define UFBXI_MAX_NODE_DEPTH 32
#define UFBXI_MAX_XML_DEPTH 32
#define UFBXI_MAX_SKIP_SIZE 0x40000000
#define UFBXI_MAP_MAX_SCAN 32
#define UFBXI_KD_FAST_DEPTH 6
#define UFBXI_HUGE_MAX_SCAN 16
#define UFBXI_MIN_FILE_FORMAT_LOOKAHEAD 32
#define UFBXI_FACE_GROUP_HASH_BITS 8
#define UFBXI_MIN_THREADED_DEFLATE_BYTES 256
#define UFBXI_MIN_THREADED_ASCII_VALUES 64
#define UFBXI_GEOMETRY_CACHE_BUFFER_SIZE 512
#ifndef UFBXI_MAX_NURBS_ORDER
#define UFBXI_MAX_NURBS_ORDER 128
#endif
// By default enough to have squares be non-denormal
#ifndef UFBX_EPSILON
#define UFBX_EPSILON (sizeof(ufbx_real) == sizeof(float) ? \
(ufbx_real)1.0842021795674597e-19f : (ufbx_real)1.4916681462400413e-154)
#endif
// -- Feature exclusion
#if !defined(UFBX_MINIMAL)
#if !defined(UFBX_NO_SUBDIVISION)
#define UFBXI_FEATURE_SUBDIVISION 1
#endif
#if !defined(UFBX_NO_TESSELLATION)
#define UFBXI_FEATURE_TESSELLATION 1
#endif
#if !defined(UFBX_NO_GEOMETRY_CACHE)
#define UFBXI_FEATURE_GEOMETRY_CACHE 1
#endif
#if !defined(UFBX_NO_SCENE_EVALUATION)
#define UFBXI_FEATURE_SCENE_EVALUATION 1
#endif
#if !defined(UFBX_NO_SKINNING_EVALUATION)
#define UFBXI_FEATURE_SKINNING_EVALUATION 1
#endif
#if !defined(UFBX_NO_ANIMATION_BAKING)
#define UFBXI_FEATURE_ANIMATION_BAKING 1
#endif
#if !defined(UFBX_NO_TRIANGULATION)
#define UFBXI_FEATURE_TRIANGULATION 1
#endif
#if !defined(UFBX_NO_INDEX_GENERATION)
#define UFBXI_FEATURE_INDEX_GENERATION 1
#endif
#if !defined(UFBX_NO_FORMAT_OBJ)
#define UFBXI_FEATURE_FORMAT_OBJ 1
#endif
#endif
#if defined(UFBX_DEV)
#if !defined(UFBX_NO_ERROR_STACK)
#define UFBXI_FEATURE_ERROR_STACK 1
#endif
#endif
#if !defined(UFBXI_FEATURE_SUBDIVISION) && defined(UFBX_ENABLE_SUBDIVISION)
#define UFBXI_FEATURE_SUBDIVISION 1
#endif
#if !defined(UFBXI_FEATURE_TESSELLATION) && defined(UFBX_ENABLE_TESSELLATION)
#define UFBXI_FEATURE_TESSELLATION 1
#endif
#if !defined(UFBXI_FEATURE_GEOMETRY_CACHE) && defined(UFBX_ENABLE_GEOMETRY_CACHE)
#define UFBXI_FEATURE_GEOMETRY_CACHE 1
#endif
#if !defined(UFBXI_FEATURE_SCENE_EVALUATION) && defined(UFBX_ENABLE_SCENE_EVALUATION)
#define UFBXI_FEATURE_SCENE_EVALUATION 1
#endif
#if !defined(UFBXI_FEATURE_SKINNING_EVALUATION) && defined(UFBX_ENABLE_SKINNING_EVALUATION)
#define UFBXI_FEATURE_SKINNING_EVALUATION 1
#endif
#if !defined(UFBXI_FEATURE_ANIMATION_BAKING) && defined(UFBX_ENABLE_ANIMATION_BAKING)
#define UFBXI_FEATURE_ANIMATION_BAKING 1
#endif
#if !defined(UFBXI_FEATURE_TRIANGULATION) && defined(UFBX_ENABLE_TRIANGULATION)
#define UFBXI_FEATURE_TRIANGULATION 1
#endif
#if !defined(UFBXI_FEATURE_INDEX_GENERATION) && defined(UFBX_ENABLE_INDEX_GENERATION)
#define UFBXI_FEATURE_INDEX_GENERATION 1
#endif
#if !defined(UFBXI_FEATURE_FORMAT_OBJ) && defined(UFBX_ENABLE_FORMAT_OBJ)
#define UFBXI_FEATURE_FORMAT_OBJ 1
#endif
#if !defined(UFBXI_FEATURE_ERROR_STACK) && defined(UFBX_ENABLE_ERROR_STACK)
#define UFBXI_FEATURE_ERROR_STACK 1
#endif
#if !defined(UFBXI_FEATURE_SUBDIVISION)
#define UFBXI_FEATURE_SUBDIVISION 0
#endif
#if !defined(UFBXI_FEATURE_TESSELLATION)
#define UFBXI_FEATURE_TESSELLATION 0
#endif
#if !defined(UFBXI_FEATURE_GEOMETRY_CACHE)
#define UFBXI_FEATURE_GEOMETRY_CACHE 0
#endif
#if !defined(UFBXI_FEATURE_SCENE_EVALUATION)
#define UFBXI_FEATURE_SCENE_EVALUATION 0
#endif
#if !defined(UFBXI_FEATURE_SKINNING_EVALUATION)
#define UFBXI_FEATURE_SKINNING_EVALUATION 0
#endif
#if !defined(UFBXI_FEATURE_ANIMATION_BAKING)
#define UFBXI_FEATURE_ANIMATION_BAKING 0
#endif
#if !defined(UFBXI_FEATURE_TRIANGULATION)
#define UFBXI_FEATURE_TRIANGULATION 0
#endif
#if !defined(UFBXI_FEATURE_INDEX_GENERATION)
#define UFBXI_FEATURE_INDEX_GENERATION 0
#endif
#if !defined(UFBXI_FEATURE_FORMAT_OBJ)
#define UFBXI_FEATURE_FORMAT_OBJ 0
#endif
#if !defined(UFBXI_FEATURE_ERROR_STACK)
#define UFBXI_FEATURE_ERROR_STACK 0
#endif
// Derived features
#if UFBXI_FEATURE_GEOMETRY_CACHE
#define UFBXI_FEATURE_XML 1
#else
#define UFBXI_FEATURE_XML 0
#endif
#if UFBXI_FEATURE_TRIANGULATION
#define UFBXI_FEATURE_KD 1
#else
#define UFBXI_FEATURE_KD 0
#endif
#if !UFBXI_FEATURE_SUBDIVISION || !UFBXI_FEATURE_TESSELLATION || !UFBXI_FEATURE_GEOMETRY_CACHE || !UFBXI_FEATURE_SCENE_EVALUATION || !UFBXI_FEATURE_SKINNING_EVALUATION || !UFBXI_FEATURE_ANIMATION_BAKING || !UFBXI_FEATURE_TRIANGULATION || !UFBXI_FEATURE_INDEX_GENERATION || !UFBXI_FEATURE_XML || !UFBXI_FEATURE_KD || !UFBXI_FEATURE_FORMAT_OBJ
#define UFBXI_PARTIAL_FEATURES 1
#endif
// -- Headers
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <locale.h>
#include <float.h>
#if !defined(UFBX_NO_MATH_H)
#include <math.h>
#define UFBX_INFINITY INFINITY
#define UFBX_NAN NAN
#endif
#if !defined(UFBX_MATH_PREFIX)
#define UFBX_MATH_PREFIX
#endif
#define ufbxi_math_cat2(a, b) a##b
#define ufbxi_math_cat(a, b) ufbxi_math_cat2(a, b)
#define ufbxi_math_fn(name) ufbxi_math_cat(UFBX_MATH_PREFIX, name)
#if !defined(UFBX_NO_MATH_DEFINES)
#define ufbx_sqrt ufbxi_math_fn(sqrt)
#define ufbx_fabs ufbxi_math_fn(fabs)
#define ufbx_pow ufbxi_math_fn(pow)
#define ufbx_sin ufbxi_math_fn(sin)
#define ufbx_cos ufbxi_math_fn(cos)
#define ufbx_tan ufbxi_math_fn(tan)
#define ufbx_asin ufbxi_math_fn(asin)
#define ufbx_acos ufbxi_math_fn(acos)
#define ufbx_atan ufbxi_math_fn(atan)
#define ufbx_atan2 ufbxi_math_fn(atan2)
#define ufbx_copysign ufbxi_math_fn(copysign)
#define ufbx_fmin ufbxi_math_fn(fmin)
#define ufbx_fmax ufbxi_math_fn(fmax)
#define ufbx_nextafter ufbxi_math_fn(nextafter)
#define ufbx_rint ufbxi_math_fn(rint)
#define ufbx_ceil ufbxi_math_fn(ceil)
#define ufbx_isnan ufbxi_math_fn(isnan)
#endif
#if defined(UFBX_NO_MATH_H) && !defined(UFBX_NO_MATH_DECLARATIONS)
double ufbx_sqrt(double x);
double ufbx_sin(double x);
double ufbx_cos(double x);
double ufbx_tan(double x);
double ufbx_asin(double x);
double ufbx_acos(double x);
double ufbx_atan(double x);
double ufbx_atan2(double y, double x);
double ufbx_pow(double x, double y);
double ufbx_fmin(double a, double b);
double ufbx_fmax(double a, double b);
double ufbx_fabs(double x);
double ufbx_copysign(double x, double y);
double ufbx_nextafter(double x, double y);
double ufbx_rint(double x);
double ufbx_ceil(double x);
int ufbx_isnan(double x);
#endif
#if !defined(UFBX_INFINITY)
#define UFBX_INFINITY (1e+300 * 1e+300)
#endif
#if !defined(UFBX_NAN)
#define UFBX_NAN (UFBX_INFINITY * 0.0f)
#endif
// -- Platform
#if defined(_MSC_VER)
#define UFBXI_MSC_VER _MSC_VER
#else
#define UFBXI_MSC_VER 0
#endif
#if defined(__GNUC__)
#define UFBXI_GNUC __GNUC__
#else
#define UFBXI_GNUC 0
#endif
#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER)
#define ufbxi_noinline __declspec(noinline)
#define ufbxi_forceinline __forceinline
#define ufbxi_restrict __restrict
#if defined(_Check_return_)
#define ufbxi_nodiscard _Check_return_
#else
#define ufbxi_nodiscard
#endif
#define ufbxi_unused
#define ufbxi_unlikely(cond) (cond)
#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__))
#define ufbxi_noinline __attribute__((noinline))
#define ufbxi_forceinline inline __attribute__((always_inline))
#define ufbxi_restrict __restrict
#define ufbxi_nodiscard __attribute__((warn_unused_result))
#define ufbxi_unused __attribute__((unused))
#define ufbxi_unlikely(cond) __builtin_expect((cond), 0)
#else
#define ufbxi_noinline
#define ufbxi_forceinline
#define ufbxi_nodiscard
#define ufbxi_restrict
#define ufbxi_unused
#define ufbxi_unlikely(cond) (cond)
#endif
#if !defined(UFBX_STANDARD_C) && defined(__clang__)
#define ufbxi_nounroll _Pragma("clang loop unroll(disable)") _Pragma("clang loop vectorize(disable)")
#elif !defined(UFBX_STANDARD_C) && UFBXI_GNUC >= 8
#define ufbxi_nounroll _Pragma("GCC unroll 0")
#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER)
#define ufbxi_nounroll __pragma(loop(no_vector))
#else
#define ufbxi_nounroll
#endif
#if defined(__GNUC__) && !defined(__clang__)
#define ufbxi_ignore(cond) (void)!(cond)
#else
#define ufbxi_ignore(cond) (void)(cond)
#endif
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 4061) // enumerator 'ENUM' in switch of enum 'enum' is not explicitly handled by a case label
#pragma warning(disable: 4200) // nonstandard extension used: zero-sized array in struct/union
#pragma warning(disable: 4201) // nonstandard extension used: nameless struct/union
#pragma warning(disable: 4210) // nonstandard extension used: function given file scope
#pragma warning(disable: 4127) // conditional expression is constant
#pragma warning(disable: 4706) // assignment within conditional expression
#pragma warning(disable: 4789) // buffer 'type_and_name' of size 8 bytes will be overrun; 16 bytes will be written starting at offset 0
#pragma warning(disable: 4820) // type': 'N' bytes padding added after data member 'member'
#if defined(UFBX_STANDARD_C)
#pragma warning(disable: 4996) // 'fopen': This function or variable may be unsafe. Consider using fopen_s instead.
#endif
#if defined(UFBXI_PARTIAL_FEATURES)
#pragma warning(disable: 4100) // 'name': unreferenced formal parameter
#pragma warning(disable: 4505) // 'func': unreferenced function with internal linkage has been removed
#endif
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#pragma clang diagnostic ignored "-Wmissing-braces"
#pragma clang diagnostic ignored "-Wdouble-promotion"
#pragma clang diagnostic ignored "-Wpedantic"
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wcast-align"
#pragma clang diagnostic ignored "-Wcovered-switch-default"
#pragma clang diagnostic ignored "-Wpadded"
#pragma clang diagnostic ignored "-Wswitch-enum"
#pragma clang diagnostic ignored "-Wfloat-equal"
#pragma clang diagnostic ignored "-Wformat-nonliteral"
#if __has_warning("-Watomic-implicit-seq-cst")
#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst"
#endif
#if defined(UFBX_STANDARD_C)
#pragma clang diagnostic ignored "-Wunused-function"
#endif
#if defined(UFBXI_PARTIAL_FEATURES)
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#if defined(__cplusplus)
#pragma clang diagnostic ignored "-Wold-style-cast"
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
#else
#pragma clang diagnostic ignored "-Wdeclaration-after-statement"
#pragma clang diagnostic ignored "-Wbad-function-cast"
#endif
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wmissing-braces"
#pragma GCC diagnostic ignored "-Wdouble-promotion"
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wcast-align"
#pragma GCC diagnostic ignored "-Wpadded"
#pragma GCC diagnostic ignored "-Wswitch-enum"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wlong-long"
#if defined(UFBX_STANDARD_C)
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
#if defined(UFBXI_PARTIAL_FEATURES)
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
#if defined(__cplusplus)
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#else
#pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
#pragma GCC diagnostic ignored "-Wbad-function-cast"
#if __GNUC__ >= 5
#pragma GCC diagnostic ignored "-Wc90-c99-compat"
#pragma GCC diagnostic ignored "-Wc99-c11-compat"
#endif
#endif
#endif
#if !defined(ufbx_static_assert)
#if defined(__cplusplus) && __cplusplus >= 201103
#define ufbx_static_assert(desc, cond) static_assert(cond, #desc ": " #cond)
#else
#define ufbx_static_assert(desc, cond) typedef char ufbxi_static_assert_##desc[(cond)?1:-1]
#endif
#endif
#if defined(__has_feature)
#if __has_feature(undefined_behavior_sanitizer) && !defined(UFBX_UBSAN)
#define UFBX_UBSAN 1
#endif
#endif
#if defined(__SANITIZE_UNDEFINED__) && !defined(UFBX_UBSAN)
#define UFBX_UBSAN 1
#endif
// Don't use unaligned loads with UB-sanitizer
#if defined(UFBX_UBSAN) && !defined(UFBX_NO_UNALIGNED_LOADS)
#define UFBX_NO_UNALIGNED_LOADS
#endif
#if defined(__clang_analyzer__) && !defined(UFBX_STATIC_ANALYSIS)
#define UFBX_STATIC_ANALYSIS 1
#endif
#if defined(UFBX_STATIC_ANALYSIS)
bool ufbxi_analysis_opaque;
#define ufbxi_maybe_null(ptr) (ufbxi_analysis_opaque ? (ptr) : NULL)
#else
#define ufbxi_maybe_null(ptr) (ptr)
#endif
#if !defined(ufbxi_trace)
#if defined(UFBX_TRACE)
#define ufbxi_trace(desc) (fprintf(stderr, "ufbx trace: %s:%d: %s\n", __FILE__, __LINE__, #desc), fflush(stderr), desc)
#else
#define ufbxi_trace(desc) (desc)
#endif
#endif
#ifndef UFBX_PATH_SEPARATOR
#if defined(_WIN32)
#define UFBX_PATH_SEPARATOR '\\'
#else
#define UFBX_PATH_SEPARATOR '/'
#endif
#endif
#if !defined(UFBX_STANDARD_C) && defined(_POSIX_C_SOURCE)
#if _POSIX_C_SOURCE >= 200112l
#ifndef UFBX_HAS_FTELLO
#define UFBX_HAS_FTELLO
#endif
#endif
#endif
#if !defined(UFBX_STANDARD_C) && (defined(_MSC_VER) && defined(_M_X64)) || ((defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)) || defined(UFBX_USE_SSE)
#define UFBXI_HAS_SSE 1
#include <xmmintrin.h>
#include <emmintrin.h>
#else
#define UFBXI_HAS_SSE 0
#endif
#if !defined(UFBX_LITTLE_ENDIAN)
#if !defined(UFBX_STANDARD_C) && (defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__))
#define UFBX_LITTLE_ENDIAN 1
#else
#define UFBX_LITTLE_ENDIAN 0
#endif
#endif
// Unaligned little-endian load functions
// On platforms that support unaligned access natively (x86, x64, ARM64) just use normal loads,
// with unaligned attributes, otherwise do manual byte-wise load.
#define ufbxi_read_u8(ptr) (*(const uint8_t*)(ptr))
// Detect support for `__attribute__((aligned(1)))`
#if !defined(UFBX_STANDARD_C) && (defined(__clang__) && defined(__APPLE__))
// Apple overrides Clang versioning, 5.0 here maps to 3.3
#if __clang_major__ >= 5
#define UFBXI_HAS_ATTRIBUTE_ALIGNED 1
#endif
#elif !defined(UFBX_STANDARD_C) && defined(__clang__)
#if (__clang_major__ >= 4) || (__clang_major__ == 3 && __clang_minor__ >= 3)
#define UFBXI_HAS_ATTRIBUTE_ALIGNED 1
#endif
#elif !defined(UFBX_STANDARD_C) && defined(__GNUC__)
#if __GNUC__ >= 5
#define UFBXI_HAS_ATTRIBUTE_ALIGNED 1
#endif
#endif
#if defined(UFBXI_HAS_ATTRIBUTE_ALIGNED)
#define UFBXI_HAS_UNALIGNED 1
#define ufbxi_unaligned
typedef uint16_t __attribute__((aligned(1))) ufbxi_unaligned_u16;
typedef uint32_t __attribute__((aligned(1))) ufbxi_unaligned_u32;
typedef uint64_t __attribute__((aligned(1))) ufbxi_unaligned_u64;
typedef float __attribute__((aligned(1))) ufbxi_unaligned_f32;
typedef double __attribute__((aligned(1))) ufbxi_unaligned_f64;
#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER)
#define UFBXI_HAS_UNALIGNED 1
#if defined(_M_IX86)
// MSVC seems to assume all pointers are unaligned for x86
#define ufbxi_unaligned
#else
#define ufbxi_unaligned __unaligned
#endif
typedef uint16_t ufbxi_unaligned_u16;
typedef uint32_t ufbxi_unaligned_u32;
typedef uint64_t ufbxi_unaligned_u64;
typedef float ufbxi_unaligned_f32;
typedef double ufbxi_unaligned_f64;
#endif
#if (defined(UFBXI_HAS_UNALIGNED) && UFBX_LITTLE_ENDIAN && !defined(UFBX_NO_UNALIGNED_LOADS)) || defined(UFBX_USE_UNALIGNED_LOADS)
#define ufbxi_read_u16(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u16*)(ptr))
#define ufbxi_read_u32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u32*)(ptr))
#define ufbxi_read_u64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u64*)(ptr))
#define ufbxi_read_f32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_f32*)(ptr))
#define ufbxi_read_f64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_f64*)(ptr))
#else
static ufbxi_forceinline uint16_t ufbxi_read_u16(const void *ptr) {
const char *p = (const char*)ptr;
return (uint16_t)(
(unsigned)(uint8_t)p[0] << 0u |
(unsigned)(uint8_t)p[1] << 8u );
}
static ufbxi_forceinline uint32_t ufbxi_read_u32(const void *ptr) {
const char *p = (const char*)ptr;
return (uint32_t)(
(unsigned)(uint8_t)p[0] << 0u |
(unsigned)(uint8_t)p[1] << 8u |
(unsigned)(uint8_t)p[2] << 16u |
(unsigned)(uint8_t)p[3] << 24u );
}
static ufbxi_forceinline uint64_t ufbxi_read_u64(const void *ptr) {
const char *p = (const char*)ptr;
return (uint64_t)(
(uint64_t)(uint8_t)p[0] << 0u |
(uint64_t)(uint8_t)p[1] << 8u |
(uint64_t)(uint8_t)p[2] << 16u |
(uint64_t)(uint8_t)p[3] << 24u |
(uint64_t)(uint8_t)p[4] << 32u |
(uint64_t)(uint8_t)p[5] << 40u |
(uint64_t)(uint8_t)p[6] << 48u |
(uint64_t)(uint8_t)p[7] << 56u );
}
static ufbxi_forceinline float ufbxi_read_f32(const void *ptr) {
uint32_t u = ufbxi_read_u32(ptr);
float f;
memcpy(&f, &u, 4);
return f;
}
static ufbxi_forceinline double ufbxi_read_f64(const void *ptr) {
uint64_t u = ufbxi_read_u64(ptr);
double f;
memcpy(&f, &u, 8);
return f;
}
#endif
#define ufbxi_read_i8(ptr) (int8_t)(ufbxi_read_u8(ptr))
#define ufbxi_read_i16(ptr) (int16_t)(ufbxi_read_u16(ptr))
#define ufbxi_read_i32(ptr) (int32_t)(ufbxi_read_u32(ptr))
#define ufbxi_read_i64(ptr) (int64_t)(ufbxi_read_u64(ptr))
ufbx_static_assert(sizeof_bool, sizeof(bool) == 1);
ufbx_static_assert(sizeof_char, sizeof(char) == 1);
ufbx_static_assert(sizeof_i8, sizeof(int8_t) == 1);
ufbx_static_assert(sizeof_i16, sizeof(int16_t) == 2);
ufbx_static_assert(sizeof_i32, sizeof(int32_t) == 4);
ufbx_static_assert(sizeof_i64, sizeof(int64_t) == 8);
ufbx_static_assert(sizeof_u8, sizeof(uint8_t) == 1);
ufbx_static_assert(sizeof_u16, sizeof(uint16_t) == 2);
ufbx_static_assert(sizeof_u32, sizeof(uint32_t) == 4);
ufbx_static_assert(sizeof_u64, sizeof(uint64_t) == 8);
ufbx_static_assert(sizeof_f32, sizeof(float) == 4);
ufbx_static_assert(sizeof_f64, sizeof(double) == 8);
// -- Version
#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 13, 0)
ufbx_abi_data_def const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION;
ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEADER_VERSION/1000u);
// -- Fast copy
#if UFBXI_HAS_SSE
#define ufbxi_copy_16_bytes(dst, src) _mm_storeu_si128((__m128i*)(dst), _mm_loadu_si128((const __m128i*)(src)))
#elif defined(UFBXI_HAS_UNALIGNED)
#define ufbxi_copy_16_bytes(dst, src) do { \
ufbxi_unaligned ufbxi_unaligned_u64 *mi_dst = (ufbxi_unaligned ufbxi_unaligned_u64 *)(dst); \
const ufbxi_unaligned ufbxi_unaligned_u64 *mi_src = (const ufbxi_unaligned ufbxi_unaligned_u64 *)src; \
mi_dst[0] = mi_src[0]; \
mi_dst[1] = mi_src[1]; \
} while (0)
#else
#define ufbxi_copy_16_bytes(dst, src) memcpy((dst), (src), 16)
#endif
// -- Large fast integer
#if !defined(UFBX_STANDARD_C) && (defined(__wasm__) || defined(__EMSCRIPTEN__)) && !defined(UFBX_WASM_32BIT)
typedef uint64_t ufbxi_fast_uint;
#else
typedef size_t ufbxi_fast_uint;
#endif
// -- Wrapping right shift
#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) && defined(_M_X64)
#define ufbxi_wrap_shr64(a, b) ((a) >> (b))
#else
#define ufbxi_wrap_shr64(a, b) ((a) >> ((b) & 63))
#endif
// -- Atomic counter
#define UFBXI_THREAD_SAFE 1
#if defined(__cplusplus)
#define ufbxi_extern_c extern "C"
#else
#define ufbxi_extern_c
#endif
#if !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER))
typedef size_t ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_inc(ptr) __sync_fetch_and_add((ptr), 1)
#define ufbxi_atomic_counter_dec(ptr) __sync_fetch_and_sub((ptr), 1)
#define ufbxi_atomic_counter_load(ptr) __sync_fetch_and_add((ptr), 0) // TODO: Proper atomic load
#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER)
#if defined(_M_X64) || defined(_M_ARM64)
ufbxi_extern_c __int64 _InterlockedIncrement64(__int64 volatile * lpAddend);
ufbxi_extern_c __int64 _InterlockedDecrement64(__int64 volatile * lpAddend);
ufbxi_extern_c __int64 _InterlockedExchangeAdd64(__int64 volatile * lpAddend, __int64 Value);
typedef volatile __int64 ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement64(ptr) - 1)
#define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement64(ptr) + 1)
#define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd64((ptr), 0))
#else
ufbxi_extern_c long __cdecl _InterlockedIncrement(long volatile * lpAddend);
ufbxi_extern_c long __cdecl _InterlockedDecrement(long volatile * lpAddend);
ufbxi_extern_c long __cdecl _InterlockedExchangeAdd(long volatile * lpAddend, long Value);
typedef volatile long ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_inc(ptr) ((size_t)_InterlockedIncrement(ptr) - 1)
#define ufbxi_atomic_counter_dec(ptr) ((size_t)_InterlockedDecrement(ptr) + 1)
#define ufbxi_atomic_counter_load(ptr) ((size_t)_InterlockedExchangeAdd((ptr), 0))
#endif
#elif !defined(UFBX_STANDARD_C) && defined(__TINYC__)
#if defined(__x86_64__) || defined(_AMD64_)
static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) {
__asm__ __volatile__("lock; xaddq %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst));
return value;
}
#elif defined(__i386__) || defined(_X86_)
static size_t ufbxi_tcc_atomic_add(volatile size_t *dst, size_t value) {
__asm__ __volatile__("lock; xaddl %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst));
return value;
}
#else
#error Unexpected TCC architecture
#endif
typedef volatile size_t ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_inc(ptr) ufbxi_tcc_atomic_add((ptr), 1)
#define ufbxi_atomic_counter_dec(ptr) ufbxi_tcc_atomic_add((ptr), SIZE_MAX)
#define ufbxi_atomic_counter_load(ptr) ufbxi_tcc_atomic_add((ptr), 0)
#elif defined(__cplusplus) && (__cplusplus >= 201103L)
#include <new>
#include <atomic>
typedef struct { alignas(std::atomic_size_t) char data[sizeof(std::atomic_size_t)]; } ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) (new (&(ptr)->data) std::atomic_size_t(0))
#define ufbxi_atomic_counter_free(ptr) (((std::atomic_size_t*)(ptr)->data)->~atomic())
#define ufbxi_atomic_counter_inc(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_add(1)
#define ufbxi_atomic_counter_dec(ptr) ((std::atomic_size_t*)(ptr)->data)->fetch_sub(1)
#define ufbxi_atomic_counter_load(ptr) ((std::atomic_size_t*)(ptr)->data)->load(std::memory_order_acquire)
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
typedef volatile atomic_size_t ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) atomic_init(ptr, 0)
#define ufbxi_atomic_counter_free(ptr) (void)(ptr)
#define ufbxi_atomic_counter_inc(ptr) atomic_fetch_add((ptr), 1)
#define ufbxi_atomic_counter_dec(ptr) atomic_fetch_sub((ptr), 1)
#define ufbxi_atomic_counter_load(ptr) atomic_load_explicit((ptr), memory_order_acquire)
#else
typedef volatile size_t ufbxi_atomic_counter;
#define ufbxi_atomic_counter_init(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_free(ptr) (*(ptr) = 0)
#define ufbxi_atomic_counter_inc(ptr) ((*(ptr))++)
#define ufbxi_atomic_counter_dec(ptr) ((*(ptr))--)
#define ufbxi_atomic_counter_load(ptr) (*(ptr))
#undef UFBXI_THREAD_SAFE
#define UFBXI_THREAD_SAFE 0
#endif
// -- Bit manipulation
#if !defined(UFBX_STANDARD_C) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
ufbxi_extern_c unsigned char _BitScanReverse(unsigned long * _Index, unsigned long _Mask);
ufbxi_extern_c unsigned char _BitScanReverse64(unsigned long * _Index, unsigned __int64 _Mask);
static ufbxi_forceinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) {
unsigned long index;
#if defined(_M_X64)
_BitScanReverse64(&index, (unsigned __int64)v);
#else
uint32_t hi = (uint32_t)(v >> 32u);
uint32_t hi_nonzero = hi != 0 ? 1 : 0;
uint32_t part = hi_nonzero ? hi : (uint32_t)v;
_BitScanReverse(&index, (unsigned long)part);
index += hi_nonzero * 32u;
#endif
return 63 - (uint32_t)index;
}
#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__))
#define ufbxi_lzcnt64(v) ((uint32_t)__builtin_clzll((unsigned long long)(v)))
#else
// DeBrujin table lookup
static const uint8_t ufbxi_lzcnt_table[] = {
63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, 9, 5, 28, 11, 13, 21, 42,
19, 25, 31, 34, 40, 46, 52, 59, 1, 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41,
53, 18, 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0,
};
static ufbxi_noinline ufbxi_unused uint32_t ufbxi_lzcnt64(uint64_t v) {
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v |= v >> 32;
return ufbxi_lzcnt_table[(v * UINT64_C(0x03f79d71b4cb0a89)) >> 58];
}
#endif
// -- Debug
#if defined(UFBX_DEBUG_BINARY_SEARCH) || defined(UFBX_REGRESSION)
#define ufbxi_clamp_linear_threshold(v) (2)
#else
#define ufbxi_clamp_linear_threshold(v) (v)
#endif
#if defined(UFBX_REGRESSION)
#undef UFBXI_MAX_SKIP_SIZE
#define UFBXI_MAX_SKIP_SIZE 128
#undef UFBXI_MAP_MAX_SCAN
#define UFBXI_MAP_MAX_SCAN 2
#undef UFBXI_KD_FAST_DEPTH
#define UFBXI_KD_FAST_DEPTH 2
#undef UFBXI_FACE_GROUP_HASH_BITS
#define UFBXI_FACE_GROUP_HASH_BITS 2
#endif
#if defined(UFBX_REGRESSION) || defined(UFBX_EXTENSIVE_THREADING)
#undef UFBXI_MIN_THREADED_DEFLATE_BYTES
#define UFBXI_MIN_THREADED_DEFLATE_BYTES 2
#undef UFBXI_MIN_THREADED_ASCII_VALUES
#define UFBXI_MIN_THREADED_ASCII_VALUES 2
#endif
#if defined(UFBX_REGRESSION)
#define ufbxi_regression_assert(cond) ufbx_assert(cond)
#else
#define ufbxi_regression_assert(cond) (void)0
#endif
#if defined(UFBX_REGRESSION) || defined(UFBX_DEV)
#define ufbxi_dev_assert(cond) ufbx_assert(cond)
#else
#define ufbxi_dev_assert(cond) (void)0
#endif
#define ufbxi_unreachable(reason) do { ufbx_assert(0 && reason); } while (0)
#if defined(UFBX_REGRESSION)
#define UFBXI_IS_REGRESSION 1
#else
#define UFBXI_IS_REGRESSION 0
#endif
#if defined(_MSC_VER)
#define ufbxi_thread_local __declspec(thread)
#elif defined(__GNUC__) || defined(__clang__)
#define ufbxi_thread_local __thread
#elif UFBXI_HAS_CPP11
#define ufbxi_thread_local thread_local
#elif UFBX_STDC >= 201112L
#define ufbxi_thread_local _Thread_local
#endif
#if defined(UFBXI_ANALYSIS_RECURSIVE)
#define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) UFBXI_RECURSIVE_FUNCTION(m_name, m_max_depth);
#define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) UFBXI_RECURSIVE_FUNCTION(m_name, m_max_depth);
#elif UFBXI_IS_REGRESSION && defined(ufbxi_thread_local)
#define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params) \
{ \
m_ret m_name##_rec m_params; \
static ufbxi_thread_local unsigned ufbxi_recursion_depth; \
ufbx_assert(ufbxi_recursion_depth < m_max_depth); \
++ufbxi_recursion_depth; \
m_ret ret = m_name##_rec m_args; \
--ufbxi_recursion_depth; \
return ret; \
} \
m_ret m_name##_rec m_params
#define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params) \
{ \
void m_name##_rec m_params; \
static ufbxi_thread_local unsigned ufbxi_recursion_depth; \
ufbx_assert(ufbxi_recursion_depth < m_max_depth); \
++ufbxi_recursion_depth; \
m_name##_rec m_args; \
--ufbxi_recursion_depth; \
} \
void m_name##_rec m_params
#else
#define ufbxi_recursive_function(m_ret, m_name, m_args, m_max_depth, m_params)
#define ufbxi_recursive_function_void(m_name, m_args, m_max_depth, m_params)
#endif
// -- Utility
#if defined(UFBX_UBSAN)
static void ufbxi_assert_zero(size_t offset) { ufbx_assert(offset == 0); }
#define ufbxi_add_ptr(ptr, offset) ((ptr) ? (ptr) + (offset) : (ufbxi_assert_zero((size_t)(offset)), (ptr)))
#define ufbxi_sub_ptr(ptr, offset) ((ptr) ? (ptr) - (offset) : (ufbxi_assert_zero((size_t)(offset)), (ptr)))
#else
#define ufbxi_add_ptr(ptr, offset) ((ptr) + (offset))
#define ufbxi_sub_ptr(ptr, offset) ((ptr) - (offset))
#endif
#define ufbxi_arraycount(arr) (sizeof(arr) / sizeof(*(arr)))
#define ufbxi_for(m_type, m_name, m_begin, m_num) for (m_type *m_name = m_begin, *m_name##_end = ufbxi_add_ptr(m_name, m_num); m_name != m_name##_end; m_name++)
#define ufbxi_for_ptr(m_type, m_name, m_begin, m_num) for (m_type **m_name = m_begin, **m_name##_end = ufbxi_add_ptr(m_name, m_num); m_name != m_name##_end; m_name++)
// WARNING: Evaluates `m_list` twice!
#define ufbxi_for_list(m_type, m_name, m_list) for (m_type *m_name = (m_list).data, *m_name##_end = ufbxi_add_ptr(m_name, (m_list).count); m_name != m_name##_end; m_name++)
#define ufbxi_for_ptr_list(m_type, m_name, m_list) for (m_type **m_name = (m_list).data, **m_name##_end = ufbxi_add_ptr(m_name, (m_list).count); m_name != m_name##_end; m_name++)
#define ufbxi_string_literal(str) { str, sizeof(str) - 1 }
static ufbxi_forceinline uint32_t ufbxi_min32(uint32_t a, uint32_t b) { return a < b ? a : b; }
static ufbxi_forceinline uint32_t ufbxi_max32(uint32_t a, uint32_t b) { return a < b ? b : a; }
static ufbxi_forceinline uint64_t ufbxi_min64(uint64_t a, uint64_t b) { return a < b ? a : b; }
static ufbxi_forceinline uint64_t ufbxi_max64(uint64_t a, uint64_t b) { return a < b ? b : a; }
static ufbxi_forceinline size_t ufbxi_min_sz(size_t a, size_t b) { return a < b ? a : b; }
static ufbxi_forceinline size_t ufbxi_max_sz(size_t a, size_t b) { return a < b ? b : a; }
static ufbxi_forceinline ufbx_real ufbxi_min_real(ufbx_real a, ufbx_real b) { return a < b ? a : b; }
static ufbxi_forceinline ufbx_real ufbxi_max_real(ufbx_real a, ufbx_real b) { return a < b ? b : a; }
static ufbxi_forceinline int32_t ufbxi_f64_to_i32(double value)
{
if (ufbx_fabs(value) <= (double)INT32_MAX) {
return (int32_t)value;
} else {
return value >= 0.0 ? INT32_MAX : INT32_MIN;
}
}
static ufbxi_forceinline int64_t ufbxi_f64_to_i64(double value)
{
if (ufbx_fabs(value) <= (double)INT64_MAX) {
return (int64_t)value;
} else {
return value >= 0.0 ? INT64_MAX : INT64_MIN;
}
}
#if defined(UFBX_REGRESSION)
static size_t ufbxi_to_size(ptrdiff_t delta) {
ufbx_assert(delta >= 0);
return (size_t)delta;
}
#else
#define ufbxi_to_size(delta) ((size_t)(delta))
#endif
// Stable sort array `m_type m_data[m_size]` using the predicate `m_cmp_lambda(a, b)`
// `m_linear_size` is a hint for how large blocks handle initially do with insertion sort
// `m_tmp` must be a memory buffer with at least the same size and alignment as `m_data`
#define ufbxi_macro_stable_sort(m_type, m_linear_size, m_data, m_tmp, m_size, m_cmp_lambda) do { \
typedef m_type mi_type; \
mi_type *mi_src = (mi_type*)(m_tmp); \
mi_type *mi_data = m_data, *mi_dst = mi_data; \
size_t mi_block_size = ufbxi_clamp_linear_threshold(m_linear_size), mi_size = m_size; \
/* Insertion sort in `m_linear_size` blocks */ \
for (size_t mi_base = 0; mi_base < mi_size; mi_base += mi_block_size) { \
size_t mi_i_end = mi_base + mi_block_size; \
if (mi_i_end > mi_size) mi_i_end = mi_size; \
for (size_t mi_i = mi_base + 1; mi_i < mi_i_end; mi_i++) { \
size_t mi_j = mi_i; \
mi_src[0] = mi_dst[mi_i]; \
for (; mi_j != mi_base; --mi_j) { \
mi_type *a = &mi_src[0], *b = &mi_dst[mi_j - 1]; \
if (!( m_cmp_lambda )) break; \
mi_dst[mi_j] = mi_dst[mi_j - 1]; \
} \
mi_dst[mi_j] = mi_src[0]; \
} \
} \
/* Merge sort ping-ponging between `m_data` and `m_tmp` */ \
for (; mi_block_size < mi_size; mi_block_size *= 2) { \
mi_type *mi_swap = mi_dst; mi_dst = mi_src; mi_src = mi_swap; \
for (size_t mi_base = 0; mi_base < mi_size; mi_base += mi_block_size * 2) { \
size_t mi_i = mi_base, mi_i_end = mi_base + mi_block_size; \
size_t mi_j = mi_i_end, mi_j_end = mi_j + mi_block_size; \
size_t mi_k = mi_base; \
if (mi_i_end > mi_size) mi_i_end = mi_size; \
if (mi_j_end > mi_size) mi_j_end = mi_size; \
while ((mi_i < mi_i_end) & (mi_j < mi_j_end)) { \
mi_type *a = &mi_src[mi_j], *b = &mi_src[mi_i]; \
if ( m_cmp_lambda ) { \
mi_dst[mi_k] = *a; mi_j++; \
} else { \
mi_dst[mi_k] = *b; mi_i++; \
} \
mi_k++; \
} \
while (mi_i < mi_i_end) mi_dst[mi_k++] = mi_src[mi_i++]; \
while (mi_j < mi_j_end) mi_dst[mi_k++] = mi_src[mi_j++]; \
} \
} \
/* Copy the result to `m_data` if we ended up in `m_tmp` */ \
if (mi_dst != mi_data) memcpy((void*)mi_data, mi_dst, sizeof(mi_type) * mi_size); \
} while (0)
#define ufbxi_macro_lower_bound_eq(m_type, m_linear_size, m_result_ptr, m_data, m_begin, m_size, m_cmp_lambda, m_eq_lambda) do { \
typedef m_type mi_type; \
const mi_type *mi_data = (m_data); \
size_t mi_lo = m_begin, mi_hi = m_size, mi_linear_size = ufbxi_clamp_linear_threshold(m_linear_size); \
ufbx_assert(mi_linear_size > 1); \
/* Binary search until we get down to `m_linear_size` elements */ \
while (mi_hi - mi_lo > mi_linear_size) { \
size_t mi_mid = mi_lo + (mi_hi - mi_lo) / 2; \
const mi_type *a = &mi_data[mi_mid]; \
if ( m_cmp_lambda ) { mi_lo = mi_mid + 1; } else { mi_hi = mi_mid + 1; } \
} \
/* Linearly scan until we find the edge */ \
for (; mi_lo < mi_hi; mi_lo++) { \
const mi_type *a = &mi_data[mi_lo]; \
if ( m_eq_lambda ) { *(m_result_ptr) = mi_lo; break; } \
} \
} while (0)
#define ufbxi_macro_upper_bound_eq(m_type, m_linear_size, m_result_ptr, m_data, m_begin, m_size, m_eq_lambda) do { \
typedef m_type mi_type; \
const mi_type *mi_data = (m_data); \
size_t mi_lo = m_begin, mi_hi = m_size, mi_linear_size = ufbxi_clamp_linear_threshold(m_linear_size); \
ufbx_assert(mi_linear_size > 1); \
/* Linearly scan with galloping */ \
for (size_t mi_step = 1; mi_step < 100 && mi_hi - mi_lo > mi_step; mi_step *= 2) { \
const mi_type *a = &mi_data[mi_lo + mi_step]; \
if (!( m_eq_lambda )) { mi_hi = mi_lo + mi_step; break; } \
mi_lo += mi_step; \
} \
/* Binary search until we get down to `m_linear_size` elements */ \
while (mi_hi - mi_lo > mi_linear_size) { \
size_t mi_mid = mi_lo + (mi_hi - mi_lo) / 2; \
const mi_type *a = &mi_data[mi_mid]; \
if ( m_eq_lambda ) { mi_lo = mi_mid + 1; } else { mi_hi = mi_mid + 1; } \
} \
/* Linearly scan until we find the edge */ \
for (; mi_lo < mi_hi; mi_lo++) { \
const mi_type *a = &mi_data[mi_lo]; \
if (!( m_eq_lambda )) break; \
} \
*(m_result_ptr) = mi_lo; \
} while (0)
typedef bool ufbxi_less_fn(void *user, const void *a, const void *b);
static ufbxi_noinline void ufbxi_stable_sort(size_t stride, size_t linear_size, void *in_data, void *in_tmp, size_t size, ufbxi_less_fn *less_fn, void *less_user)
{
(void)linear_size;
char *src = (char*)in_tmp;
char *data = (char*)in_data, *dst = (char*)data;
size_t block_size = ufbxi_clamp_linear_threshold(linear_size);
/* Insertion sort in `linear_size` blocks */
for (size_t base = 0; base < size; base += block_size) {
size_t i_end = base + block_size;
if (i_end > size) i_end = size;
for (size_t i = base + 1; i < i_end; i++) {
{
char *a = dst + i * stride, *b = dst + (i - 1) * stride;
if (!less_fn(less_user, a, b)) continue;
}
size_t j = i - 1;
memcpy(src, dst + i * stride, stride);
memcpy(dst + i * stride, dst + j * stride, stride);
for (; j != base; --j) {
char *a = src, *b = dst + (j - 1) * stride;
if (!less_fn(less_user, a, b)) break;
memcpy(dst + j * stride, dst + (j - 1) * stride, stride);
}
memcpy(dst + j * stride, src, stride);
}
}
/* Merge sort ping-ponging between `data` and `tmp` */
for (; block_size < size; block_size *= 2) {
char *swap = dst; dst = src; src = swap;
for (size_t base = 0; base < size; base += block_size * 2) {
size_t i = base, i_end = base + block_size;
size_t j = i_end, j_end = j + block_size;
size_t k = base;
if (i_end > size) i_end = size;
if (j_end > size) j_end = size;
while ((i < i_end) & (j < j_end)) {
char *a = src + j * stride, *b = src + i * stride;
if (less_fn(less_user, a, b)) {
memcpy(dst + k * stride, a, stride);
j++;
} else {
memcpy(dst + k * stride, b, stride);
i++;
}
k++;
}
memcpy(dst + k * stride, src + i * stride, (i_end - i) * stride);
if (j < j_end) {
memcpy(dst + (k + (i_end - i)) * stride, src + j * stride, (j_end - j) * stride);
}
}
}
/* Copy the result to `data` if we ended up in `tmp` */
if (dst != data) memcpy((void*)data, dst, size * stride);
}
static ufbxi_forceinline void ufbxi_swap(void *a, void *b, size_t size)
{
char *ca = (char*)a, *cb = (char*)b;
#if defined(UFBXI_HAS_UNALIGNED)
ufbxi_nounroll while (size >= 4) {
uint32_t t = *(ufbxi_unaligned ufbxi_unaligned_u32*)ca;
*(ufbxi_unaligned ufbxi_unaligned_u32*)ca = *(ufbxi_unaligned ufbxi_unaligned_u32*)cb;
*(ufbxi_unaligned ufbxi_unaligned_u32*)cb = t;
ca += 4; cb += 4; size -= 4;
}
#endif
ufbxi_nounroll while (size > 0) {
char t = *ca; *ca = *cb; *cb = t;
ca++; cb++; size--;
}
}
static ufbxi_noinline void ufbxi_unstable_sort(void *in_data, size_t size, size_t stride, ufbxi_less_fn *less_fn, void *less_user)
{
if (size <= 1) return;
char *data = (char*)in_data;
size_t start = (size - 1) >> 1;
size_t end = size - 1;
for (;;) {
size_t root = start;
size_t child;
while ((child = root*2 + 1) <= end) {
size_t next = less_fn(less_user, data + child * stride, data + root * stride) ? root : child;
if (child + 1 <= end && less_fn(less_user, data + next * stride, data + (child + 1) * stride)) {
next = child + 1;
}
if (next == root) break;
ufbxi_swap(data + root * stride, data + next * stride, stride);
root = next;
}
if (start > 0) {
start--;
} else if (end > 0) {
ufbxi_swap(data + end * stride, data, stride);
end--;
} else {
break;
}
}
}
// -- Float parsing
//
// Custom float parsing that handles floats up to (-)ddddddddddddddddddd.ddddddddddddddddddd
// If larger or scientific notation is used then it defers to `strtod()`.
// For the algorithm we need 128-bit division that is either provided by hardware on x64 or
// a custom implementation below.
#if !defined(UFBX_STANDARD_C) && UFBXI_MSC_VER >= 1920 && defined(_M_X64) && !defined(__clang__)
ufbxi_extern_c extern unsigned __int64 __cdecl _udiv128(unsigned __int64 highdividend,
unsigned __int64 lowdividend, unsigned __int64 divisor, unsigned __int64 *remainder);
#define ufbxi_div128(a_hi, a_lo, b, p_rem) (_udiv128((a_hi), (a_lo), (b), (p_rem)))
#elif !defined(UFBX_STANDARD_C) && (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(_M_X64))
static ufbxi_forceinline uint64_t ufbxi_div128(uint64_t a_hi, uint64_t a_lo, uint64_t b, uint64_t *p_rem) {
uint64_t quot, rem;
__asm__("divq %[v]" : "=a"(quot), "=d"(rem) : [v] "r"(b), "a"(a_lo), "d"(a_hi));
*p_rem = rem;
return quot;
}
#else
static ufbxi_forceinline uint64_t ufbxi_div128(uint64_t a_hi, uint64_t a_lo, uint64_t b, uint64_t *p_rem) {
// Divide `(a_hi << 64 | a_lo)` by `b`, returns quotinent and stores reminder in `p_rem`.
// Based on TAOCP 2.4 multi-word division single algorithm digit step.
//
// Notation:
// b is the base (2^32) in this case
// aN is the Nth digit (base b) of a from the least significant digit
// { x y z } is a multi-digit number b^2*x + b*y + z
// ie. for a 64-bit number a = { a1 a0 } = b*a1 + a0
//
// We do the division in two steps by dividing three digits in each iteration:
//
// q1, r = { a3 a2 a1 } / { b1 b0 }
// q0, r = { r1 r0 a0 } / { b1 b0 }
//
// In each step we want to compute the expression:
//
// q, r = { u2 u1 u0 } / { v1 v0 }
//
// However we cannot rely on being able to do `u96 / u64` division we estimate
// the result by considering only the leading digits:
//
// q^ = { u2 u1 } / v1 [A]
// r^ = { u2 u1 } % v1 = { u2 u1 } - v1 * q^ [B]
//
// As long as `v1 >= b/2` the estimate `q^` is at most two larger than the actual `q`
// (proof in TAOCP 2.4) so we can compute the correction amount `c`:
//
// q <= q^ <= q + 2
// q = q^ - c [C]
//
// We can compute the final remainder (that must be non-negative) as follows:
//
// r = { u2 u1 u0 } - v*q
// r = { u2 u1 u0 } - v*(q^ - c)
// r = { u2 u1 u0 } - v*q^ + v*c
// r = { u2 u1 u0 } - { v1 v0 } * q^ + v*c
// r = b^2*u2 + b*u1 + u0 - b*v1*q^ - v0*q^ + v*c
// r = b*(b*u2 + u1 - v1*q^) + u0 - v0*q^ + v*c
// r = b*({ u2 u1 } - v1*q^) + u0 - v0*q^ + v*c
// r = b*r^ + u0 - v0*q^ + v*c
// r = { r^ u0 } - v0*q^ + v*c [D]
//
// As we know `0 <= c <= 2` we can first check if `r < 0` requiring `c >= 1`:
//
// { r^ u0 } - v0*q^ < 0
// { r^ u0 } < v0*q^ [E]
//
// If we know that `r < 0` we can check if `r < -v` requiring `c = 2`:
//
// { r^ u0 } - v0*q^ < -v
// v0*q^ - { r^ u0 } > v [F]
//
// First we need to make sure `v1 >= b/2`, we can do this by multiplying the whole
// expression by `2^shift` so that the high bit of `v` is set.
uint32_t shift = ufbxi_lzcnt64(b);
a_hi = (a_hi << shift) | (shift ? a_lo >> (64 - shift) : 0);
a_lo <<= shift;
b <<= shift;
uint64_t v = b;
uint32_t v1 = (uint32_t)(v >> 32);
uint32_t v0 = (uint32_t)(v);
uint64_t q1, q0, r;
// q1, r = { a3 a2 a1 } / { b1 b0 }
{
uint64_t u2_u1 = a_hi;
uint32_t u0 = (uint32_t)(a_lo >> 32u);
uint64_t qh = u2_u1 / v1; // q^ = { u2 u1 } / v1 [A]
uint64_t rh = u2_u1 % v1; // r^ = { u2 u1 } % v1 [B]
uint64_t rh_u0 = rh << 32u | u0; // { r^ u0 }
uint64_t v0qh = v0 * qh; // v0*q^
uint32_t c = rh_u0 < v0qh ? 1 : 0; // { r^ u0 } < v0*q^ [E]
c += c & (v0qh - rh_u0 > v ? 1 : 0); // v0*q^ - { r^ u0 } > v [F]
q1 = qh - c; // q1 = q^ - c [C]
r = rh_u0 - v0qh + v*c; // r = { r^ u0 } - v0*q^ + v*c [D]
}
// q0, r = { r1 r0 a0 } / { b1 b0 }
{
uint64_t u2_u1 = r;
uint32_t u0 = (uint32_t)a_lo;
uint64_t qh = u2_u1 / v1; // q^ = { u2 u1 } / v1 [A]
uint64_t rh = u2_u1 % v1; // r^ = { u2 u1 } % v1 [B]
uint64_t rh_u0 = rh << 32u | u0; // { r^ u0 }
uint64_t v0qh = v0 * qh; // v0*q^
uint32_t c = rh_u0 < v0qh ? 1 : 0; // { r^ u0 } < v0*q^ [E]
c += c & (v0qh - rh_u0 > v ? 1 : 0); // v0*q^ - { r^ u0 } > v [F]
q0 = qh - c; // q0 = q^ - c [C]
r = rh_u0 - v0qh + v*c; // r = { r^ u0 } - v0*q^ + v*c [D]
}
// Un-normalize the remainder and return the quotinent
*p_rem = r >> shift;
return q1 << 32u | q0;
}
#endif
typedef enum {
UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH = 0x1,
UFBXI_PARSE_DOUBLE_VERIFY_LENGTH = 0x2,
} ufbxi_parse_double_flag;
static const uint64_t ufbxi_pow5_tab[] = {
UINT64_C(0x8000000000000000), // 5^0 * 2^63
UINT64_C(0xa000000000000000), // 5^1 * 2^61
UINT64_C(0xc800000000000000), // 5^2 * 2^59
UINT64_C(0xfa00000000000000), // 5^3 * 2^57
UINT64_C(0x9c40000000000000), // 5^4 * 2^54
UINT64_C(0xc350000000000000), // 5^5 * 2^52
UINT64_C(0xf424000000000000), // 5^6 * 2^50
UINT64_C(0x9896800000000000), // 5^7 * 2^47
UINT64_C(0xbebc200000000000), // 5^8 * 2^45
UINT64_C(0xee6b280000000000), // 5^9 * 2^43
UINT64_C(0x9502f90000000000), // 5^10 * 2^40
UINT64_C(0xba43b74000000000), // 5^11 * 2^38
UINT64_C(0xe8d4a51000000000), // 5^12 * 2^36
UINT64_C(0x9184e72a00000000), // 5^13 * 2^33
UINT64_C(0xb5e620f480000000), // 5^14 * 2^31
UINT64_C(0xe35fa931a0000000), // 5^15 * 2^29
UINT64_C(0x8e1bc9bf04000000), // 5^16 * 2^26
UINT64_C(0xb1a2bc2ec5000000), // 5^17 * 2^24
UINT64_C(0xde0b6b3a76400000), // 5^18 * 2^22
UINT64_C(0x8ac7230489e80000), // 5^19 * 2^19
UINT64_C(0xad78ebc5ac620000), // 5^20 * 2^17
UINT64_C(0xd8d726b7177a8000), // 5^21 * 2^15
UINT64_C(0x878678326eac9000), // 5^22 * 2^12
UINT64_C(0xa968163f0a57b400), // 5^23 * 2^10
UINT64_C(0xd3c21bcecceda100), // 5^24 * 2^8
UINT64_C(0x84595161401484a0), // 5^25 * 2^5
UINT64_C(0xa56fa5b99019a5c8), // 5^26 * 2^3
UINT64_C(0xcecb8f27f4200f3a), // 5^27 * 2^1
};
static const int8_t ufbxi_pow2_tab[] = {
62, 59, 56, 53, 49, 46, 43, 39, 36, 33, 29, 26, 23, 19, 16, 13, 9, 6, 3, -1, -4, -7, -11, -14, -17, -21, -24, -27,
};
static const double ufbxi_pow10_tab_f64[] = {
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
};
static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags()
{
// We require evaluation in double precision, either for doubles (0) or always (1)
// and rounding to nearest, which we can check for with `1 + eps == 1 - eps`.
#if defined(FLT_EVAL_METHOD)
#if FLT_EVAL_METHOD == 0 || FLT_EVAL_METHOD == 1
static volatile double ufbxi_volatile_eps = 2.2250738585072014e-308;
if (1.0 + ufbxi_volatile_eps == 1.0 - ufbxi_volatile_eps) return UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH;
#endif
#endif
return 0;
}
static ufbxi_noinline double ufbxi_parse_double_slow(const char *str, char **end)
{
// TODO: Locales
return strtod(str, end);
}
static ufbxi_noinline double ufbxi_parse_double(const char *str, size_t max_length, char **end, uint32_t flags)
{
// TODO: Use this for optimizing digit parsing
(void)max_length;
uint64_t integer = 0;
uint32_t n_integer = 0;
int32_t n_decimals = 0;
uint32_t n_exp = 0;
bool negative = false;
// Parse /[+-]?[0-9]*(\.[0-9]*)([eE][+-]?[0-9]*)?/ retaining all digits
// in `integer` and number of decimals in `n_decimals`, exponent simply
// modifies `n_decimals` accordingly.
const char *p = str;
if (*p == '-') {
negative = true;
p++;
} else if (*p == '+') {
p++;
}
while (((uint32_t)*p - '0') < 10) {
integer = integer * 10 + (uint64_t)(*p++ - '0');
n_integer++;
}
if (*p == '.') {
p++;
while (((uint32_t)*p - '0') < 10) {
integer = integer * 10 + (uint64_t)(*p++ - '0');
n_integer++;
n_decimals++;
}
}
if ((*p | 0x20) == 'e') {
p++;
int32_t exp = 0;
int32_t exp_sign = -1;
if (*p == '-') {
p++;
exp_sign = 1;
} else if (*p == '+') {
p++;
}
while (((uint32_t)*p - '0') < 10) {
exp = exp * 10 + (int32_t)(*p++ - '0');
n_exp++;
}
n_decimals += exp * exp_sign;
}
*end = (char*)p;
// Check that the number is not potentially truncated.
if (ufbxi_to_size(p - str) >= max_length && (flags & UFBXI_PARSE_DOUBLE_VERIFY_LENGTH) != 0) {
*end = NULL;
return 0.0;
}
// Overflowed either 64-bit `integer` or 31-bit `exp`.
if (n_integer > 19 || n_exp > 9 || (integer >> 63) != 0) {
return ufbxi_parse_double_slow(str, end);
}
// Both power of 10 and integer are exactly representable as doubles
// Powers of 10 are factored as 2*5, and 2^N can be always exactly represented.
if ((flags & UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH) != 0 && n_decimals >= -22 && n_decimals <= 22 && (integer >> 53) == 0) {
double value;
if (n_decimals > 0) {
value = (double)integer / ufbxi_pow10_tab_f64[n_decimals];
} else {
value = (double)integer * ufbxi_pow10_tab_f64[-n_decimals];
}
return negative ? -value : value;
}
// Cannot handle positive exponents here, fortunately the fast case should
// take care of most of them, for negative exponents we can only handle
// up to e-27 as `5^28 > 2^64` and cannot be used as a divisor below.
if (n_decimals < 0) {
return ufbxi_parse_double_slow(str, end);
} else if (!n_decimals || !integer) {
double value = (double)integer;
return negative ? -value : value;
} else if (n_decimals > 27) {
return ufbxi_parse_double_slow(str, end);
}
// We want to compute `integer / 10^N` precisely, we can do this
// using 128-bit division `2^64 * dividend / divisor`:
// dividend = integer * 2^S (S set such that highest bit is 62)
// divisor = 10^N * 2^T (T set such that highest bit is 63)
// We have to compensate for the shifts in the exponent:
// (2^64 * integer * 2^S) / (10^N * 2^T) * 2^(-1 - S + T)
// To get larger exponent range split 10^N to 5^N * 2^N and move 2^N to the exponent
// (2^64 * integer * 2^S) / (5^N * 2^T) * 2^(-1 - S + T - N)
uint32_t shift = ufbxi_lzcnt64(integer) - 1;
uint64_t dividend = integer << shift;
uint64_t divisor = ufbxi_pow5_tab[n_decimals];
int32_t exponent = (int32_t)ufbxi_pow2_tab[n_decimals] - (int32_t)shift; // (-1 + T - N) - S
uint64_t rem_hi;
uint64_t b_hi = ufbxi_div128(dividend, 0, divisor, &rem_hi);
// Align the mantissa so that high bit is set, due to the shifting of the
// divisor and dividend the smallest result is `2^62 + N`, so we need to
// shift at most by one bit.
uint64_t b_bit = 1 - (b_hi >> 63u);
uint64_t mantissa = b_hi << b_bit;
exponent -= (int32_t)b_bit;
// Round to 53 bits, accounting for potential remainder.
bool nonzero_tail = rem_hi != 0;
bool r_odd = mantissa & (1 << 11u);
bool r_round = mantissa & (1 << 10u);
bool r_tail = (mantissa & ((1 << 10u) - 1)) != 0 || nonzero_tail;
uint64_t round = (r_round && (r_odd || r_tail)) ? 1u : 0u;
// Assemble the IEEE 754 binary64 number.
uint64_t bits
= (uint64_t)negative << 63u
| (uint64_t)(exponent + 1023) << 52u
| ((mantissa >> 11u) & ~(UINT64_C(1) << 52u));
bits += round;
// Type punning via unions is safe in C but in C++ the only safe way
// (pre std::bit_cast) is to use `memcpy()` and hope it gets optimized out.
#if defined(__cplusplus)
double result;
memcpy(&result, &bits, 8);
return result;
#else
union { uint64_t u; double d; } u_to_d;
u_to_d.u = bits;
return u_to_d.d;
#endif
}
static ufbxi_forceinline int64_t ufbxi_parse_int64(const char *str, char **end)
{
uint64_t abs_val = 0;
bool negative = *str == '-';
bool positive = *str == '+';
size_t init_len = (negative | positive) ? 1 : 0;
size_t len = init_len;
for (; len < 30; len++) {
char c = str[len];
if (!(c >= '0' && c <= '9')) break;
abs_val = 10 * abs_val + (uint64_t)(c - '0');
}
if (len == 30 || len == init_len) {
*end = NULL;
return 0;
}
// TODO: Wrap/clamp?
*end = (char*)str + len;
return negative ? -(int64_t)abs_val : (int64_t)abs_val;
}
// -- DEFLATE implementation
#if !defined(ufbx_inflate)
// Lookup data: [0:5] extra bits [5:8] flags [16:32] base value
// Generated by `misc/deflate_lut.py`
static const uint32_t ufbxi_deflate_length_lut[] = {
0x00000020, 0x00030040, 0x00040040, 0x00050040, 0x00060040, 0x00070040, 0x00080040, 0x00090040,
0x000a0040, 0x000b0041, 0x000d0041, 0x000f0041, 0x00110041, 0x00130042, 0x00170042, 0x001b0042,
0x001f0042, 0x00230043, 0x002b0043, 0x00330043, 0x003b0043, 0x00430044, 0x00530044, 0x00630044,
0x00730044, 0x00830045, 0x00a30045, 0x00c30045, 0x00e30045, 0x01020040, 0x00010020, 0x00010020,
};
static const uint32_t ufbxi_deflate_dist_lut[] = {
0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050001, 0x00070001, 0x00090002, 0x000d0002,
0x00110003, 0x00190003, 0x00210004, 0x00310004, 0x00410005, 0x00610005, 0x00810006, 0x00c10006,
0x01010007, 0x01810007, 0x02010008, 0x03010008, 0x04010009, 0x06010009, 0x0801000a, 0x0c01000a,
0x1001000b, 0x1801000b, 0x2001000c, 0x3001000c, 0x4001000d, 0x6001000d, 0x00010020, 0x00010020,
};
static const uint8_t ufbxi_deflate_code_length_permutation[] = {
16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15,
};
#define UFBXI_INFLATE_FAST_MIN_IN 8
#define UFBXI_INFLATE_FAST_MIN_OUT 2
#define UFBXI_HUFF_MAX_BITS 16
#define UFBXI_HUFF_MAX_VALUE 288
#define UFBXI_HUFF_FAST_BITS 10
#define UFBXI_HUFF_FAST_SIZE (1 << UFBXI_HUFF_FAST_BITS)
#define UFBXI_HUFF_FAST_MASK (UFBXI_HUFF_FAST_SIZE - 1)
#define UFBXI_HUFF_MAX_LONG_BITS 5
#define UFBXI_HUFF_MAX_LONG_SYMS 380
#define UFBXI_HUFF_CODELEN_FAST_BITS 8
#define UFBXI_HUFF_CODELEN_FAST_MASK ((1<<UFBXI_HUFF_CODELEN_FAST_BITS)-1)
#define UFBXI_HUFF_MAX_EXTRA_SYMS 32
typedef struct {
// Number of bytes left to read from `read_fn()`
size_t input_left;
// User-supplied read callback
ufbx_read_fn *read_fn;
void *read_user;
// Buffer to read to from `read_fn()`, may point to `local_buffer` if user
// didn't supply a suitable buffer.
char *buffer;
size_t buffer_size;
// Current chunk of data to process, either the initial buffer of input
// or part of `buffer`.
const char *chunk_begin; // < Begin of the buffer
const char *chunk_ptr; // < Next bytes to read to `bits`
const char *chunk_yield; // < End of data before needing to call `ufbxi_bit_yield()`
const char *chunk_end; // < End of data before needing to call `ufbxi_bit_refill()`
const char *chunk_real_end; // < Actual end of the data buffer
// Amount of bytes read before the current chunk
size_t num_read_before_chunk;
uint64_t progress_bias;
uint64_t progress_total;
size_t progress_interval;
uint64_t bits; // < Buffered bits
size_t left; // < Number of valid low bits in `bits`
// Progress tracking, maybe `NULL` it not requested
ufbx_progress_cb progress_cb;
// When `progress_cb.fn()` returns `false` set the `cancelled` flag and
// set the buffered bits to `cancel_bits`.
uint64_t cancel_bits;
bool cancelled;
char local_buffer[256];
} ufbxi_bit_stream;
// Packed symbol information:
//
// [0:5] total_bits // [fast=1] Number of bits in the symbol _including_ extra bits
// [0:5] extra_mask // [fast=0] Mask of extra bits to use
// [5] end // 1 if end/invalid symbol, always 0 if `fast` (*1)
// [6] match // 1 if the symbol is a match length, always 0 in non `lit_length` trees (always 0 if `end`)
// [7] fast // 1 if the symbol can be determined from the table bits and `end=0`
// [8:16] lit_byte // [lit_length] Literal byte
// [8:16] len_index // [lit_length, match=1] Match length index
// [8:16] dist_index // [dist] Match distance index
// [8:16] code_length // [code_length] Dynamic Huffman code length code
// [8:16] long_offset // [fast_sym, fast=0, extra_mask>0] Base offset (halved) to `long_sym[]`
// [8:16] code_prefix // [fast_sym, fast=0, extra_mask=0] First 8-bits of the code (reverse of the lookup)
//
// (*1) Not allowing `end` if `fast` serves a dual purpose: It allows us to omit a check for the end symbol in the
// fast path and allows using the symbol as a 64-bit shift amount (x64/ARM64/WASM have native modulo 64 shifts).
//
// Valid bit patterns, all other patterns are forbidden (`sorted_to_sym[]` contains same patterns as `long_sym[]`):
//
// tree b e m f v
//
// lit_length.fast_sym[] N 0 0 1 L // Short N bit code (no extra allowed) for literal byte L
// lit_length.fast_sym[] N 0 1 1 I // Short N bit code (huff+extra bits) for length index I
// lit_length.fast_sym[] M 0 0 0 X // Long code at `lit_length.long_sym[X*2 + ((bits>>FAST_BITS) & M)]`
// lit_length.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `lit_length.sorted_to_sym[]` to resolve (*1)
// lit_length.fast_sym[] N 1 0 0 0 // Short N bit code for end-of-block (256) symbol
// lit_length.fast_sym[] 0 1 0 0 1 // Invalid lit_length code
//
// lit_length.long_sym[] N 0 0 0 L // Long N bit code (no extra allowed) for literal byte L
// lit_length.long_sym[] N 0 1 0 I // Long N bit code (huff+extra bits) for length index I
// lit_length.long_sym[] N 1 0 0 0 // Long N bit code for end-of-block (256) symbol
// lit_length.long_sym[] 0 1 0 0 1 // Invalid lit_length code
//
// dist.fast_sym[] N 0 0 1 L // Short N bit code (huff+extra bits) for distance index I
// dist.fast_sym[] M 0 0 0 X // Long code at `dist.long_sym[X*2 + ((bits>>FAST_BITS) & M)]`
// dist.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `dist.sorted_to_sym[]` to resolve (*1)
// dist.fast_sym[] N 1 0 0 1 // Unused symbol 30-31 or invalid distance code
//
// dist.long_sym[] N 0 0 0 I // Long N bit code (huff+extra bits) for distance index I
// dist.long_sym[] N 1 0 0 1 // Unused symbol 30-31 or invalid distance code
//
// code_length.fast_sym[] N 0 0 1 B // Short N bit code (huff only, extra handled explicitly) for symbol bit count B
// code_length.fast_sym[] M 0 0 0 X // Long code at `dist.long_sym[X*2 + ((bits>>FAST_BITS) & M)]`
// code_length.fast_sym[] 0 0 0 0 R // Extra long code with prefix R, use `code_length.sorted_to_sym[]` to resolve (*1)
//
// code_length.long_sym[] N 0 0 0 B // Long N bit code (huff only, extra handled explicitly) for symbol bit count B
//
// (*1) Never necessary if `fast_bits >= 10` due to `long_sym[]` covering all possible codes,
//
typedef uint16_t ufbxi_huff_sym;
#define ufbxi_huff_sym_total_bits(sym) ((uint32_t)(sym) & 0x1f)
#define ufbxi_huff_sym_long_mask(sym) ((uint32_t)(sym) & 0x1f)
#define ufbxi_huff_sym_long_offset(sym) ((uint32_t)(sym) >> 7u)
#define ufbxi_huff_sym_value(sym) ((uint32_t)(sym) >> 8u)
enum {
UFBXI_HUFF_SYM_END = 0x20,
UFBXI_HUFF_SYM_MATCH = 0x40,
UFBXI_HUFF_SYM_FAST = 0x80,
};
#define UFBXI_HUFF_ERROR_SYM ((ufbxi_huff_sym)0x0120) // Error symbol, END (value 1)
#define UFBXI_HUFF_UNINITIALIZED_SYM ((ufbxi_huff_sym)0x0220) // Uninitialized symbol for regression, END (value 2)
typedef struct {
ufbxi_huff_sym fast_sym[UFBXI_HUFF_FAST_SIZE]; // < Lookup from N bytes to symbol information
ufbxi_huff_sym long_sym[UFBXI_HUFF_MAX_LONG_SYMS]; // < Fast long symbol lookup
ufbxi_huff_sym sorted_to_sym[UFBXI_HUFF_MAX_VALUE]; // < Symbol information per sorted index
uint32_t extra_shift_base[UFBXI_HUFF_MAX_EXTRA_SYMS]; // < [0:6] shift [16:32] base value
uint16_t extra_mask[UFBXI_HUFF_MAX_EXTRA_SYMS]; // < Mask for extra bits
uint16_t past_max_code[UFBXI_HUFF_MAX_BITS]; // < One past maximum code value per bit length
int16_t code_to_sorted[UFBXI_HUFF_MAX_BITS]; // < Code to sorted symbol index per bit length
uint32_t num_symbols;
uint32_t end_of_block_bits;
} ufbxi_huff_tree;
typedef struct {
union {
struct {
ufbxi_huff_tree lit_length;
ufbxi_huff_tree dist;
};
ufbxi_huff_tree trees[2];
};
uint32_t fast_bits;
} ufbxi_trees;
typedef struct {
bool initialized;
ufbxi_trees static_trees;
} ufbxi_inflate_retain_imp;
ufbx_static_assert(inflate_retain_size, sizeof(ufbxi_inflate_retain_imp) <= sizeof(ufbx_inflate_retain));
typedef struct {
ufbxi_bit_stream stream;
uint32_t fast_bits;
char *out_begin;
char *out_ptr;
char *out_end;
} ufbxi_deflate_context;
static ufbxi_forceinline uint32_t
ufbxi_bit_reverse(uint32_t mask, uint32_t num_bits)
{
ufbxi_dev_assert(num_bits <= 16);
uint32_t x = mask;
x = (((x & 0xaaaa) >> 1) | ((x & 0x5555) << 1));
x = (((x & 0xcccc) >> 2) | ((x & 0x3333) << 2));
x = (((x & 0xf0f0) >> 4) | ((x & 0x0f0f) << 4));
x = (((x & 0xff00) >> 8) | ((x & 0x00ff) << 8));
return x >> (16 - num_bits);
}
static ufbxi_noinline const char *
ufbxi_bit_chunk_refill(ufbxi_bit_stream *s, const char *ptr)
{
// Copy any left-over data to the beginning of `buffer`
size_t left = ufbxi_to_size(s->chunk_real_end - ptr);
ufbxi_dev_assert(left < 64);
if (left > 0) memmove(s->buffer, ptr, left);
s->num_read_before_chunk += ufbxi_to_size(ptr - s->chunk_begin);
// Read more user data if the user supplied a `read_fn()`, otherwise
// we assume the initial data chunk is the whole input buffer.
if (s->read_fn && !s->cancelled) {
size_t to_read = ufbxi_min_sz(s->input_left, s->buffer_size - left);
if (to_read > 0) {
size_t num_read = s->read_fn(s->read_user, s->buffer + left, to_read);
// TODO: IO error, should unify with (currently broken) cancel logic
if (num_read > to_read) num_read = 0;
ufbxi_dev_assert(s->input_left >= num_read);
s->input_left -= num_read;
left += num_read;
}
}
// Pad the rest with zeros
if (left < 64) {
memset(s->buffer + left, 0, 64 - left);
left = 64;
}
s->chunk_begin = s->buffer;
s->chunk_ptr = s->buffer;
s->chunk_end = s->buffer + left - 8;
s->chunk_real_end = s->buffer + left;
return s->buffer;
}
static ufbxi_noinline void ufbxi_bit_stream_init(ufbxi_bit_stream *s, const ufbx_inflate_input *input)
{
size_t data_size = input->data_size;
if (data_size > input->total_size) {
data_size = input->total_size;
}
s->read_fn = input->read_fn;
s->read_user = input->read_user;
s->progress_cb = input->progress_cb;
s->chunk_begin = (const char*)input->data;
s->chunk_ptr = (const char*)input->data;
s->chunk_end = ufbxi_add_ptr((const char*)input->data, ufbxi_max_sz(8, data_size) - 8);
s->chunk_real_end = ufbxi_add_ptr((const char*)input->data, data_size);
s->input_left = input->total_size - data_size;
// Use the user buffer if it's large enough, otherwise `local_buffer`
if (input->buffer_size > sizeof(s->local_buffer)) {
s->buffer = (char*)input->buffer;
s->buffer_size = input->buffer_size;
} else {
s->buffer = s->local_buffer;
s->buffer_size = sizeof(s->local_buffer);
}
s->num_read_before_chunk = 0;
s->progress_bias = input->progress_size_before;
s->progress_total = input->total_size + input->progress_size_before + input->progress_size_after;
if (!s->progress_cb.fn || input->progress_interval_hint >= SIZE_MAX) {
s->progress_interval = SIZE_MAX;
} else if (input->progress_interval_hint > 0) {
s->progress_interval = (size_t)input->progress_interval_hint;
} else {
s->progress_interval = 0x4000;
}
s->cancelled = false;
// Clear the initial bit buffer
s->bits = 0;
s->left = 0;
// If the initial data buffer is not large enough to be read directly
// from refill the chunk once.
if (data_size < 64) {
ufbxi_bit_chunk_refill(s, s->chunk_begin);
}
if (s->progress_cb.fn && ufbxi_to_size(s->chunk_end - s->chunk_ptr) > s->progress_interval + 8) {
s->chunk_yield = s->chunk_ptr + s->progress_interval;
} else {
s->chunk_yield = s->chunk_end;
}
}
static ufbxi_noinline const char *
ufbxi_bit_yield(ufbxi_bit_stream *s, const char *ptr)
{
if (ptr > s->chunk_end) {
ptr = ufbxi_bit_chunk_refill(s, ptr);
}
if (s->progress_cb.fn) {
size_t num_read = s->num_read_before_chunk + ufbxi_to_size(ptr - s->chunk_begin);
ufbx_progress progress = { s->progress_bias + num_read, s->progress_total };
uint32_t result = (uint32_t)s->progress_cb.fn(s->progress_cb.user, &progress);
ufbx_assert(result == UFBX_PROGRESS_CONTINUE || result == UFBX_PROGRESS_CANCEL);
if (result == UFBX_PROGRESS_CANCEL) {
s->cancelled = true;
ptr = s->local_buffer;
s->buffer = s->local_buffer;
s->buffer_size = sizeof(s->local_buffer);
s->chunk_begin = ptr;
s->chunk_ptr = ptr;
s->chunk_end = ptr + sizeof(s->local_buffer) - 8;
s->chunk_real_end = ptr + sizeof(s->local_buffer);
memset(s->local_buffer, 0, sizeof(s->local_buffer));
}
}
if (s->progress_cb.fn && ufbxi_to_size(s->chunk_end - ptr) > s->progress_interval + 8) {
s->chunk_yield = ptr + s->progress_interval;
} else {
s->chunk_yield = s->chunk_end;
}
return ptr;
}
static ufbxi_forceinline void
ufbxi_bit_refill(uint64_t *p_bits, size_t *p_left, const char **p_data, ufbxi_bit_stream *s)
{
if (*p_data > s->chunk_yield) {
*p_data = ufbxi_bit_yield(s, *p_data);
if (s->cancelled) {
// Force an end-of-block symbol when cancelled so we don't need an
// extra branch in the chunk decoding loop.
*p_bits = s->cancel_bits;
}
}
// See https://fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/
// variant 4. This branchless refill guarantees [56,63] bits to be valid in `*p_bits`.
ufbxi_regression_assert(*p_left <= 64); \
*p_bits |= ufbxi_read_u64(*p_data) << *p_left;
*p_data += (63 - *p_left) >> 3;
*p_left |= 56;
}
// See `ufbxi_bit_refill()`
#define ufbxi_macro_bit_refill_fast(m_bits, m_left, m_data, m_refill_bits) do { \
ufbxi_regression_assert(m_left <= 64); \
m_bits |= m_refill_bits << m_left; \
m_data += (63 - m_left) >> 3; \
m_left |= 56; \
} while (0)
static ufbxi_noinline int
ufbxi_bit_copy_bytes(void *dst, ufbxi_bit_stream *s, size_t len)
{
ufbx_assert(s->left % 8 == 0);
char *ptr = (char*)dst;
// Copy the buffered bits first
while (len > 0 && s->left > 0) {
*ptr++ = (char)(uint8_t)s->bits;
len -= 1;
s->bits >>= 8;
s->left -= 8;
}
// We need to clear the top bits as there may be data
// read ahead past `s->left` in some cases
s->bits = 0;
// Copy the current chunk
size_t chunk_left = ufbxi_to_size(s->chunk_real_end - s->chunk_ptr);
if (chunk_left >= len) {
memcpy(ptr, s->chunk_ptr, len);
s->chunk_ptr += len;
return 1;
} else {
memcpy(ptr, s->chunk_ptr, chunk_left);
s->chunk_ptr += chunk_left;
ptr += chunk_left;
len -= chunk_left;
}
// Read extra bytes from user
if (len > s->input_left) return 0;
size_t num_read = 0;
if (s->read_fn) {
num_read = s->read_fn(s->read_user, ptr, len);
s->input_left -= num_read;
}
return num_read == len;
}
// 0: Success
// -1: Overfull
// -2: Underfull
static ufbxi_noinline ptrdiff_t
ufbxi_huff_build_imp(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits, uint32_t *bits_counts)
{
uint32_t fast_mask = (1u << fast_bits) - 1;
ufbx_assert(sym_count <= UFBXI_HUFF_MAX_VALUE);
tree->num_symbols = sym_count;
uint32_t nonzero_sym_count = sym_count - bits_counts[0];
uint32_t total_syms[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit
uint32_t first_code[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit
tree->code_to_sorted[0] = INT16_MAX;
tree->past_max_code[0] = 0;
total_syms[0] = 0;
// Clear to uninitialized symbols
#if defined(UFBX_REGRESSION)
{
for (size_t i = 0; i < UFBXI_HUFF_FAST_SIZE; i++) {
tree->fast_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM;
}
for (size_t i = 0; i < UFBXI_HUFF_MAX_VALUE; i++) {
tree->sorted_to_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM;
}
for (size_t i = 0; i < UFBXI_HUFF_MAX_LONG_SYMS; i++) {
tree->long_sym[i] = UFBXI_HUFF_UNINITIALIZED_SYM;
}
}
#endif
uint32_t last_valid_prefix = 0;
// Resolve the maximum code per bit length and ensure that the tree is not
// overfull or underfull.
{
int num_codes_left = 1;
uint32_t code = 0;
uint32_t prev_count = 0;
uint32_t long_offset = 0;
for (uint32_t bits = 1; bits < UFBXI_HUFF_MAX_BITS; bits++) {
uint32_t count = bits_counts[bits];
code = (code + prev_count) << 1;
first_code[bits] = code;
tree->past_max_code[bits] = (uint16_t)(code + count);
uint32_t prev_syms = total_syms[bits - 1];
total_syms[bits] = prev_syms + count;
// Each bit level doubles the amount of codes and potentially removes some
num_codes_left = (num_codes_left << 1) - (int32_t)count;
if (num_codes_left < 0) {
return -1;
}
if (count > 0 && bits > fast_bits && bits - fast_bits <= UFBXI_HUFF_MAX_LONG_BITS) {
uint32_t shift = bits - fast_bits;
uint32_t last_inclusive = num_codes_left == 0 ? (1u<<shift) - 1u : 0u;
uint32_t first_prefix = code >> shift;
uint32_t last_prefix = (code + count + last_inclusive) >> shift;
uint32_t mask = (1u << shift) - 1u;
uint32_t half_step = 1u << (shift - 1u);
for (uint32_t prefix = first_prefix; prefix < last_prefix; prefix++) {
uint32_t rev_prefix = ufbxi_bit_reverse(prefix, fast_bits);
tree->fast_sym[rev_prefix] = (ufbxi_huff_sym)(mask | (long_offset << 8));
long_offset += half_step;
}
last_valid_prefix = last_prefix;
}
if (count > 0) {
tree->code_to_sorted[bits] = (int16_t)((int)prev_syms - (int)code);
} else {
tree->code_to_sorted[bits] = INT16_MAX;
}
prev_count = count;
}
// All codes should be used if there's more than one symbol, if there's only one symbol there should be
// only a single 1-bit code.
if (nonzero_sym_count > 1 && num_codes_left != 0) {
return -2;
} else if (nonzero_sym_count == 1 && total_syms[1] != 1) {
return -2;
}
// We should always have enough space for long symbols as we support up to 5 (UFBXI_HUFF_MAX_LONG_BITS)
// bits and the largest tree has 286 symbols. For each bit we may waste at most 2^bits slots (conservative)
// and in the end we may waste 2^5 slots giving us `286+2+4+8+16+32+32 = 380` (UFBXI_HUFF_MAX_LONG_SYMS)
ufbx_assert(long_offset <= UFBXI_HUFF_MAX_LONG_SYMS);
}
tree->end_of_block_bits = 0;
uint32_t num_extra = 0;
tree->extra_shift_base[0] = 0;
tree->extra_mask[0] = 0;
// Fill `fast_sym[]` with error symbols if necessary, we don't need to do this if we have two or more symbols
// as the tree is guaranteed to be full, which means we will populate the whole `fast_sym[]`
if (nonzero_sym_count <= 1) {
for (uint32_t i = 0; i <= fast_mask; i++) {
tree->fast_sym[i] = UFBXI_HUFF_ERROR_SYM;
}
}
// Generate per-length sorted-to-symbol and fast lookup tables
uint32_t bits_index[UFBXI_HUFF_MAX_BITS] = { 0 };
for (uint32_t i = 0; i < sym_count; i++) {
uint32_t bits = sym_bits[i];
if (bits == 0) continue;
uint32_t sym = i << 8 | bits;
if (i >= sym_extra_offset) {
uint32_t extra = sym_extra[i - sym_extra_offset];
sym += extra;
// Store length/distance codes with extra values in a table.
// TODO: This is unnecessary for small values
if ((extra & 0xffff001f) != 0 && (extra & 0x20) == 0) {
uint32_t ix = ++num_extra;
tree->extra_shift_base[ix] = (extra & 0xffff0000) | bits;
tree->extra_mask[ix] = (uint16_t)((1u << (extra & 0x1f)) - 1);
sym = (sym & 0xff) | ix << 8;
}
}
uint32_t index = bits_index[bits]++;
uint32_t sorted = total_syms[bits - 1] + index;
tree->sorted_to_sym[sorted] = (ufbxi_huff_sym)sym;
// Reverse the code and fill all fast lookups with the reversed prefix
uint32_t code = first_code[bits] + index;
uint32_t rev_code = ufbxi_bit_reverse(code, bits);
if (bits <= fast_bits) {
uint32_t fast_sym = sym;
// The `end` and `fast` flags are mutually exclusive
if ((fast_sym & UFBXI_HUFF_SYM_END) == 0) {
fast_sym |= UFBXI_HUFF_SYM_FAST;
}
uint32_t hi_max = 1u << (fast_bits - bits);
for (uint32_t hi = 0; hi < hi_max; hi++) {
ufbxi_regression_assert(nonzero_sym_count <= 1 || tree->fast_sym[rev_code | hi << bits] == UFBXI_HUFF_UNINITIALIZED_SYM);
tree->fast_sym[rev_code | hi << bits] = (ufbxi_huff_sym)fast_sym;
}
} else if (bits <= fast_bits + UFBXI_HUFF_MAX_LONG_BITS && (code >> (bits - fast_bits)) < last_valid_prefix) {
uint32_t fast_sym = tree->fast_sym[rev_code & fast_mask];
ufbxi_regression_assert(fast_sym != UFBXI_HUFF_UNINITIALIZED_SYM);
uint32_t long_bits = 0;
uint32_t long_mask = fast_sym;
while (long_bits < UFBXI_HUFF_MAX_LONG_BITS && (long_mask & 1) != 0) {
long_mask >>= 1;
long_bits += 1;
}
ufbxi_dev_assert(long_bits >= 1);
uint32_t long_base = fast_sym >> 7u; // aka (fast_sym >> 8) * 2
uint32_t lo_bits = bits - fast_bits;
uint32_t hi_max = 1u << (long_bits - lo_bits);
uint32_t rev_suffix = rev_code >> fast_bits;
for (uint32_t hi = 0; hi < hi_max; hi++) {
ufbxi_regression_assert(tree->long_sym[long_base + (rev_suffix | hi << lo_bits)] == UFBXI_HUFF_UNINITIALIZED_SYM);
tree->long_sym[long_base + (rev_suffix | hi << lo_bits)] = (ufbxi_huff_sym)sym;
}
} else {
uint32_t fast_sym = (code >> (bits - fast_bits)) << 8;
ufbxi_regression_assert(
tree->fast_sym[rev_code & fast_mask] == UFBXI_HUFF_UNINITIALIZED_SYM ||
tree->fast_sym[rev_code & fast_mask] == (ufbxi_huff_sym)fast_sym);
tree->fast_sym[rev_code & fast_mask] = (ufbxi_huff_sym)fast_sym;
}
// Make sure the end-of-block symbol goes through the slow path
// Also store the end-of-block code so we can interrupt decoding
if (i == 256) {
tree->end_of_block_bits = rev_code;
}
}
// Make sure all `fast_sym[]` are filled with an initialized value.
#if defined(UFBX_REGRESSION)
{
for (size_t i = 0; i < UFBXI_HUFF_FAST_SIZE; i++) {
if (i <= fast_mask) {
ufbx_assert(tree->fast_sym[i] != UFBXI_HUFF_UNINITIALIZED_SYM);
} else {
ufbx_assert(tree->fast_sym[i] == UFBXI_HUFF_UNINITIALIZED_SYM);
}
}
for (size_t i = 0; i < nonzero_sym_count; i++) {
ufbx_assert(tree->sorted_to_sym[i] != UFBXI_HUFF_UNINITIALIZED_SYM);
}
}
#endif
return 0;
}
// 0: Success
// -1: Overfull
// -2: Underfull
static ufbxi_noinline ptrdiff_t
ufbxi_huff_build(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits)
{
// Count the number of codes per bit length
// `bits_counts[0]` contains the number of non-used symbols
uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit
memset(bits_counts, 0, sizeof(bits_counts));
for (uint32_t i = 0; i < sym_count; i++) {
uint32_t bits = sym_bits[i];
ufbx_assert(bits < UFBXI_HUFF_MAX_BITS);
bits_counts[bits]++;
}
return ufbxi_huff_build_imp(tree, sym_bits, sym_count, sym_extra, sym_extra_offset, fast_bits, bits_counts);
}
static ufbxi_forceinline ufbxi_huff_sym
ufbxi_huff_decode_bits(const ufbxi_huff_tree *tree, uint64_t bits, uint32_t fast_bits, uint32_t fast_mask)
{
ufbxi_huff_sym sym = tree->fast_sym[bits & fast_mask];
ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM);
if ((sym & (UFBXI_HUFF_SYM_FAST|UFBXI_HUFF_SYM_END)) != 0) {
return sym;
}
uint32_t tail = (uint32_t)(bits >> fast_bits);
uint32_t long_mask = ufbxi_huff_sym_long_mask(sym);
if (long_mask) {
sym = tree->long_sym[ufbxi_huff_sym_long_offset(sym) + (tail & long_mask)];
ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM);
return sym;
}
ufbxi_dev_assert(fast_bits <= 8);
uint32_t code = ufbxi_huff_sym_value(sym);
uint32_t num_bits = fast_bits;
for (;;) {
code = code << 1 | (tail & 1);
tail >>= 1;
num_bits++;
ufbxi_regression_assert(num_bits < UFBXI_HUFF_MAX_BITS);
if (code < tree->past_max_code[num_bits]) {
sym = tree->sorted_to_sym[(int32_t)code + (int32_t)tree->code_to_sorted[num_bits]];
ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM);
return sym;
}
}
}
static ufbxi_noinline void ufbxi_init_static_huff(ufbxi_trees *trees, const ufbx_inflate_input *input)
{
ptrdiff_t err = 0;
// Override `fast_bits` if necessary, this must always be valid as it's checked in the beginning of `ufbx_inflate()`.
if (input && input->internal_fast_bits != 0) {
trees->fast_bits = (uint32_t)input->internal_fast_bits;
ufbx_assert(!(trees->fast_bits < 1 || trees->fast_bits == 9 || trees->fast_bits > 10));
} else {
trees->fast_bits = UFBXI_HUFF_FAST_BITS;
}
// 0-143: 8 bits, 144-255: 9 bits, 256-279: 7 bits, 280-287: 8 bits
uint8_t lit_length_bits[288]; // ufbxi_uninit
memset(lit_length_bits + 0, 8, 144 - 0);
memset(lit_length_bits + 144, 9, 256 - 144);
memset(lit_length_bits + 256, 7, 280 - 256);
memset(lit_length_bits + 280, 8, 288 - 280);
err |= ufbxi_huff_build(&trees->lit_length, lit_length_bits, sizeof(lit_length_bits), ufbxi_deflate_length_lut, 256, trees->fast_bits);
// "Distance codes 0-31 are represented by (fixed-length) 5-bit codes"
uint8_t dist_bits[32]; // ufbxi_uninit
memset(dist_bits + 0, 5, 32 - 0);
err |= ufbxi_huff_build(&trees->dist, dist_bits, sizeof(dist_bits), ufbxi_deflate_dist_lut, 0, trees->fast_bits);
// Building the static trees cannot fail as we use pre-defined code lengths.
ufbxi_ignore(err);
ufbx_assert(err == 0);
}
// 0: Success
// -1: Huffman Overfull
// -2: Huffman Underfull
// -3: Code 16 repeat overflow
// -4: Code 17 repeat overflow
// -5: Code 18 repeat overflow
// -6: Bad length code
// -7: Cancelled
static ufbxi_noinline ptrdiff_t ufbxi_init_dynamic_huff_tree(ufbxi_deflate_context *dc, const ufbxi_huff_tree *huff_code_length, ufbxi_huff_tree *tree,
uint32_t num_symbols, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits)
{
uint8_t code_lengths[UFBXI_HUFF_MAX_VALUE]; // ufbxi_uninit
ufbx_assert(num_symbols <= UFBXI_HUFF_MAX_VALUE);
uint64_t bits = dc->stream.bits;
size_t left = dc->stream.left;
const char *data = dc->stream.chunk_ptr;
uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit
memset(bits_counts, 0, sizeof(bits_counts));
uint32_t symbol_index = 0;
uint8_t prev = 0;
while (symbol_index < num_symbols) {
ufbxi_bit_refill(&bits, &left, &data, &dc->stream);
if (dc->stream.cancelled) return -7;
ufbxi_huff_sym sym = ufbxi_huff_decode_bits(huff_code_length, bits, UFBXI_HUFF_CODELEN_FAST_BITS, UFBXI_HUFF_CODELEN_FAST_MASK);
ufbxi_regression_assert(sym != UFBXI_HUFF_UNINITIALIZED_SYM);
uint32_t inst = ufbxi_huff_sym_value(sym);
uint32_t sym_len = ufbxi_huff_sym_total_bits(sym);
bits >>= sym_len;
left -= sym_len;
if (inst <= 15) {
// "0 - 15: Represent code lengths of 0 - 15"
prev = (uint8_t)inst;
code_lengths[symbol_index++] = (uint8_t)inst;
bits_counts[(int32_t)inst]++;
} else if (inst == 16) {
// "16: Copy the previous code length 3 - 6 times. The next 2 bits indicate repeat length."
uint32_t num = 3 + ((uint32_t)bits & 0x3);
bits >>= 2;
left -= 2;
if (symbol_index + num > num_symbols) return -3;
memset(code_lengths + symbol_index, prev, num);
symbol_index += num;
bits_counts[(int32_t)prev] += num;
} else if (inst == 17) {
// "17: Repeat a code length of 0 for 3 - 10 times. (3 bits of length)"
uint32_t num = 3 + ((uint32_t)bits & 0x7);
bits >>= 3;
left -= 3;
if (symbol_index + num > num_symbols) return -4;
memset(code_lengths + symbol_index, 0, num);
symbol_index += num;
prev = 0;
bits_counts[0] += num;
} else if (inst == 18) {
// "18: Repeat a code length of 0 for 11 - 138 times (7 bits of length)"
uint32_t num = 11 + ((uint32_t)bits & 0x7f);
bits >>= 7;
left -= 7;
if (symbol_index + num > num_symbols) return -5;
memset(code_lengths + symbol_index, 0, num);
symbol_index += num;
prev = 0;
bits_counts[0] += num;
} else {
return -6;
}
}
ptrdiff_t err = ufbxi_huff_build_imp(tree, code_lengths, num_symbols, sym_extra, sym_extra_offset, fast_bits, bits_counts);
if (err != 0) return err;
dc->stream.bits = bits;
dc->stream.left = left;
dc->stream.chunk_ptr = data;
return 0;
}
static ufbxi_noinline ptrdiff_t
ufbxi_init_dynamic_huff(ufbxi_deflate_context *dc, ufbxi_trees *trees)
{
uint64_t bits = dc->stream.bits;
size_t left = dc->stream.left;
const char *data = dc->stream.chunk_ptr;
ufbxi_bit_refill(&bits, &left, &data, &dc->stream);
if (dc->stream.cancelled) return -28;
trees->fast_bits = dc->fast_bits;
// The header contains the number of Huffman codes in each of the three trees.
uint32_t num_lit_lengths = 257 + (uint32_t)(bits & 0x1f);
uint32_t num_dists = 1 + (uint32_t)(bits >> 5 & 0x1f);
uint32_t num_code_lengths = 4 + (uint32_t)(bits >> 10 & 0xf);
bits >>= 14;
left -= 14;
// Code lengths for the "code length" Huffman tree are represented literally
// 3 bits in order of: 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 up to
// `num_code_lengths`, rest of the code lengths are 0 (unused)
uint8_t code_lengths[19]; // ufbxi_uninit
memset(code_lengths, 0, sizeof(code_lengths));
for (size_t len_i = 0; len_i < num_code_lengths; len_i++) {
if (len_i == 14) {
ufbxi_bit_refill(&bits, &left, &data, &dc->stream);
if (dc->stream.cancelled) return -28;
}
code_lengths[ufbxi_deflate_code_length_permutation[len_i]] = (uint32_t)bits & 0x7;
bits >>= 3;
left -= 3;
}
dc->stream.bits = bits;
dc->stream.left = left;
dc->stream.chunk_ptr = data;
ufbxi_huff_tree huff_code_length; // ufbxi_uninit
ptrdiff_t err; // ufbxi_uninit
// Build the temporary "code length" Huffman tree used to encode the actual
// trees used to compress the data. Use that to build the literal/length and
// distance trees.
err = ufbxi_huff_build(&huff_code_length, code_lengths, ufbxi_arraycount(code_lengths), NULL, INT32_MAX, UFBXI_HUFF_CODELEN_FAST_BITS);
if (err) return -14 + 1 + err;
err = ufbxi_init_dynamic_huff_tree(dc, &huff_code_length, &trees->lit_length, num_lit_lengths, ufbxi_deflate_length_lut, 256, dc->fast_bits);
if (err) return err == -7 ? -28 : -16 + 1 + err;
err = ufbxi_init_dynamic_huff_tree(dc, &huff_code_length, &trees->dist, num_dists, ufbxi_deflate_dist_lut, 0, dc->fast_bits);
if (err) return err == -7 ? -28 : -22 + 1 + err;
return 0;
}
static ufbxi_noinline uint32_t ufbxi_adler32(const void *data, size_t size)
{
ufbxi_fast_uint a = 1, b = 0;
const char *p = (const char*)data;
// Adler-32 consists of two running sums modulo 65521. As an optimization
// we can accumulate N sums before applying the modulo, where N depends on
// the size of the type holding the sum.
const ufbxi_fast_uint num_before_wrap = sizeof(ufbxi_fast_uint) == 8 ? 380368439u : 5552u;
ufbxi_fast_uint size_left = size;
while (size_left > 0) {
ufbxi_fast_uint num = size_left <= num_before_wrap ? size_left : num_before_wrap;
size_left -= num;
const char *end = p + num;
// Align to 16 bytes
while (p != end && ((uintptr_t)p & 0xf) != 0) {
a += (ufbxi_fast_uint)(uint8_t)p[0]; b += a;
p++;
}
#if UFBXI_HAS_SSE
static const uint16_t factors[2][8] = {
{ 16, 15, 14, 13, 12, 11, 10, 9, },
{ 8, 7, 6, 5, 4, 3, 2, 1, },
};
const __m128i zero = _mm_setzero_si128();
const __m128i factor_1 = _mm_set1_epi16(1);
const __m128i factor_16 = _mm_set1_epi16(16);
const __m128i factor_lo = _mm_loadu_si128((const __m128i*)factors[0]);
const __m128i factor_hi = _mm_loadu_si128((const __m128i*)factors[1]);
for (;;) {
size_t chunk_size = ufbxi_min_sz(ufbxi_to_size(end - p), 5803) & ~(size_t)0xff;
if (chunk_size == 0) break;
const char *chunk_end = p + chunk_size;
__m128i s1 = zero;
__m128i s2 = zero;
while (p != chunk_end) {
__m128i s1_lo = zero, s1_hi = zero;
__m128i tmp_lo = zero, tmp_hi = zero;
ufbxi_nounroll for (size_t i = 0; i < 256; i += 32) {
__m128i d0 = _mm_load_si128((const __m128i*)(p + i + 0));
__m128i d1 = _mm_load_si128((const __m128i*)(p + i + 16));
tmp_lo = _mm_add_epi16(tmp_lo, s1_lo);
tmp_hi = _mm_add_epi16(tmp_hi, s1_hi);
s1_lo = _mm_add_epi16(s1_lo, _mm_unpacklo_epi8(d0, zero));
s1_hi = _mm_add_epi16(s1_hi, _mm_unpackhi_epi8(d0, zero));
tmp_lo = _mm_add_epi16(tmp_lo, s1_lo);
tmp_hi = _mm_add_epi16(tmp_hi, s1_hi);
s1_lo = _mm_add_epi16(s1_lo, _mm_unpacklo_epi8(d1, zero));
s1_hi = _mm_add_epi16(s1_hi, _mm_unpackhi_epi8(d1, zero));
}
s2 = _mm_add_epi32(s2, _mm_slli_epi32(s1, 8));
s1 = _mm_add_epi32(s1, _mm_madd_epi16(s1_lo, factor_1));
s1 = _mm_add_epi32(s1, _mm_madd_epi16(s1_hi, factor_1));
s2 = _mm_add_epi32(s2, _mm_madd_epi16(tmp_lo, factor_16));
s2 = _mm_add_epi32(s2, _mm_madd_epi16(tmp_hi, factor_16));
s2 = _mm_add_epi32(s2, _mm_madd_epi16(s1_lo, factor_lo));
s2 = _mm_add_epi32(s2, _mm_madd_epi16(s1_hi, factor_hi));
p += 256;
}
s1 = _mm_add_epi32(s1, _mm_shuffle_epi32(s1, _MM_SHUFFLE(2,3,0,1)));
s2 = _mm_add_epi32(s2, _mm_shuffle_epi32(s2, _MM_SHUFFLE(2,3,0,1)));
s1 = _mm_add_epi32(s1, _mm_shuffle_epi32(s1, _MM_SHUFFLE(1,0,3,2)));
s2 = _mm_add_epi32(s2, _mm_shuffle_epi32(s2, _MM_SHUFFLE(1,0,3,2)));
b += chunk_size * a;
a += (uint32_t)_mm_cvtsi128_si32(s1);
b += (uint32_t)_mm_cvtsi128_si32(s2);
}
#elif UFBX_LITTLE_ENDIAN
for (;;) {
size_t chunk_size = ufbxi_min_sz(ufbxi_to_size(end - p), 256*8/4) & ~(size_t)0xf;
if (chunk_size == 0) break;
const char *chunk_end = p + chunk_size;
uint64_t s1_lo = 0, s1_hi = 0;
uint64_t tmp, s2 = 0;
uint64_t mask8 = UINT64_C(0x00ff00ff00ff00ff);
uint64_t mask16 = UINT64_C(0x0000ffff0000ffff);
while (p != chunk_end) {
uint64_t d0 = *(const uint64_t*)p;
uint64_t d1 = *(const uint64_t*)(p + 8);
tmp = s1_lo + s1_hi;
s1_lo += d0 & mask8;
s1_hi += (d0 >> 8) & mask8;
tmp += s1_lo + s1_hi;
s1_lo += d1 & mask8;
s1_hi += (d1 >> 8) & mask8;
s2 += (tmp & mask16) + ((tmp >> 16) & mask16);
p += 16;
}
uint64_t s1 = s1_lo + s1_hi;
s1 = (s1 & mask16) + ((s1 >> 16u) & mask16);
ufbxi_fast_uint s1_sum = (ufbxi_fast_uint)(s1 + (s1 >> 32u));
ufbxi_fast_uint s2_sum = (ufbxi_fast_uint)(s2 + (s2 >> 32u)) * 8;
s2_sum += ((ufbxi_fast_uint)(s1_lo >> 0) & 0xffff) * 8;
s2_sum += ((ufbxi_fast_uint)(s1_hi >> 0) & 0xffff) * 7;
s2_sum += ((ufbxi_fast_uint)(s1_lo >> 16) & 0xffff) * 6;
s2_sum += ((ufbxi_fast_uint)(s1_hi >> 16) & 0xffff) * 5;
s2_sum += ((ufbxi_fast_uint)(s1_lo >> 32) & 0xffff) * 4;
s2_sum += ((ufbxi_fast_uint)(s1_hi >> 32) & 0xffff) * 3;
s2_sum += ((ufbxi_fast_uint)(s1_lo >> 48) & 0xffff) * 2;
s2_sum += ((ufbxi_fast_uint)(s1_hi >> 48) & 0xffff) * 1;
b += chunk_size * a;
a += s1_sum & 0xffffffffu;
b += s2_sum & 0xffffffffu;
}
#endif
while (p != end) {
a += (size_t)(uint8_t)p[0]; b += a;
p++;
}
a %= 65521u;
b %= 65521u;
}
return (uint32_t)((b << 16) | (a & 0xffff));
}
static ufbxi_noinline int
ufbxi_inflate_block_slow(ufbxi_deflate_context *dc, ufbxi_trees *trees, size_t max_symbols)
{
char *out_ptr = dc->out_ptr;
char *const out_begin = dc->out_begin;
char *const out_end = dc->out_end;
uint32_t fast_bits = trees->fast_bits;
uint32_t fast_mask = (1u << fast_bits) - 1;
uint64_t bits = dc->stream.bits;
size_t left = dc->stream.left;
const char *data = dc->stream.chunk_ptr;
for (;;) {
if (max_symbols-- == 0) break;
ufbxi_bit_refill(&bits, &left, &data, &dc->stream);
uint64_t sym_bits = bits;
ufbxi_huff_sym sym0 = ufbxi_huff_decode_bits(&trees->lit_length, bits, fast_bits, fast_mask);
ufbxi_regression_assert(sym0 != UFBXI_HUFF_UNINITIALIZED_SYM);
uint32_t sym0_bits = ufbxi_huff_sym_total_bits(sym0);
bits >>= sym0_bits;
left -= sym0_bits;
if (sym0 & UFBXI_HUFF_SYM_END) {
if (ufbxi_huff_sym_value(sym0) != 0) return -13;
dc->out_ptr = out_ptr;
dc->stream.bits = bits;
dc->stream.left = left;
dc->stream.chunk_ptr = data;
return 0;
} else if ((sym0 & UFBXI_HUFF_SYM_MATCH) == 0) {
if (out_ptr == out_end) return -10;
*out_ptr++ = (char)ufbxi_huff_sym_value(sym0);
continue;
}
uint32_t sym0_value = ufbxi_huff_sym_value(sym0);
uint32_t len_shift_base = trees->lit_length.extra_shift_base[sym0_value];
uint16_t len_mask = trees->lit_length.extra_mask[sym0_value];
uint32_t length = (len_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym_bits, len_shift_base) & len_mask);
ufbxi_huff_sym sym1 = ufbxi_huff_decode_bits(&trees->dist, bits, fast_bits, fast_mask);
ufbxi_regression_assert(sym1 != UFBXI_HUFF_UNINITIALIZED_SYM);
if (sym1 & UFBXI_HUFF_SYM_END) return -11;
uint32_t sym1_bits = ufbxi_huff_sym_total_bits(sym1);
bits >>= sym1_bits;
left -= sym1_bits;
uint32_t sym1_value = ufbxi_huff_sym_value(sym1);
uint32_t dist_shift_base = trees->dist.extra_shift_base[sym1_value];
uint16_t dist_mask = trees->dist.extra_mask[sym1_value];
uint32_t distance = (dist_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym_bits, dist_shift_base + sym0) & dist_mask);
// Bounds checking
size_t out_space = ufbxi_to_size(out_end - out_ptr);
if ((ptrdiff_t)distance > out_ptr - out_begin || length > out_space) {
return -12;
}
// Copy the match
const char *src = out_ptr - distance;
char *dst = out_ptr;
char *end = dst + length;
out_ptr += length;
if (out_space >= length + 16) {
uint32_t min_dist = length < 16 ? length : 16;
if (distance >= min_dist) {
ufbxi_copy_16_bytes(dst, src);
while (length > 16) {
src += 16;
dst += 16;
length -= 16;
ufbxi_copy_16_bytes(dst, src);
}
} else {
while (dst != end) {
*dst++ = *src++;
}
}
} else {
while (dst != end) {
*dst++ = *src++;
}
}
}
dc->out_ptr = out_ptr;
dc->stream.bits = bits;
dc->stream.left = left;
dc->stream.chunk_ptr = data;
return 1;
}
ufbx_static_assert(inflate_huff_fast_bits, UFBXI_HUFF_FAST_BITS <= 11); // `fast lit, fast len, slow dist` in 56 bits
ufbx_static_assert(inflate_huff_long_bits, UFBXI_HUFF_FAST_BITS + UFBXI_HUFF_MAX_LONG_BITS >= 15); // Largest code fits in a single long lookup
// Optimized version of `ufbxi_inflate_block_slow()`.
// Has a lot of assumptions (see asserts) and does not call _any_ (even forceinlined) functions.
static ufbxi_noinline int
ufbxi_inflate_block_fast(ufbxi_deflate_context *dc, ufbxi_trees *trees)
{
ufbxi_dev_assert(!dc->stream.cancelled);
ufbxi_dev_assert(trees->fast_bits == UFBXI_HUFF_FAST_BITS);
ufbxi_dev_assert(dc->stream.chunk_yield - dc->stream.chunk_ptr >= UFBXI_INFLATE_FAST_MIN_IN);
ufbxi_dev_assert(dc->out_end - dc->out_ptr >= UFBXI_INFLATE_FAST_MIN_OUT);
char *out_ptr = dc->out_ptr;
char *const out_begin = dc->out_begin;
char *const out_end = dc->out_end - UFBXI_INFLATE_FAST_MIN_OUT;
const ufbxi_huff_tree *tree_lit_length = &trees->lit_length;
const ufbxi_huff_tree *tree_dist = &trees->dist;
uint64_t bits = dc->stream.bits;
size_t left = dc->stream.left;
const char *data = dc->stream.chunk_ptr;
const char *data_end = dc->stream.chunk_yield - UFBXI_INFLATE_FAST_MIN_IN;
uint64_t sym01_bits;
ufbxi_huff_sym sym0, sym1;
uint64_t refill_bits = ufbxi_read_u64(data);
#define ufbxi_fast_inflate_refill_and_decode() do { \
ufbxi_macro_bit_refill_fast(bits, left, data, refill_bits); \
sym01_bits = bits; \
sym0 = tree_lit_length->fast_sym[sym01_bits & UFBXI_HUFF_FAST_MASK]; \
sym1 = ((sym0 & UFBXI_HUFF_SYM_MATCH) ? tree_dist : tree_lit_length)->fast_sym[ufbxi_wrap_shr64(sym01_bits, sym0) & UFBXI_HUFF_FAST_MASK]; \
refill_bits = ufbxi_read_u64(data); \
} while (0)
#define ufbxi_fast_inflate_should_continue() \
(((data_end - data) | (out_end - out_ptr)) >= 0)
ufbxi_fast_inflate_refill_and_decode();
for (;;) {
if ((sym0 & sym1) & UFBXI_HUFF_SYM_FAST) {
bits = ufbxi_wrap_shr64(sym01_bits, sym0 + sym1);
left -= (sym0 + sym1) & 0x3f;
if (((sym0 | sym1) & UFBXI_HUFF_SYM_MATCH) == 0) {
// Literal, Literal
// -> Output the two literals and loop back to start.
out_ptr[0] = (char)ufbxi_huff_sym_value(sym0);
out_ptr[1] = (char)ufbxi_huff_sym_value(sym1);
out_ptr += 2;
ufbxi_fast_inflate_refill_and_decode();
if (ufbxi_fast_inflate_should_continue()) continue;
break;
} else if ((sym0 & UFBXI_HUFF_SYM_MATCH) == 0) {
// Literal, Match, (Distance)
// -> Output a single literal, decode the missing distance and fall through to match.
out_ptr[0] = (char)ufbxi_huff_sym_value(sym0);
out_ptr += 1;
sym01_bits = ufbxi_wrap_shr64(sym01_bits, sym0);
// This must fit as literals never have extra bits and the match length is fast so:
// 10 (lit) + 10 (len code) + 5 (len extra) + 15 (dist code) + 13 (dist extra) = 53 <= 56
sym0 = sym1;
sym1 = tree_dist->fast_sym[bits & UFBXI_HUFF_FAST_MASK];
if ((sym1 & UFBXI_HUFF_SYM_FAST) == 0) {
// Slow sym1
if (sym1 & UFBXI_HUFF_SYM_END) return -11;
uint32_t tail = (uint32_t)(bits >> UFBXI_HUFF_FAST_BITS);
uint32_t long_mask = ufbxi_huff_sym_long_mask(sym1);
sym1 = tree_dist->long_sym[ufbxi_huff_sym_long_offset(sym1) + (tail & long_mask)];
if (sym1 & UFBXI_HUFF_SYM_END) return -11;
}
bits = ufbxi_wrap_shr64(bits, sym1);
left -= sym1 & 0x3f;
} else {
// Match, Distance
// -> Fall through to match copy.
}
} else {
if ((sym0 & (UFBXI_HUFF_SYM_FAST|UFBXI_HUFF_SYM_END)) == 0) {
// Slow sym0
uint32_t tail = (uint32_t)(sym01_bits >> UFBXI_HUFF_FAST_BITS);
uint32_t long_mask = ufbxi_huff_sym_long_mask(sym0);
sym0 = tree_lit_length->long_sym[ufbxi_huff_sym_long_offset(sym0) + (tail & long_mask)];
}
uint32_t sym0_bits = ufbxi_huff_sym_total_bits(sym0);
bits >>= sym0_bits;
left -= sym0_bits;
if (sym0 & UFBXI_HUFF_SYM_END) {
if (ufbxi_huff_sym_value(sym0) != 0) return -13;
dc->out_ptr = out_ptr;
dc->stream.bits = bits;
dc->stream.left = left;
dc->stream.chunk_ptr = data;
return 0;
}
if (sym0 & UFBXI_HUFF_SYM_MATCH) {
sym1 = tree_dist->fast_sym[bits & UFBXI_HUFF_FAST_MASK];
if ((sym1 & UFBXI_HUFF_SYM_FAST) == 0) {
// Slow sym1
if (sym1 & UFBXI_HUFF_SYM_END) return -11;
uint32_t tail = (uint32_t)(bits >> UFBXI_HUFF_FAST_BITS);
uint32_t long_mask = ufbxi_huff_sym_long_mask(sym1);
sym1 = tree_dist->long_sym[ufbxi_huff_sym_long_offset(sym1) + (tail & long_mask)];
if (sym1 & UFBXI_HUFF_SYM_END) return -11;
}
bits = ufbxi_wrap_shr64(bits, sym1);
left -= sym1 & 0x3f;
} else {
*out_ptr++ = (char)ufbxi_huff_sym_value(sym0);
ufbxi_fast_inflate_refill_and_decode();
if (ufbxi_fast_inflate_should_continue()) continue;
break;
}
}
uint32_t sym0_value = ufbxi_huff_sym_value(sym0);
uint32_t len_shift_base = trees->lit_length.extra_shift_base[sym0_value];
uint16_t len_mask = trees->lit_length.extra_mask[sym0_value];
uint32_t length = (len_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym01_bits, len_shift_base) & len_mask);
uint32_t sym1_value = ufbxi_huff_sym_value(sym1);
uint32_t dist_shift_base = trees->dist.extra_shift_base[sym1_value];
uint16_t dist_mask = trees->dist.extra_mask[sym1_value];
uint32_t distance = (dist_shift_base >> 16) + (uint32_t)(ufbxi_wrap_shr64(sym01_bits, dist_shift_base + sym0) & dist_mask);
ufbxi_fast_inflate_refill_and_decode();
// Bounds checking: We don't actually handle the error here, just bail out to the slow implementation
ptrdiff_t dst_space = out_end - out_ptr - (ptrdiff_t)length + UFBXI_INFLATE_FAST_MIN_OUT;
ptrdiff_t src_space = out_ptr - out_begin - (ptrdiff_t)distance;
if ((dst_space | src_space) < 0) {
return -12;
}
const char *src = out_ptr - distance;
char *dst = out_ptr;
char *end = dst + length;
out_ptr += length;
// Copy the match
uint32_t min_dist = length < 16 ? length : 16;
if (distance >= min_dist && dst_space >= 16) {
ufbxi_copy_16_bytes(dst, src);
while (length > 16) {
src += 16;
dst += 16;
length -= 16;
ufbxi_copy_16_bytes(dst, src);
}
} else {
while (dst != end) {
*dst++ = *src++;
}
}
if (ufbxi_fast_inflate_should_continue()) continue;
break;
}
dc->out_ptr = out_ptr;
dc->stream.bits = bits;
dc->stream.left = left;
dc->stream.chunk_ptr = data;
return 1;
#undef ufbxi_fast_inflate_refill_and_decode
#undef ufbxi_fast_inflate_should_continue
}
static void ufbxi_inflate_init_retain(ufbx_inflate_retain *retain)
{
ufbxi_inflate_retain_imp *ret_imp = (ufbxi_inflate_retain_imp*)retain;
if (!ret_imp->initialized) {
ufbxi_init_static_huff(&ret_imp->static_trees, NULL);
ret_imp->initialized = true;
}
}
// TODO: Error codes should have a quick test if the destination buffer overflowed
// Returns actual number of decompressed bytes or negative error:
// -1: Bad compression method (ZLIB header)
// -2: Requires dictionary (ZLIB header)
// -3: Bad FCHECK (ZLIB header)
// -4: Bad NLEN (Uncompressed LEN != ~NLEN)
// -5: Uncompressed source overflow
// -6: Uncompressed destination overflow
// -7: Bad block type
// -8: Truncated checksum (deprecated, reported as -9)
// -9: Checksum mismatch
// -10: Literal destination overflow
// -11: Bad distance code or distance of (30..31)
// -12: Match out of bounds
// -13: Bad lit/length code
// -14: Codelen Huffman Overfull
// -15: Codelen Huffman Underfull
// -16 - -21: Litlen Huffman: Overfull / Underfull / Repeat 16/17/18 overflow / Bad length code
// -22 - -27: Distance Huffman: Overfull / Underfull / Repeat 16/17/18 overflow / Bad length code
// -28: Cancelled
// -29: Invalid ufbx_inflate_input.internal_fast_bits value
ufbxi_extern_c ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inflate_input *input, ufbx_inflate_retain *retain)
{
ufbxi_inflate_retain_imp *ret_imp = (ufbxi_inflate_retain_imp*)retain;
ptrdiff_t err;
ufbxi_deflate_context dc;
ufbxi_bit_stream_init(&dc.stream, input);
dc.out_begin = (char*)dst;
dc.out_ptr = (char*)dst;
dc.out_end = (char*)dst + dst_size;
if (input->internal_fast_bits != 0) {
dc.fast_bits = (uint32_t)input->internal_fast_bits;
if (dc.fast_bits < 1 || dc.fast_bits == 9 || dc.fast_bits > 10) return -29;
} else {
// TODO: Profile this
dc.fast_bits = input->total_size > 2048 ? 10 : 8;
}
uint64_t bits = dc.stream.bits;
size_t left = dc.stream.left;
const char *data = dc.stream.chunk_ptr;
ufbxi_bit_refill(&bits, &left, &data, &dc.stream);
if (dc.stream.cancelled) return -28;
// Zlib header
if (!input->no_header) {
size_t cmf = (size_t)(bits & 0xff);
size_t flg = (size_t)(bits >> 8) & 0xff;
bits >>= 16;
left -= 16;
if ((cmf & 0xf) != 0x8) return -1;
if ((flg & 0x20) != 0) return -2;
if ((cmf << 8 | flg) % 31u != 0) return -3;
}
for (;;) {
ufbxi_bit_refill(&bits, &left, &data, &dc.stream);
if (dc.stream.cancelled) return -28;
// Block header: [0:1] BFINAL [1:3] BTYPE
size_t header = (size_t)bits & 0x7;
bits >>= 3;
left -= 3;
size_t type = header >> 1;
if (type == 0) {
// Round up to the next byte
size_t align_bits = left & 0x7;
bits >>= align_bits;
left -= align_bits;
size_t len = (size_t)(bits & 0xffff);
size_t nlen = (size_t)((bits >> 16) & 0xffff);
if ((len ^ nlen) != 0xffff) return -4;
if (dc.out_end - dc.out_ptr < (ptrdiff_t)len) return -6;
bits >>= 32;
left -= 32;
dc.stream.bits = bits;
dc.stream.left = left;
dc.stream.chunk_ptr = data;
// Copy `len` bytes of literal data
if (!ufbxi_bit_copy_bytes(dc.out_ptr, &dc.stream, len)) return -5;
dc.out_ptr += len;
} else if (type <= 2) {
dc.stream.bits = bits;
dc.stream.left = left;
dc.stream.chunk_ptr = data;
ufbxi_trees tree_data; // ufbxi_uninit
ufbxi_trees *trees; // ufbxi_uninit
if (type == 1) {
// Static Huffman: Initialize the trees once and cache them in `retain`.
if (!ret_imp->initialized) {
ufbxi_init_static_huff(&ret_imp->static_trees, input);
ret_imp->initialized = true;
}
trees = &ret_imp->static_trees;
} else {
// Dynamic Huffman
err = ufbxi_init_dynamic_huff(&dc, &tree_data);
if (err) return err;
trees = &tree_data;
}
for (;;) {
bool fast_viable = trees->fast_bits == UFBXI_HUFF_FAST_BITS && dc.out_end - dc.out_ptr >= UFBXI_INFLATE_FAST_MIN_OUT;
// `ufbxi_inflate_block_fast()` needs a bit more upfront setup, see asserts on top of the function
if (fast_viable && dc.stream.chunk_yield - dc.stream.chunk_ptr >= UFBXI_INFLATE_FAST_MIN_IN) {
err = ufbxi_inflate_block_fast(&dc, trees);
} else {
err = ufbxi_inflate_block_slow(&dc, trees, fast_viable ? 32 : SIZE_MAX);
}
if (err < 0) return err;
// `ufbxi_inflate_block()` returns normally on cancel so check it here
if (dc.stream.cancelled) return -28;
if (err == 0) break;
}
} else {
// 0b11 - reserved (error)
return -7;
}
bits = dc.stream.bits;
left = dc.stream.left;
data = dc.stream.chunk_ptr;
// BFINAL: End of stream
if (header & 1) break;
}
// Check Adler-32
{
// Round up to the next byte
size_t align_bits = left & 0x7;
bits >>= align_bits;
left -= align_bits;
ufbxi_bit_refill(&bits, &left, &data, &dc.stream);
if (dc.stream.cancelled) return -28;
if (!input->no_checksum) {
uint32_t ref = (uint32_t)bits;
ref = (ref>>24) | ((ref>>8)&0xff00) | ((ref<<8)&0xff0000) | (ref<<24);
uint32_t checksum = ufbxi_adler32(dc.out_begin, ufbxi_to_size(dc.out_ptr - dc.out_begin));
if (ref != checksum) {
return -9;
}
}
}
return dc.out_ptr - dc.out_begin;
}
#endif // !defined(ufbx_inflate)
// -- Errors
static const char ufbxi_empty_char[1] = { '\0' };
static ufbxi_noinline int ufbxi_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list args)
{
int result = vsnprintf(buf, buf_size, fmt, args);
if (result < 0) result = 0;
if ((size_t)result >= buf_size - 1) result = (int)buf_size - 1;
// HACK: On some MSYS/MinGW implementations `vsnprintf` is broken and does
// not write the null terminator on truncation, it's always safe to do so
// let's just do it unconditionally here...
buf[result] = '\0';
return result;
}
static ufbxi_noinline int ufbxi_snprintf(char *buf, size_t buf_size, const char *fmt, ...)
{
va_list args; // ufbxi_uninit
va_start(args, fmt);
int result = ufbxi_vsnprintf(buf, buf_size, fmt, args);
va_end(args);
return result;
}
static ufbxi_noinline void ufbxi_panicf_imp(ufbx_panic *panic, const char *fmt, ...)
{
if (panic && panic->did_panic) return;
va_list args; // ufbxi_uninit
va_start(args, fmt);
if (panic) {
panic->did_panic = true;
panic->message_length = (size_t)ufbxi_vsnprintf(panic->message, sizeof(panic->message), fmt, args);
} else {
fprintf(stderr, "ufbx panic: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
}
va_end(args);
if (!panic) {
ufbx_assert(false && "ufbx panic: See stderr for more information");
}
}
#define ufbxi_panicf(panic, cond, ...) \
((cond) ? false : (ufbxi_panicf_imp((panic), __VA_ARGS__), true))
// Prefix the error condition with $Description\0 for a human readable description
#define ufbxi_error_msg(cond, msg) "$" msg "\0" cond
static ufbxi_noinline int ufbxi_fail_imp_err(ufbx_error *err, const char *cond, const char *func, uint32_t line)
{
if (cond[0] == '$') {
if (!err->description.data) {
err->description.data = cond + 1;
err->description.length = strlen(err->description.data);
}
#if UFBXI_FEATURE_ERROR_STACK
// Skip the description part if adding to a stack
cond = cond + strlen(cond) + 1;
#endif
}
// NOTE: This is the base function all fails boil down to, place a breakpoint here to
// break at the first error
#if UFBXI_FEATURE_ERROR_STACK
if (err->stack_size < UFBX_ERROR_STACK_MAX_DEPTH) {
ufbx_error_frame *frame = &err->stack[err->stack_size++];
frame->description.data = cond;
frame->description.length = strlen(cond);
frame->function.data = func;
frame->function.length = strlen(func);
frame->source_line = line;
}
#else
ufbxi_ignore(func);
ufbxi_ignore(line);
#endif
return 0;
}
ufbxi_nodiscard static ufbxi_noinline size_t ufbxi_utf8_valid_length(const char *str, size_t length)
{
size_t index = 0;
while (index < length) {
uint8_t c = (uint8_t)str[index];
size_t left = length - index;
if ((c & 0x80) == 0) {
if (c != 0) {
index += 1;
continue;
}
} else if ((c & 0xe0) == 0xc0 && left >= 2) {
uint8_t t0 = (uint8_t)str[index + 1];
uint32_t code = (uint32_t)c << 8 | (uint32_t)t0;
if ((code & 0xc0) == 0x80 && code >= 0xc280) {
index += 2;
continue;
}
} else if ((c & 0xf0) == 0xe0 && left >= 3) {
uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2];
uint32_t code = (uint32_t)c << 16 | (uint32_t)t0 << 8 | (uint32_t)t1;
if ((code & 0xc0c0) == 0x8080 && code >= 0xe0a080 && (code < 0xeda080 || code >= 0xee8080)) {
index += 3;
continue;
}
} else if ((c & 0xf8) == 0xf0 && left >= 4) {
uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2], t2 = (uint8_t)str[index + 3];
uint32_t code = (uint32_t)c << 24 | (uint32_t)t0 << 16 | (uint32_t)t1 << 8 | (uint32_t)t2;
if ((code & 0xc0c0c0) == 0x808080 && code >= 0xf0908080u && code <= 0xf48fbfbfu) {
index += 4;
continue;
}
}
break;
}
ufbx_assert(index <= length);
return index;
}
static ufbxi_noinline void ufbxi_clean_string_utf8(char *str, size_t length)
{
size_t pos = 0;
for (;;) {
pos += ufbxi_utf8_valid_length(str + pos, length);
if (pos == length) break;
str[pos++] = '?';
}
}
static ufbxi_noinline void ufbxi_set_err_info(ufbx_error *err, const char *data, size_t length)
{
if (!err) return;
if (length == SIZE_MAX) length = strlen(data);
size_t to_copy = ufbxi_min_sz(sizeof(err->info) - 1, length);
memcpy(err->info, data, to_copy);
err->info[to_copy] = '\0';
err->info_length = to_copy;
ufbxi_clean_string_utf8(err->info, err->info_length);
}
static ufbxi_noinline void ufbxi_fmt_err_info(ufbx_error *err, const char *fmt, ...)
{
if (!err) return;
va_list args; // ufbxi_uninit
va_start(args, fmt);
err->info_length = (size_t)ufbxi_vsnprintf(err->info, sizeof(err->info), fmt, args);
va_end(args);
ufbxi_clean_string_utf8(err->info, err->info_length);
}
static ufbxi_noinline void ufbxi_clear_error(ufbx_error *err)
{
if (!err) return;
err->type = UFBX_ERROR_NONE;
err->description.data = ufbxi_empty_char;
err->description.length = 0;
err->stack_size = 0;
err->info[0] = '\0';
err->info_length = 0;
}
#if UFBXI_FEATURE_ERROR_STACK
#define ufbxi_function __FUNCTION__
#define ufbxi_line __LINE__
#define ufbxi_cond_str(cond) #cond
#else
#define ufbxi_function ""
#define ufbxi_line 0
#define ufbxi_cond_str(cond) ""
#endif
#define ufbxi_check_err(err, cond) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return 0; } } while (0)
#define ufbxi_check_return_err(err, cond, ret) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return ret; } } while (0)
#define ufbxi_fail_err(err, desc) return ufbxi_fail_imp_err(err, desc, ufbxi_function, ufbxi_line)
#define ufbxi_check_err_msg(err, cond, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return 0; } } while (0)
#define ufbxi_check_return_err_msg(err, cond, ret, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp_err((err), ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return ret; } } while (0)
#define ufbxi_fail_err_msg(err, desc, msg) return ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line)
#define ufbxi_report_err_msg(err, desc, msg) (void)ufbxi_fail_imp_err(err, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line)
static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *default_desc)
{
const char *desc = error->description.data;
if (!desc) desc = default_desc;
error->type = UFBX_ERROR_UNKNOWN;
if (!strcmp(desc, "Out of memory")) {
error->type = UFBX_ERROR_OUT_OF_MEMORY;
} else if (!strcmp(desc, "Memory limit exceeded")) {
error->type = UFBX_ERROR_MEMORY_LIMIT;
} else if (!strcmp(desc, "Allocation limit exceeded")) {
error->type = UFBX_ERROR_ALLOCATION_LIMIT;
} else if (!strcmp(desc, "Truncated file")) {
error->type = UFBX_ERROR_TRUNCATED_FILE;
} else if (!strcmp(desc, "IO error")) {
error->type = UFBX_ERROR_IO;
} else if (!strcmp(desc, "Cancelled")) {
error->type = UFBX_ERROR_CANCELLED;
} else if (!strcmp(desc, "Unrecognized file format")) {
error->type = UFBX_ERROR_UNRECOGNIZED_FILE_FORMAT;
} else if (!strcmp(desc, "File not found")) {
error->type = UFBX_ERROR_FILE_NOT_FOUND;
} else if (!strcmp(desc, "Empty file")) {
error->type = UFBX_ERROR_EMPTY_FILE;
} else if (!strcmp(desc, "External file not found")) {
error->type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND;
} else if (!strcmp(desc, "Uninitialized options")) {
error->type = UFBX_ERROR_UNINITIALIZED_OPTIONS;
} else if (!strcmp(desc, "Zero vertex size")) {
error->type = UFBX_ERROR_ZERO_VERTEX_SIZE;
} else if (!strcmp(desc, "Truncated vertex stream")) {
error->type = UFBX_ERROR_TRUNCATED_VERTEX_STREAM;
} else if (!strcmp(desc, "Invalid UTF-8")) {
error->type = UFBX_ERROR_INVALID_UTF8;
} else if (!strcmp(desc, "Feature disabled")) {
error->type = UFBX_ERROR_FEATURE_DISABLED;
} else if (!strcmp(desc, "Bad NURBS geometry")) {
error->type = UFBX_ERROR_BAD_NURBS;
} else if (!strcmp(desc, "Bad index")) {
error->type = UFBX_ERROR_BAD_INDEX;
} else if (!strcmp(desc, "Node depth limit exceeded")) {
error->type = UFBX_ERROR_NODE_DEPTH_LIMIT;
} else if (!strcmp(desc, "Threaded ASCII parse error")) {
error->type = UFBX_ERROR_THREADED_ASCII_PARSE;
} else if (!strcmp(desc, "Unsafe options")) {
error->type = UFBX_ERROR_UNSAFE_OPTIONS;
} else if (!strcmp(desc, "Duplicate override")) {
error->type = UFBX_ERROR_DUPLICATE_OVERRIDE;
}
error->description.data = desc;
error->description.length = strlen(desc);
}
// -- Allocator
// Returned for zero size allocations, place in the constant data
// to catch writes to bad allocations.
#if defined(UFBX_REGRESSION)
static const char ufbxi_zero_size_buffer[4096] = { 0 };
#else
static const char ufbxi_zero_size_buffer[64] = { 0 };
#endif
static ufbxi_forceinline size_t ufbxi_align_to_mask(size_t value, size_t align_mask)
{
return value + (((size_t)0 - value) & align_mask);
}
static ufbxi_forceinline size_t ufbxi_size_align_mask(size_t size)
{
// Align to the all bits below the lowest set one in `size`
// up to a maximum of 0x7 (align to 8 bytes).
return ((size ^ (size - 1)) >> 1) & 0x7;
}
typedef struct {
ufbx_error *error;
size_t current_size;
size_t max_size;
size_t num_allocs;
size_t max_allocs;
size_t huge_size;
size_t chunk_max;
ufbx_allocator_opts ator;
const char *name;
} ufbxi_allocator;
static ufbxi_forceinline bool ufbxi_does_overflow(size_t total, size_t a, size_t b)
{
// If `a` and `b` have at most 4 bits per `size_t` byte, the product can't overflow.
if (((a | b) >> sizeof(size_t)*4) != 0) {
if (a != 0 && total / a != b) return true;
}
return false;
}
static ufbxi_noinline void *ufbxi_alloc_size(ufbxi_allocator *ator, size_t size, size_t n)
{
// Always succeed with an empty non-NULL buffer for empty allocations
ufbx_assert(size > 0);
if (n == 0) return (void*)ufbxi_zero_size_buffer;
size_t total = size * n;
ufbxi_check_return_err(ator->error, !ufbxi_does_overflow(total, size, n), NULL);
ufbxi_check_return_err(ator->error, total <= SIZE_MAX / 2, NULL); // Make sure it's always safe to double allocations
if (!(total < ator->max_size - ator->current_size)) {
ufbxi_report_err_msg(ator->error, "total <= ator->max_size - ator->current_size", "Memory limit exceeded");
ufbxi_fmt_err_info(ator->error, "%s", ator->name);
return NULL;
}
if (!(ator->num_allocs < ator->max_allocs)) {
ufbxi_report_err_msg(ator->error, "ator->num_allocs < ator->max_allocs", "Allocation limit exceeded");
ufbxi_fmt_err_info(ator->error, "%s", ator->name);
return NULL;
}
ator->num_allocs++;
void *ptr;
if (ator->ator.allocator.alloc_fn) {
ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total);
} else if (ator->ator.allocator.realloc_fn) {
ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, NULL, 0, total);
} else {
ptr = malloc(total);
}
if (!ptr) {
ufbxi_report_err_msg(ator->error, "ptr", "Out of memory");
ufbxi_fmt_err_info(ator->error, "%s", ator->name);
return NULL;
}
ufbx_assert(((uintptr_t)ptr & ufbxi_size_align_mask(total)) == 0);
ator->current_size += total;
return ptr;
}
static void ufbxi_free_size(ufbxi_allocator *ator, size_t size, void *ptr, size_t n);
static ufbxi_noinline void *ufbxi_realloc_size(ufbxi_allocator *ator, size_t size, void *old_ptr, size_t old_n, size_t n)
{
ufbx_assert(size > 0);
// realloc() with zero old/new size is equivalent to alloc()/free()
if (old_n == 0) return ufbxi_alloc_size(ator, size, n);
if (n == 0) { ufbxi_free_size(ator, size, old_ptr, old_n); return NULL; }
size_t old_total = size * old_n;
size_t total = size * n;
// The old values have been checked by a previous allocate call
ufbx_assert(!ufbxi_does_overflow(old_total, size, old_n));
ufbx_assert(old_total <= ator->current_size);
ufbxi_check_return_err(ator->error, !ufbxi_does_overflow(total, size, n), NULL);
ufbxi_check_return_err(ator->error, total <= SIZE_MAX / 2, NULL); // Make sure it's always safe to double allocations
ufbxi_check_return_err_msg(ator->error, total <= ator->max_size - ator->current_size, NULL, "Memory limit exceeded");
ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded");
ator->num_allocs++;
void *ptr;
if (ator->ator.allocator.realloc_fn) {
ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, old_ptr, old_total, total);
} else if (ator->ator.allocator.alloc_fn) {
// Use user-provided alloc_fn() and free_fn()
ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total);
if (ptr) memcpy(ptr, old_ptr, old_total);
if (ator->ator.allocator.free_fn) {
ator->ator.allocator.free_fn(ator->ator.allocator.user, old_ptr, old_total);
}
} else {
ptr = realloc(old_ptr, total);
}
ufbxi_check_return_err_msg(ator->error, ptr, NULL, "Out of memory");
ufbx_assert(((uintptr_t)ptr & ufbxi_size_align_mask(total)) == 0);
ator->current_size += total;
ator->current_size -= old_total;
return ptr;
}
static ufbxi_noinline void ufbxi_free_size(ufbxi_allocator *ator, size_t size, void *ptr, size_t n)
{
ufbx_assert(size > 0);
if (n == 0) return;
ufbx_assert(ptr);
size_t total = size * n;
// The old values have been checked by a previous allocate call
ufbx_assert(!ufbxi_does_overflow(total, size, n));
ufbx_assert(total <= ator->current_size);
ator->current_size -= total;
if (ator->ator.allocator.alloc_fn || ator->ator.allocator.realloc_fn) {
// Don't call default free() if there is an user-provided `alloc_fn()`
if (ator->ator.allocator.free_fn) {
ator->ator.allocator.free_fn(ator->ator.allocator.user, ptr, total);
} else if (ator->ator.allocator.realloc_fn) {
ator->ator.allocator.realloc_fn(ator->ator.allocator.user, ptr, total, 0);
}
} else {
free(ptr);
}
}
ufbxi_noinline ufbxi_nodiscard static bool ufbxi_grow_array_size(ufbxi_allocator *ator, size_t size, void *p_ptr, size_t *p_cap, size_t n)
{
#if defined(UFBX_REGRESSION)
{
ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, false, "Allocation limit exceeded");
ator->num_allocs++;
}
#endif
if (n <= *p_cap) return true;
void *ptr = *(void**)p_ptr;
size_t old_n = *p_cap;
if (old_n >= n) return true;
size_t new_n = ufbxi_max_sz(old_n * 2, n);
void *new_ptr = ufbxi_realloc_size(ator, size, ptr, old_n, new_n);
if (!new_ptr) return false;
*(void**)p_ptr = new_ptr;
*p_cap = new_n;
return true;
}
static ufbxi_noinline void ufbxi_free_ator(ufbxi_allocator *ator)
{
ufbx_assert(ator->current_size == 0);
ufbx_free_allocator_fn *free_fn = ator->ator.allocator.free_allocator_fn;
if (free_fn) {
void *user = ator->ator.allocator.user;
free_fn(user);
}
}
#define ufbxi_alloc(ator, type, n) ufbxi_maybe_null((type*)ufbxi_alloc_size((ator), sizeof(type), (n)))
#define ufbxi_alloc_zero(ator, type, n) ufbxi_maybe_null((type*)ufbxi_alloc_zero_size((ator), sizeof(type), (n)))
#define ufbxi_realloc(ator, type, old_ptr, old_n, n) ufbxi_maybe_null((type*)ufbxi_realloc_size((ator), sizeof(type), (old_ptr), (old_n), (n)))
#define ufbxi_realloc_zero(ator, type, old_ptr, old_n, n) ufbxi_maybe_null((type*)ufbxi_realloc_zero_size((ator), sizeof(type), (old_ptr), (old_n), (n)))
#define ufbxi_free(ator, type, ptr, n) ufbxi_free_size((ator), sizeof(type), (ptr), (n))
#define ufbxi_grow_array(ator, p_ptr, p_cap, n) ufbxi_grow_array_size((ator), sizeof(**(p_ptr)), (p_ptr), (p_cap), (n))
#define UFBXI_SCENE_IMP_MAGIC 0x58424655
#define UFBXI_MESH_IMP_MAGIC 0x48534d55
#define UFBXI_LINE_CURVE_IMP_MAGIC 0x55434c55
#define UFBXI_CACHE_IMP_MAGIC 0x48434355
#define UFBXI_ANIM_IMP_MAGIC 0x494e4155
#define UFBXI_BAKED_ANIM_IMP_MAGIC 0x4b414255
#define UFBXI_REFCOUNT_IMP_MAGIC 0x46455255
#define UFBXI_BUF_CHUNK_IMP_MAGIC 0x46554255
// -- Memory buffer
//
// General purpose memory buffer that can be used either as a chunked linear memory
// allocator or a non-contiguous stack. You can convert the contents of `ufbxi_buf`
// to a contiguous range of memory by calling `ufbxi_make_array[_all]()`
typedef struct ufbxi_buf_padding ufbxi_buf_padding;
typedef struct ufbxi_buf_chunk ufbxi_buf_chunk;
struct ufbxi_buf_padding {
size_t original_pos; // < Original position before aligning
size_t prev_padding; // < Starting offset of the previous `ufbxi_buf_padding`
};
struct ufbxi_buf_chunk {
// Linked list of nodes
ufbxi_buf_chunk *root;
ufbxi_buf_chunk *prev;
ufbxi_buf_chunk *next;
union {
size_t magic; // < Magic for debugging
void *align_0; // < Align to 4x pointer size (16/32 bytes)
};
size_t size; // < Size of the chunk `data`, excluding this header
size_t pushed_pos; // < Size of valid data when pushed to the list
size_t next_size; // < Next geometrically growing chunk size to allocate
size_t padding_pos; // < One past the offset of the most recent `ufbxi_buf_padding`
char data[]; // < Must be aligned to 8 bytes
};
ufbx_static_assert(buf_chunk_align, offsetof(ufbxi_buf_chunk, data) % 8 == 0);
typedef struct {
ufbxi_allocator *ator;
// Current chunks for normal and huge allocations.
// Ordered buffers (`!ufbxi_buf.unordered`) never use `chunks[1]`
ufbxi_buf_chunk *chunks[2];
// Inline state for non-huge chunks
size_t pos; // < Next offset to allocate from
size_t size; // < Size of the current chunk ie. `chunks[0]->size` (or 0 if `chunks[0] == NULL`)
size_t num_items; // < Number of individual items pushed to the buffer
size_t pushed_size; // < Cumulative size of pushed chunks, not tracked across pops
bool unordered; // < Does not support popping from the buffer
bool clearable; // < Supports clearing the whole buffer even if `unordered`
} ufbxi_buf;
typedef struct {
ufbxi_buf_chunk *chunk;
size_t pos;
size_t num_items;
} ufbxi_buf_state;
static ufbxi_noinline void *ufbxi_push_size_new_block(ufbxi_buf *b, size_t size)
{
bool huge = size >= b->ator->huge_size;
// Use the second chunk "list" for huge unordered chunks.
// The state of these chunks is not tracked by `ufbxi_buf.pos/size`.
uint32_t list_ix = ((uint32_t)b->unordered & (uint32_t)huge);
ufbxi_buf_chunk *chunk = b->chunks[list_ix];
if (chunk) {
if (list_ix == 0) {
// Store the final position for the retired chunk and scan free
// chunks in case we find one the allocation fits in.
b->pushed_size += b->pos;
chunk->pushed_pos = b->pos;
ufbxi_buf_chunk *next = chunk->next;
while (next != NULL) {
ufbx_assert(next->magic == UFBXI_BUF_CHUNK_IMP_MAGIC);
chunk = next;
ufbx_assert(b->unordered || chunk->pushed_pos == 0);
chunk->pushed_pos = 0;
if (size <= chunk->size) {
b->chunks[0] = chunk;
b->pos = (uint32_t)size;
b->size = chunk->size;
return chunk->data;
}
next = chunk->next;
}
} else if (b->clearable) {
// Keep track of the `UFBXI_HUGE_MAX_SCAN` largest chunks and
// retain them. Overflowing chunks are freed in `ufbxi_buf_clear()`
size_t align_mask = ufbxi_size_align_mask(size);
ufbxi_buf_chunk *next = chunk;
ufbxi_buf_chunk *best_chunk = NULL;
size_t best_space = SIZE_MAX;
// Clearable huge chunks are sorted by descending size. Check the first N
// chunks for reuse and find the place a new block should be inserted if
// no suitable space is found. Chunk ordering in the tail doesn't matter
// as those chunks are never reused.
// Unreachable chunks in the tail are freed in `ufbxi_buf_clear()`.
for (size_t i = 0; next && i < UFBXI_HUGE_MAX_SCAN; i++) {
ufbx_assert(next->magic == UFBXI_BUF_CHUNK_IMP_MAGIC);
if (next->size < size) break;
chunk = next;
// Try to reuse chunks using a best-fit strategy.
size_t pos = ufbxi_align_to_mask(chunk->pushed_pos, align_mask);
size_t space = chunk->size - pos;
if (size <= space) {
if (space < best_space) {
best_chunk = chunk;
best_space = space;
}
}
next = chunk->next;
}
// Early return if we found a slot.
if (best_chunk) {
size_t pos = ufbxi_align_to_mask(best_chunk->pushed_pos, align_mask);
best_chunk->pushed_pos = pos + size;
b->pushed_size += size;
return best_chunk->data + pos;
}
}
}
// Allocate a new chunk, grow `next_size` geometrically but don't double
// the current or previous user sizes if they are larger.
size_t chunk_size, next_size;
// If `size` is larger than `huge_size` don't grow `next_size` geometrically,
// but use a dedicated allocation.
if (huge) {
next_size = chunk ? chunk->next_size : 4096;
if (next_size > b->ator->chunk_max) next_size = b->ator->chunk_max;
chunk_size = size;
} else {
next_size = chunk ? chunk->next_size * 2 : 4096;
if (next_size > b->ator->chunk_max) next_size = b->ator->chunk_max;
chunk_size = next_size - sizeof(ufbxi_buf_chunk);
if (chunk_size < size) chunk_size = size;
}
// Align chunk sizes to 16 bytes
chunk_size = ufbxi_align_to_mask(chunk_size, 0xf);
ufbxi_buf_chunk *new_chunk = (ufbxi_buf_chunk*)ufbxi_alloc_size(b->ator, 1, sizeof(ufbxi_buf_chunk) + chunk_size);
if (!new_chunk) return NULL;
new_chunk->prev = chunk;
new_chunk->size = chunk_size;
new_chunk->next_size = next_size;
new_chunk->magic = UFBXI_BUF_CHUNK_IMP_MAGIC;
new_chunk->padding_pos = 0;
new_chunk->pushed_pos = 0;
// Link the chunk to the list and set it as the active one
if (chunk) {
ufbxi_buf_chunk *next = chunk->next;
if (next) next->prev = new_chunk;
new_chunk->next = next;
chunk->next = new_chunk;
new_chunk->root = chunk->root;
} else {
new_chunk->next = NULL;
new_chunk->root = new_chunk;
}
if (list_ix == 0) {
b->chunks[0] = new_chunk;
b->pos = size;
b->size = chunk_size;
} else {
ufbxi_buf_chunk *root = b->chunks[1];
b->pushed_size += size;
if (!root) {
b->chunks[1] = new_chunk;
} else if (root->size < chunk_size) {
// Swap root and self if necessary, we should have bailed out
// in the search loop in the first iteration so `new_chunk` should
// directly follow `root`.
// HACK: This ends up with `chunks[1]` entries having inconsistent
// `ufbxi_buf_chunk.root` pointers but other code only reads `chunks[1].root`
// TODO: Move roots out of the chunks?
ufbx_assert(root->next == new_chunk);
ufbx_assert(new_chunk->prev == root);
if (new_chunk->next) new_chunk->next->prev = root;
root->next = new_chunk->next;
new_chunk->next = root;
new_chunk->prev = NULL;
new_chunk->root = new_chunk;
b->chunks[1] = new_chunk;
}
new_chunk->pushed_pos = size;
}
return new_chunk->data;
}
static ufbxi_noinline void *ufbxi_push_size(ufbxi_buf *b, size_t size, size_t n)
{
// Always succeed with an empty non-NULL buffer for empty allocations
ufbx_assert(size > 0);
if (n == 0) return (void*)ufbxi_zero_size_buffer;
size_t total = size * n;
if (ufbxi_does_overflow(total, size, n)) return NULL;
#if defined(UFBX_REGRESSION)
{
ufbxi_allocator *ator = b->ator;
ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded");
ator->num_allocs++;
}
#endif
b->num_items += n;
// Align to the natural alignment based on the size
size_t align_mask = ufbxi_size_align_mask(size);
size_t pos = ufbxi_align_to_mask(b->pos, align_mask);
if (!b->unordered && pos != b->pos) {
// Alignment mismatch in an unordered block. Align to 16 bytes to guarantee
// sufficient alignment for anything afterwards and mark the padding.
// If we overflow the current block we don't need to care as the block
// boundaries are not contiguous.
pos = ufbxi_align_to_mask(b->pos, 0xf);
if (total < SIZE_MAX - 16 && total + 16 <= b->size - pos) {
ufbxi_buf_chunk *chunk = b->chunks[0];
ufbxi_buf_padding *padding = (ufbxi_buf_padding*)(chunk->data + pos);
padding->original_pos = b->pos;
padding->prev_padding = chunk->padding_pos;
chunk->padding_pos = pos + 16 + 1;
b->pos = pos + 16 + total;
return (char*)padding + 16;
} else {
return ufbxi_push_size_new_block(b, total);
}
} else {
// Try to push to the current block. Allocate a new block
// if the aligned size doesn't fit.
if (total <= b->size - pos) {
b->pos = pos + total;
return b->chunks[0]->data + pos;
} else {
return ufbxi_push_size_new_block(b, total);
}
}
}
static ufbxi_forceinline void *ufbxi_push_size_fast(ufbxi_buf *b, size_t size, size_t n)
{
// Always succeed with an empty non-NULL buffer for empty allocations
ufbxi_regression_assert(size > 0);
ufbxi_regression_assert(n > 0);
size_t total = size * n;
ufbxi_regression_assert(!ufbxi_does_overflow(total, size, n));
#if defined(UFBX_REGRESSION)
{
ufbxi_allocator *ator = b->ator;
ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded");
ator->num_allocs++;
}
#endif
b->num_items += n;
// Homogeneous arrays should always be aligned
size_t pos = b->pos;
ufbxi_regression_assert((pos & ufbxi_size_align_mask(size)) == 0);
// Try to push to the current block. Allocate a new block
// if the aligned size doesn't fit.
if (total <= b->size - pos) {
b->pos = pos + total;
return b->chunks[0]->data + pos;
} else {
return ufbxi_push_size_new_block(b, total);
}
}
static ufbxi_noinline void *ufbxi_push_size_zero(ufbxi_buf *b, size_t size, size_t n)
{
void *ptr = ufbxi_push_size(b, size, n);
if (ptr) memset(ptr, 0, size * n);
return ptr;
}
ufbxi_nodiscard static ufbxi_noinline void *ufbxi_push_size_copy(ufbxi_buf *b, size_t size, size_t n, const void *data)
{
// Always succeed with an empty non-NULL buffer for empty allocations, even if `data == NULL`
ufbx_assert(size > 0);
if (n == 0) return (void*)ufbxi_zero_size_buffer;
ufbx_assert(data);
void *ptr = ufbxi_push_size(b, size, n);
if (ptr) memcpy(ptr, data, size * n);
return ptr;
}
ufbxi_nodiscard static ufbxi_forceinline void *ufbxi_push_size_copy_fast(ufbxi_buf *b, size_t size, size_t n, const void *data)
{
// Always succeed with an empty non-NULL buffer for empty allocations, even if `data == NULL`
ufbx_assert(size > 0);
if (n == 0) return (void*)ufbxi_zero_size_buffer;
ufbx_assert(data);
void *ptr = ufbxi_push_size_fast(b, size, n);
if (ptr) memcpy(ptr, data, size * n);
return ptr;
}
static ufbxi_noinline void ufbxi_buf_free_unused(ufbxi_buf *b)
{
ufbx_assert(!b->unordered);
ufbxi_buf_chunk *chunk = b->chunks[0];
if (!chunk) return;
ufbxi_buf_chunk *next = chunk->next;
while (next) {
ufbxi_buf_chunk *to_free = next;
next = next->next;
ufbx_assert(to_free->magic == UFBXI_BUF_CHUNK_IMP_MAGIC);
to_free->magic = 0;
ufbxi_free_size(b->ator, 1, to_free, sizeof(ufbxi_buf_chunk) + to_free->size);
}
chunk->next = NULL;
while (b->pos == 0 && chunk) {
ufbxi_buf_chunk *prev = chunk->prev;
ufbx_assert(chunk->magic == UFBXI_BUF_CHUNK_IMP_MAGIC);
chunk->magic = 0;
ufbxi_free_size(b->ator, 1, chunk, sizeof(ufbxi_buf_chunk) + chunk->size);
chunk = prev;
b->chunks[0] = prev;
if (prev) {
prev->next = NULL;
b->pos = prev->pushed_pos;
b->size = prev->size;
} else {
b->pos = 0;
b->size = 0;
}
}
}
static ufbxi_noinline void ufbxi_pop_size(ufbxi_buf *b, size_t size, size_t n, void *dst, bool peek)
{
ufbx_assert(!b->unordered);
ufbx_assert(size > 0);
ufbx_assert(b->num_items >= n);
if (!peek) b->num_items -= n;
char *ptr = (char*)dst;
size_t bytes_left = size * n;
// We've already pushed this, it better not overflow
ufbx_assert(!ufbxi_does_overflow(bytes_left, size, n));
if (ptr) {
ptr += bytes_left;
size_t pos = b->pos;
ufbxi_buf_chunk *chunk = b->chunks[0];
for (;;) {
if (bytes_left <= pos) {
// Rest of the data is in this single chunk
pos -= bytes_left;
if (!peek) b->pos = pos;
ptr -= bytes_left;
if (bytes_left > 0) {
memcpy(ptr, chunk->data + pos, bytes_left);
}
break;
} else {
// Pop the whole chunk
ptr -= pos;
bytes_left -= pos;
memcpy(ptr, chunk->data, pos);
if (!peek) {
chunk->pushed_pos = 0;
chunk = chunk->prev;
b->chunks[0] = chunk;
b->size = chunk->size;
} else {
chunk = chunk->prev;
}
pos = chunk->pushed_pos;
}
}
} else {
size_t pos = b->pos;
ufbxi_buf_chunk *chunk = b->chunks[0];
for (;;) {
if (bytes_left <= pos) {
// Rest of the data is in this single chunk
pos -= bytes_left;
if (!peek) b->pos = pos;
break;
} else {
// Pop the whole chunk
bytes_left -= pos;
if (!peek) {
chunk->pushed_pos = 0;
chunk = chunk->prev;
b->chunks[0] = chunk;
b->size = chunk->size;
} else {
chunk = chunk->prev;
}
pos = chunk->pushed_pos;
}
}
}
if (!peek) {
// Check if we need to rewind past some alignment padding
ufbxi_buf_chunk *chunk = b->chunks[0];
if (chunk) {
size_t pos = b->pos, padding_pos = chunk->padding_pos;
if (pos < padding_pos) {
ufbx_assert(pos + 1 == padding_pos);
ufbxi_buf_padding *padding = (ufbxi_buf_padding*)(chunk->data + padding_pos - 1 - 16);
b->pos = padding->original_pos;
chunk->padding_pos = padding->prev_padding;
}
}
// Immediately free popped items if all the allocations are huge
// as it means we want to have dedicated allocations for each push.
if (b->ator->huge_size <= 1) {
ufbxi_buf_free_unused(b);
}
}
}
static ufbxi_noinline void *ufbxi_push_pop_size(ufbxi_buf *dst, ufbxi_buf *src, size_t size, size_t n)
{
void *data = ufbxi_push_size(dst, size, n);
if (!data) return NULL;
ufbxi_pop_size(src, size, n, data, false);
return data;
}
static ufbxi_noinline void *ufbxi_push_peek_size(ufbxi_buf *dst, ufbxi_buf *src, size_t size, size_t n)
{
void *data = ufbxi_push_size(dst, size, n);
if (!data) return NULL;
ufbxi_pop_size(src, size, n, data, true);
return data;
}
static ufbxi_noinline void ufbxi_buf_free(ufbxi_buf *buf)
{
ufbxi_nounroll for (size_t i = 0; i < 2; i++) {
ufbxi_buf_chunk *chunk = buf->chunks[i];
if (chunk) {
chunk = chunk->root;
while (chunk) {
ufbxi_buf_chunk *next = chunk->next;
ufbx_assert(chunk->magic == UFBXI_BUF_CHUNK_IMP_MAGIC);
chunk->magic = 0;
ufbxi_free_size(buf->ator, 1, chunk, sizeof(ufbxi_buf_chunk) + chunk->size);
chunk = next;
}
}
buf->chunks[i] = NULL;
}
buf->pos = 0;
buf->size = 0;
buf->num_items = 0;
}
static ufbxi_noinline void ufbxi_buf_clear(ufbxi_buf *buf)
{
// Only unordered or clearable buffers can be cleared
ufbx_assert(!buf->unordered || buf->clearable);
// Free the memory if using ASAN
if (buf->ator->huge_size <= 1) {
ufbxi_buf_free(buf);
return;
}
// Reset the non-huge chunks as `chunk->next` is always free.
ufbxi_buf_chunk *chunk = buf->chunks[0];
if (chunk) {
ufbxi_buf_chunk *root = chunk->root;
buf->chunks[0] = root;
buf->pos = 0;
buf->size = root->size;
}
buf->num_items = 0;
buf->pushed_size = 0;
// Huge chunks are always sorted by descending size and
// `chunks[1]` points to the largest one.
ufbxi_buf_chunk *huge = buf->chunks[1];
if (huge) {
// Reset the first N ones that are tracked.
for (size_t i = 0; huge && i < UFBXI_HUGE_MAX_SCAN; i++) {
huge->pushed_pos = 0;
huge = huge->next;
}
// Got unreachable tail that should be freed: Unlink from the last
// tracked chunk and free the rest.
if (huge) {
huge->prev->next = NULL;
while (huge) {
ufbxi_buf_chunk *next = huge->next;
ufbx_assert(huge->magic == UFBXI_BUF_CHUNK_IMP_MAGIC);
huge->magic = 0;
ufbxi_free_size(buf->ator, 1, huge, sizeof(ufbxi_buf_chunk) + huge->size);
huge = next;
}
}
}
}
#define ufbxi_push(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size((b), sizeof(type), (n)))
#define ufbxi_push_zero(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size_zero((b), sizeof(type), (n)))
#define ufbxi_push_copy(b, type, n, data) ufbxi_maybe_null((type*)ufbxi_push_size_copy((b), sizeof(type), (n), (data)))
#define ufbxi_push_copy_fast(b, type, n, data) ufbxi_maybe_null((type*)ufbxi_push_size_copy_fast((b), sizeof(type), (n), (data)))
#define ufbxi_push_fast(b, type, n) ufbxi_maybe_null((type*)ufbxi_push_size_fast((b), sizeof(type), (n)))
#define ufbxi_pop(b, type, n, dst) ufbxi_pop_size((b), sizeof(type), (n), (dst), false)
#define ufbxi_peek(b, type, n, dst) ufbxi_pop_size((b), sizeof(type), (n), (dst), true)
#define ufbxi_push_pop(dst, src, type, n) ufbxi_maybe_null((type*)ufbxi_push_pop_size((dst), (src), sizeof(type), (n)))
#define ufbxi_push_peek(dst, src, type, n) ufbxi_maybe_null((type*)ufbxi_push_peek_size((dst), (src), sizeof(type), (n)))
// -- Hash map
//
// The actual element comparison is left to the user of `ufbxi_map`, see usage below.
//
// NOTES:
// ufbxi_map_insert() does not support duplicate values, use find first if duplicates are possible!
// Inserting duplicate elements fails with an assertion if `UFBX_REGRESSION` is enabled.
typedef struct ufbxi_aa_node ufbxi_aa_node;
typedef int ufbxi_cmp_fn(void *user, const void *a, const void *b);
struct ufbxi_aa_node {
ufbxi_aa_node *left, *right;
uint32_t level;
uint32_t index;
};
typedef struct {
ufbxi_allocator *ator;
size_t data_size;
void *items;
uint64_t *entries;
uint32_t mask;
uint32_t capacity;
uint32_t size;
ufbxi_cmp_fn *cmp_fn;
void *cmp_user;
ufbxi_buf aa_buf;
ufbxi_aa_node *aa_root;
} ufbxi_map;
static ufbxi_noinline void ufbxi_map_init(ufbxi_map *map, ufbxi_allocator *ator, ufbxi_cmp_fn *cmp_fn, void *cmp_user)
{
map->ator = ator;
#if defined(UFBX_REGRESSION)
// HACK: Maps contain pointers that are not stable between runs, in regression
// mode this causes instability in allocation patterns due to different AA trees
// being built, which is a problem in fuzz checks that need to have deterministic
// allocation counts. We can work around this using a local allocator that doesn't
// count the allocations.
{
ufbxi_allocator *regression_ator = (ufbxi_allocator*)malloc(sizeof(ufbxi_allocator));
ufbx_assert(regression_ator);
memset(regression_ator, 0, sizeof(ufbxi_allocator));
regression_ator->name = "regression";
regression_ator->error = ator->error;
regression_ator->huge_size = ator->huge_size;
regression_ator->max_size = SIZE_MAX;
regression_ator->max_allocs = SIZE_MAX;
regression_ator->chunk_max = 0x1000000;
map->aa_buf.ator = regression_ator;
}
#else
map->aa_buf.ator = ator;
#endif
map->cmp_fn = cmp_fn;
map->cmp_user = cmp_user;
}
static ufbxi_noinline void ufbxi_map_free(ufbxi_map *map)
{
#if defined(UFBX_REGRESSION)
ufbxi_allocator *regression_ator = map->aa_buf.ator;
#endif
ufbxi_buf_free(&map->aa_buf);
ufbxi_free(map->ator, char, map->entries, map->data_size);
map->entries = NULL;
map->items = NULL;
map->aa_root = NULL;
map->mask = map->capacity = map->size = 0;
#if defined(UFBX_REGRESSION)
if (regression_ator) {
ufbxi_free_ator(regression_ator);
free(regression_ator);
}
#endif
}
// Recursion limit: log2(2^64 / sizeof(ufbxi_aa_node))
static ufbxi_noinline ufbxi_aa_node *ufbxi_aa_tree_insert(ufbxi_map *map, ufbxi_aa_node *node, const void *value, uint32_t index, size_t item_size)
ufbxi_recursive_function(ufbxi_aa_node *, ufbxi_aa_tree_insert, (map, node, value, index, item_size), 59,
(ufbxi_map *map, ufbxi_aa_node *node, const void *value, uint32_t index, size_t item_size))
{
if (!node) {
ufbxi_aa_node *new_node = ufbxi_push(&map->aa_buf, ufbxi_aa_node, 1);
if (!new_node) return NULL;
new_node->left = NULL;
new_node->right = NULL;
new_node->level = 1;
new_node->index = index;
return new_node;
}
void *entry = (char*)map->items + node->index * item_size;
int cmp = map->cmp_fn(map->cmp_user, value, entry);
if (cmp < 0) {
node->left = ufbxi_aa_tree_insert(map, node->left, value, index, item_size);
} else if (cmp >= 0) {
node->right = ufbxi_aa_tree_insert(map, node->right, value, index, item_size);
}
if (node->left && node->left->level == node->level) {
ufbxi_aa_node *left = node->left;
node->left = left->right;
left->right = node;
node = left;
}
if (node->right && node->right->right && node->right->right->level == node->level) {
ufbxi_aa_node *right = node->right;
node->right = right->left;
right->left = node;
right->level += 1;
node = right;
}
return node;
}
static ufbxi_noinline void *ufbxi_aa_tree_find(ufbxi_map *map, const void *value, size_t item_size)
{
ufbxi_aa_node *node = map->aa_root;
while (node) {
void *entry = (char*)map->items + node->index * item_size;
int cmp = map->cmp_fn(map->cmp_user, value, entry);
if (cmp < 0) {
node = node->left;
} else if (cmp > 0) {
node = node->right;
} else {
return entry;
}
}
return NULL;
}
static ufbxi_noinline bool ufbxi_map_grow_size_imp(ufbxi_map *map, size_t item_size, size_t min_size)
{
ufbx_assert(min_size > 0);
const double load_factor = 0.7;
// Find the lowest power of two size that fits `min_size` within `load_factor`
size_t num_entries = map->mask + 1;
size_t new_size = (size_t)((double)num_entries * load_factor);
if (min_size < map->capacity + 1) min_size = map->capacity + 1;
while (new_size < min_size) {
num_entries *= 2;
new_size = (size_t)((double)num_entries * load_factor);
}
// Check for overflow
ufbxi_check_return_err(map->ator->error, SIZE_MAX / num_entries > sizeof(uint64_t), false);
size_t alloc_size = num_entries * sizeof(uint64_t);
// Allocate a combined entry/item memory block
ufbxi_check_return_err(map->ator->error, (SIZE_MAX - alloc_size) / new_size > item_size, false);
size_t data_size = alloc_size + new_size * item_size;
char *data = ufbxi_alloc(map->ator, char, data_size);
ufbxi_check_return_err(map->ator->error, data, false);
// Copy the previous user items over
uint64_t *old_entries = map->entries;
uint64_t *new_entries = (uint64_t*)data;
void *new_items = data + alloc_size;
if (map->size > 0) {
memcpy(new_items, map->items, item_size * map->size);
}
// Re-hash the entries
uint32_t old_mask = map->mask;
uint32_t new_mask = (uint32_t)(num_entries) - 1;
memset(new_entries, 0, sizeof(uint64_t) * num_entries);
if (old_mask) {
for (uint32_t i = 0; i <= old_mask; i++) {
uint64_t entry, new_entry = old_entries[i];
if (!new_entry) continue;
// Reconstruct the hash of the old entry at `i`
uint32_t old_scan = (uint32_t)(new_entry & old_mask) - 1;
uint32_t hash = ((uint32_t)new_entry & ~old_mask) | ((i - old_scan) & old_mask);
uint32_t slot = hash & new_mask;
new_entry &= ~(uint64_t)new_mask;
// Scan forward until we find an empty slot, potentially swapping
// `new_element` if it has a shorter scan distance (Robin Hood).
uint32_t scan = 1;
while ((entry = new_entries[slot]) != 0) {
uint32_t entry_scan = (uint32_t)(entry & new_mask);
if (entry_scan < scan) {
new_entries[slot] = new_entry + scan;
new_entry = (entry & ~(uint64_t)new_mask);
scan = entry_scan;
}
scan += 1;
slot = (slot + 1) & new_mask;
}
new_entries[slot] = new_entry + scan;
}
}
// And finally free the previous allocation
ufbxi_free(map->ator, char, (char*)old_entries, map->data_size);
map->items = new_items;
map->data_size = data_size;
map->entries = new_entries;
map->mask = new_mask;
map->capacity = (uint32_t)new_size;
return true;
}
static ufbxi_forceinline bool ufbxi_map_grow_size(ufbxi_map *map, size_t size, size_t min_size)
{
#if defined(UFBX_REGRESSION)
{
ufbxi_allocator *ator = map->ator;
ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, false, "Allocation limit exceeded");
ator->num_allocs++;
}
#endif
if (map->size < map->capacity && map->capacity >= min_size) return true;
return ufbxi_map_grow_size_imp(map, size, min_size);
}
static ufbxi_noinline void *ufbxi_map_find_size(ufbxi_map *map, size_t size, uint32_t hash, const void *value)
{
uint64_t *entries = map->entries;
uint32_t mask = map->mask, scan = 0;
uint32_t ref = hash & ~mask;
if (!mask || scan == UINT32_MAX) return 0;
// Scan entries until we find an exact match of the hash or until we hit
// an element that has lower scan distance than our search (Robin Hood).
// The encoding guarantees that zero slots also terminate with the same test.
for (;;) {
uint64_t entry = entries[(hash + scan) & mask];
scan += 1;
if ((uint32_t)entry == ref + scan) {
uint32_t index = (uint32_t)(entry >> 32u);
void *data = (char*)map->items + size * index;
int cmp = map->cmp_fn(map->cmp_user, value, data);
if (cmp == 0) return data;
} else if ((entry & mask) < scan) {
if (map->aa_root) {
return ufbxi_aa_tree_find(map, value, size);
} else {
return NULL;
}
}
}
}
static ufbxi_noinline void *ufbxi_map_insert_size(ufbxi_map *map, size_t size, uint32_t hash, const void *value)
{
if (!ufbxi_map_grow_size(map, size, 64)) return NULL;
ufbxi_regression_assert(ufbxi_map_find_size(map, size, hash, value) == NULL);
uint32_t index = map->size++;
uint64_t *entries = map->entries;
uint32_t mask = map->mask;
// Scan forward until we find an empty slot, potentially swapping
// `new_element` if it has a shorter scan distance (Robin Hood).
uint32_t slot = hash & mask;
uint64_t entry, new_entry = (uint64_t)index << 32u | (hash & ~mask);
uint32_t scan = 1;
while ((entry = entries[slot]) != 0) {
uint32_t entry_scan = (uint32_t)(entry & mask);
if (entry_scan < scan) {
entries[slot] = new_entry + scan;
new_entry = (entry & ~(uint64_t)mask);
scan = entry_scan;
}
scan += 1;
slot = (slot + 1) & mask;
if (scan > UFBXI_MAP_MAX_SCAN) {
uint32_t new_index = (uint32_t)(new_entry >> 32u);
const void *new_value = new_index == index ? value : (const void*)((char*)map->items + size * new_index);
map->aa_root = ufbxi_aa_tree_insert(map, map->aa_root, new_value, new_index, size);
return (char*)map->items + size * index;
}
}
entries[slot] = new_entry + scan;
return (char*)map->items + size * index;
}
#define ufbxi_map_grow(map, type, min_size) ufbxi_map_grow_size((map), sizeof(type), (min_size))
#define ufbxi_map_find(map, type, hash, value) ufbxi_maybe_null((type*)ufbxi_map_find_size((map), sizeof(type), (hash), (value)))
#define ufbxi_map_insert(map, type, hash, value) ufbxi_maybe_null((type*)ufbxi_map_insert_size((map), sizeof(type), (hash), (value)))
static int ufbxi_map_cmp_uint64(void *user, const void *va, const void *vb)
{
(void)user;
uint64_t a = *(const uint64_t*)va, b = *(const uint64_t*)vb;
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
static int ufbxi_map_cmp_const_char_ptr(void *user, const void *va, const void *vb)
{
(void)user;
const char *a = *(const char **)va, *b = *(const char **)vb;
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
static int ufbxi_map_cmp_uintptr(void *user, const void *va, const void *vb)
{
(void)user;
uintptr_t a = *(const uintptr_t*)va, b = *(const uintptr_t*)vb;
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
// -- Hash functions
static ufbxi_noinline uint32_t ufbxi_hash_string(const char *str, size_t length)
{
uint32_t hash = (uint32_t)length;
uint32_t seed = UINT32_C(0x9e3779b9);
if (length >= 4) {
do {
uint32_t word = ufbxi_read_u32(str);
hash = ((hash << 5u | hash >> 27u) ^ word) * seed;
str += 4;
length -= 4;
} while (length >= 4);
uint32_t word = ufbxi_read_u32(str + length - 4);
hash = ((hash << 5u | hash >> 27u) ^ word) * seed;
} else {
uint32_t word = 0;
if (length >= 1) word |= (uint32_t)(uint8_t)str[0] << 0;
if (length >= 2) word |= (uint32_t)(uint8_t)str[1] << 8;
if (length >= 3) word |= (uint32_t)(uint8_t)str[2] << 16;
hash = ((hash << 5u | hash >> 27u) ^ word) * seed;
}
hash ^= hash >> 16;
hash *= UINT32_C(0x7feb352d);
hash ^= hash >> 15;
return hash;
}
// NOTE: _Must_ match `ufbxi_hash_string()`
static ufbxi_noinline uint32_t ufbxi_hash_string_check_ascii(const char *str, size_t length, bool *p_non_ascii)
{
uint32_t ascii_mask = 0;
uint32_t zero_mask = 0;
ufbx_assert(length > 0);
uint32_t hash = (uint32_t)length;
uint32_t seed = UINT32_C(0x9e3779b9);
if (length >= 4) {
do {
uint32_t word = ufbxi_read_u32(str);
ascii_mask |= word;
zero_mask |= UINT32_C(0x80808080) - word;
hash = ((hash << 5u | hash >> 27u) ^ word) * seed;
str += 4;
length -= 4;
} while (length >= 4);
uint32_t word = ufbxi_read_u32(str + length - 4);
ascii_mask |= word;
zero_mask |= UINT32_C(0x80808080) - word;
hash = ((hash << 5u | hash >> 27u) ^ word) * seed;
} else {
uint32_t word = 0;
if (length >= 1) word |= (uint32_t)(uint8_t)str[0] << 0;
if (length >= 2) word |= (uint32_t)(uint8_t)str[1] << 8;
if (length >= 3) word |= (uint32_t)(uint8_t)str[2] << 16;
ascii_mask |= word;
zero_mask |= (UINT32_C(0x80808080) >> ((4u - length) * 8u)) - word;
hash = ((hash << 5u | hash >> 27u) ^ word) * seed;
}
// If any character has high bit set or is zero we're not ASCII
if (((ascii_mask | zero_mask) & 0x80808080u) != 0) {
*p_non_ascii = true;
}
hash ^= hash >> 16;
hash *= UINT32_C(0x7feb352d);
hash ^= hash >> 15;
return hash;
}
static ufbxi_forceinline uint32_t ufbxi_hash32(uint32_t x)
{
x ^= x >> 16;
x *= UINT32_C(0x7feb352d);
x ^= x >> 15;
x *= UINT32_C(0x846ca68b);
x ^= x >> 16;
return x;
}
static ufbxi_forceinline uint32_t ufbxi_hash64(uint64_t x)
{
x ^= x >> 32;
x *= UINT64_C(0xd6e8feb86659fd93);
x ^= x >> 32;
x *= UINT64_C(0xd6e8feb86659fd93);
x ^= x >> 32;
return (uint32_t)x;
}
static ufbxi_forceinline uint32_t ufbxi_hash_uptr(uintptr_t ptr)
{
return sizeof(ptr) == 8 ? ufbxi_hash64((uint64_t)ptr) : ufbxi_hash32((uint32_t)ptr);
}
#define ufbxi_hash_ptr(ptr) ufbxi_hash_uptr((uintptr_t)(ptr))
// -- Warnings
typedef struct {
ufbx_error *error;
ufbxi_buf *result;
ufbxi_buf tmp_stack;
uint32_t deferred_element_id_plus_one;
// Separate lists for specific and non-specific warnings
ufbx_warning *prev_warnings[UFBX_WARNING_TYPE_COUNT][2];
} ufbxi_warnings;
ufbxi_nodiscard static ufbxi_noinline int ufbxi_vwarnf_imp(ufbxi_warnings *ws, ufbx_warning_type type, uint32_t element_id, const char *fmt, va_list args)
{
if (!ws) return 1;
// HACK(warning-element): Encode potential deferred element ID into `ufbx_warning.element_id`,
// `ws->element_id_index_plus_one` contains index to `uc->tmp_element_id`.
// Tag deferred indices with the high bit.
if (element_id == ~0u && ws->deferred_element_id_plus_one > 0) {
element_id = (ws->deferred_element_id_plus_one - 1) | 0x80000000u;
}
uint32_t has_element_id = element_id != ~0u;
if (type >= UFBX_WARNING_TYPE_FIRST_DEDUPLICATED) {
ufbx_warning *prev = ws->prev_warnings[type][has_element_id];
if (prev && prev->element_id == element_id) {
prev->count++;
return 1;
}
}
char desc[256];
size_t desc_len = (size_t)ufbxi_vsnprintf(desc, sizeof(desc), fmt, args);
ufbxi_clean_string_utf8(desc, desc_len);
char *desc_copy = ufbxi_push_copy(ws->result, char, desc_len + 1, desc);
ufbxi_check_err(ws->error, desc_copy);
ufbx_warning *warning = ufbxi_push(&ws->tmp_stack, ufbx_warning, 1);
ufbxi_check_err(ws->error, warning);
warning->type = type;
warning->description.data = desc_copy;
warning->description.length = desc_len;
warning->element_id = element_id;
warning->count = 1;
ws->prev_warnings[type][has_element_id] = warning;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_warnf_imp(ufbxi_warnings *ws, ufbx_warning_type type, uint32_t element_id, const char *fmt, ...)
{
// NOTE: `ws` may be `NULL` here, handled by `ufbxi_vwarnf()`
va_list args; // ufbxi_uninit
va_start(args, fmt);
int ok = ufbxi_vwarnf_imp(ws, type, element_id, fmt, args);
va_end(args);
return ok;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_pop_warnings(ufbxi_warnings *ws, ufbx_warning_list *warnings, bool *p_has_warning)
{
warnings->count = ws->tmp_stack.num_items;
warnings->data = ufbxi_push_pop(ws->result, &ws->tmp_stack, ufbx_warning, warnings->count);
ufbxi_check_err(ws->error, warnings->data);
ufbxi_for_list(ufbx_warning, warning, *warnings) {
p_has_warning[warning->type] = true;
}
return 1;
}
// -- String pool
// All strings found in FBX files are interned for deduplication and fast
// comparison. Our fixed internal strings (`ufbxi_String`) are considered the
// canonical pointers for said strings so we can compare them by address.
typedef struct {
ufbx_error *error;
ufbxi_buf buf; // < Buffer for the actual string data
ufbxi_map map; // < Map of `ufbxi_string`
size_t initial_size; // < Number of initial entries
char *temp_str; // < Temporary string buffer of `temp_cap`
size_t temp_cap; // < Capacity of the temporary buffer
ufbx_unicode_error_handling error_handling;
ufbxi_warnings *warnings;
} ufbxi_string_pool;
typedef struct {
const char *raw_data; // < UTF-8 data follows at `raw_length+1` if `utf8_length > 0`
uint32_t raw_length; // < Length of the non-sanitized original string
uint32_t utf8_length; // < Length of sanitized UTF-8 string (or zero)
} ufbxi_sanitized_string;
static ufbxi_forceinline bool ufbxi_str_equal(ufbx_string a, ufbx_string b)
{
return a.length == b.length && !memcmp(a.data, b.data, a.length);
}
static ufbxi_forceinline bool ufbxi_str_less(ufbx_string a, ufbx_string b)
{
size_t len = ufbxi_min_sz(a.length, b.length);
int cmp = memcmp(a.data, b.data, len);
if (cmp != 0) return cmp < 0;
return a.length < b.length;
}
static ufbxi_forceinline int ufbxi_str_cmp(ufbx_string a, ufbx_string b)
{
size_t len = ufbxi_min_sz(a.length, b.length);
int cmp = memcmp(a.data, b.data, len);
if (cmp != 0) return cmp;
if (a.length != b.length) return a.length < b.length ? -1 : 1;
return 0;
}
static ufbxi_forceinline ufbx_string ufbxi_str_c(const char *str)
{
ufbx_string s = { str, strlen(str) };
return s;
}
static ufbxi_noinline uint32_t ufbxi_get_concat_key(const ufbx_string *parts, size_t num_parts)
{
uint32_t key = 0, shift = 32;
ufbxi_for(const ufbx_string, part, parts, num_parts) {
size_t length = part->length != SIZE_MAX ? part->length : strlen(part->data);
for (size_t i = 0; i < length; i++) {
shift -= 8;
key |= (uint32_t)(uint8_t)part->data[i] << shift;
if (shift == 0) return key;
}
}
return key;
}
static ufbxi_noinline int ufbxi_concat_str_cmp(const ufbx_string *ref, const ufbx_string *parts, size_t num_parts)
{
const char *ptr = ref->data, *end = ptr + ref->length;
ufbxi_for(const ufbx_string, part, parts, num_parts) {
size_t length = part->length != SIZE_MAX ? part->length : strlen(part->data);
size_t to_cmp = ufbxi_min_sz(ufbxi_to_size(end - ptr), length);
int cmp = to_cmp > 0 ? memcmp(ptr, part->data, to_cmp) : 0;
if (cmp != 0) return cmp;
if (to_cmp != length) return -1;
ptr += length;
}
return ptr == end ? 0 : +1;
}
static ufbxi_forceinline bool ufbxi_starts_with(ufbx_string str, ufbx_string prefix)
{
return str.length >= prefix.length && !memcmp(str.data, prefix.data, prefix.length);
}
static ufbxi_forceinline bool ufbxi_ends_with(ufbx_string str, ufbx_string suffix)
{
return str.length >= suffix.length && !memcmp(str.data + str.length - suffix.length, suffix.data, suffix.length);
}
static ufbxi_noinline bool ufbxi_remove_prefix_len(ufbx_string *str, const char *prefix, size_t prefix_len)
{
ufbx_string prefix_str = { prefix, prefix_len };
if (ufbxi_starts_with(*str, prefix_str)) {
str->data += prefix_len;
str->length -= prefix_len;
return true;
}
return false;
}
static ufbxi_noinline bool ufbxi_remove_suffix_len(ufbx_string *str, const char *suffix, size_t suffix_len)
{
ufbx_string suffix_str = { suffix, suffix_len };
if (ufbxi_ends_with(*str, suffix_str)) {
str->length -= suffix_len;
return true;
}
return false;
}
static ufbxi_forceinline bool ufbxi_remove_prefix_str(ufbx_string *str, ufbx_string prefix)
{
return ufbxi_remove_prefix_len(str, prefix.data, prefix.length);
}
static ufbxi_forceinline bool ufbxi_remove_suffix_c(ufbx_string *str, const char *suffix)
{
return ufbxi_remove_suffix_len(str, suffix, strlen(suffix));
}
static int ufbxi_map_cmp_string(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_string *a = (const ufbx_string*)va, *b = (const ufbx_string*)vb;
return ufbxi_str_cmp(*a, *b);
}
static ufbxi_forceinline ufbx_string ufbxi_safe_string(const char *data, size_t length)
{
ufbx_string str = { length > 0 ? data : ufbxi_empty_char, length };
return str;
}
static void ufbxi_string_pool_temp_free(ufbxi_string_pool *pool)
{
ufbxi_free(pool->map.ator, char, pool->temp_str, pool->temp_cap);
ufbxi_map_free(&pool->map);
}
ufbxi_nodiscard static size_t ufbxi_add_replacement_char(ufbxi_string_pool *pool, char *dst, char c)
{
switch (pool->error_handling) {
case UFBX_UNICODE_ERROR_HANDLING_REPLACEMENT_CHARACTER:
dst[0] = (char)(uint8_t)0xefu;
dst[1] = (char)(uint8_t)0xbfu;
dst[2] = (char)(uint8_t)0xbdu;
return 3;
case UFBX_UNICODE_ERROR_HANDLING_UNDERSCORE:
dst[0] = '_';
return 1;
case UFBX_UNICODE_ERROR_HANDLING_QUESTION_MARK:
dst[0] = '?';
return 1;
case UFBX_UNICODE_ERROR_HANDLING_REMOVE:
return 0;
case UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE:
dst[0] = c;
return 1;
default:
return 0;
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_sanitize_string(ufbxi_string_pool *pool, ufbxi_sanitized_string *sanitized, const char *str, size_t length, size_t valid_length, bool push_both)
{
// Handle only invalid cases here
ufbx_assert(valid_length < length);
ufbxi_check_err_msg(pool->error, pool->error_handling != UFBX_UNICODE_ERROR_HANDLING_ABORT_LOADING, "Invalid UTF-8");
ufbxi_check_err(pool->error, ufbxi_warnf_imp(pool->warnings, UFBX_WARNING_BAD_UNICODE, ~0u, "Bad UTF-8 string"));
size_t index = valid_length;
size_t dst_len = index;
if (push_both) {
// Copy both the full raw string and the initial valid part
ufbxi_check_err(pool->error, length <= SIZE_MAX / 2 - 64);
ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, length * 2 + 64));
memcpy(pool->temp_str, str, length);
pool->temp_str[length] = '\0';
memcpy(pool->temp_str + length + 1, str, index);
dst_len += length + 1;
} else {
// Copy the initial valid part
ufbxi_check_err(pool->error, length <= SIZE_MAX - 64);
ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, length + 64));
memcpy(pool->temp_str, str, index);
}
char *dst = pool->temp_str;
while (index < length) {
uint8_t c = (uint8_t)str[index];
size_t left = length - index;
// Not optimal but not the worst thing ever
if (pool->temp_cap - dst_len < 16) {
ufbxi_check_err(pool->error, ufbxi_grow_array(pool->map.ator, &pool->temp_str, &pool->temp_cap, dst_len + 16));
dst = pool->temp_str;
}
if ((c & 0x80) == 0) {
if (c != 0) {
dst[dst_len] = (char)c;
dst_len += 1;
index += 1;
continue;
}
} else if ((c & 0xe0) == 0xc0 && left >= 2) {
uint8_t t0 = (uint8_t)str[index + 1];
uint32_t code = (uint32_t)c << 8 | (uint32_t)t0 << 0;
if ((code & 0xc0) == 0x80 && code >= 0xc280) {
dst[dst_len + 0] = (char)c;
dst[dst_len + 1] = (char)t0;
dst_len += 2;
index += 2;
continue;
}
} else if ((c & 0xf0) == 0xe0 && left >= 3) {
uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2];
uint32_t code = (uint32_t)c << 16 | (uint32_t)t0 << 8 | (uint32_t)t1;
if ((code & 0xc0c0) == 0x8080 && code >= 0xe0a080 && (code < 0xeda080 || code >= 0xee8080)) {
dst[dst_len + 0] = (char)c;
dst[dst_len + 1] = (char)t0;
dst[dst_len + 2] = (char)t1;
dst_len += 3;
index += 3;
continue;
}
} else if ((c & 0xf8) == 0xf0 && left >= 4) {
uint8_t t0 = (uint8_t)str[index + 1], t1 = (uint8_t)str[index + 2], t2 = (uint8_t)str[index + 3];
uint32_t code = (uint32_t)c << 24 | (uint32_t)t0 << 16 | (uint32_t)t1 << 8 | (uint32_t)t2;
if ((code & 0xc0c0c0) == 0x808080 && code >= 0xf0908080u && code <= 0xf48fbfbfu) {
dst[dst_len + 0] = (char)c;
dst[dst_len + 1] = (char)t0;
dst[dst_len + 2] = (char)t1;
dst[dst_len + 3] = (char)t2;
dst_len += 4;
index += 4;
continue;
}
}
dst_len += ufbxi_add_replacement_char(pool, dst + dst_len, (char)c);
index++;
}
// Sanitized strings are packed to 32-bit integers, in practice this should be fine
// as strings are limited to 32-bit length in FBX itself.
// The only problem case is a massive string that is full of unicode errors, ie.
// >1GB binary blob, but these should never be sanitized.
ufbxi_check_err(pool->error, length <= UINT32_MAX);
sanitized->raw_data = pool->temp_str;
if (push_both) {
// Reserve `UINT32_MAX` for invalid UTF-8 without sanitization
size_t utf8_length = dst_len - (length + 1);
ufbxi_check_err(pool->error, utf8_length < UINT32_MAX);
sanitized->raw_length = (uint32_t)length;
sanitized->utf8_length = (uint32_t)utf8_length;
} else {
ufbxi_check_err(pool->error, dst_len <= UINT32_MAX);
sanitized->raw_length = (uint32_t)dst_len;
sanitized->utf8_length = 0;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_sanitized_string(ufbxi_string_pool *pool, ufbxi_sanitized_string *sanitized, const char *str, size_t length, uint32_t hash, bool raw)
{
ufbxi_regression_assert(hash == ufbxi_hash_string(str, length));
ufbxi_check_err(pool->error, length <= UINT32_MAX);
ufbxi_check_err(pool->error, ufbxi_map_grow(&pool->map, ufbx_string, pool->initial_size));
const char *total_data = str;
size_t total_length = length;
sanitized->raw_length = (uint32_t)length;
sanitized->utf8_length = 0;
if (!raw) {
size_t valid_length = ufbxi_utf8_valid_length(str, length);
if (valid_length != length) {
ufbxi_check_err(pool->error, ufbxi_sanitize_string(pool, sanitized, str, length, valid_length, true));
total_data = sanitized->raw_data;
total_length = sanitized->raw_length + sanitized->utf8_length + 1;
hash = ufbxi_hash_string(str, length);
}
}
ufbx_string ref = { total_data, total_length };
ufbx_string *entry = ufbxi_map_find(&pool->map, ufbx_string, hash, &ref);
if (entry) {
sanitized->raw_data = entry->data;
} else {
entry = ufbxi_map_insert(&pool->map, ufbx_string, hash, &ref);
ufbxi_check_err(pool->error, entry);
entry->length = total_length;
char *dst = ufbxi_push(&pool->buf, char, total_length + 1);
ufbxi_check_err(pool->error, dst);
memcpy(dst, total_data, total_length);
dst[total_length] = '\0';
entry->data = dst;
sanitized->raw_data = dst;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_push_string_imp(ufbxi_string_pool *pool, const char *str, size_t length, size_t *p_out_length, bool copy, bool raw)
{
if (length == 0) return ufbxi_empty_char;
ufbxi_check_return_err(pool->error, ufbxi_map_grow(&pool->map, ufbx_string, pool->initial_size), NULL);
uint32_t hash;
if (raw) {
hash = ufbxi_hash_string(str, length);
} else {
bool non_ascii = false;
hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii);
if (non_ascii) {
size_t valid_length = ufbxi_utf8_valid_length(str, length);
if (valid_length < length) {
ufbxi_sanitized_string sanitized;
ufbxi_check_return_err(pool->error, ufbxi_sanitize_string(pool, &sanitized, str, length, valid_length, false), NULL);
str = sanitized.raw_data;
length = sanitized.raw_length;
hash = ufbxi_hash_string(str, length);
*p_out_length = length;
}
}
}
ufbx_string ref = { str, length };
ufbx_string *entry = ufbxi_map_find(&pool->map, ufbx_string, hash, &ref);
if (entry) return entry->data;
entry = ufbxi_map_insert(&pool->map, ufbx_string, hash, &ref);
ufbxi_check_return_err(pool->error, entry, NULL);
entry->length = length;
if (copy) {
char *dst = ufbxi_push(&pool->buf, char, length + 1);
ufbxi_check_return_err(pool->error, dst, NULL);
memcpy(dst, str, length);
dst[length] = '\0';
entry->data = dst;
} else {
entry->data = str;
}
return entry->data;
}
ufbxi_nodiscard static ufbxi_forceinline const char *ufbxi_push_string(ufbxi_string_pool *pool, const char *str, size_t length, size_t *p_out_length, bool raw)
{
return ufbxi_push_string_imp(pool, str, length, p_out_length, true, raw);
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_push_string_place(ufbxi_string_pool *pool, const char **p_str, size_t *p_length, bool raw)
{
const char *str = *p_str;
size_t length = *p_length;
ufbxi_check_err(pool->error, str || length == 0);
str = ufbxi_push_string(pool, str, length, p_length, raw);
ufbxi_check_err(pool->error, str);
*p_str = str;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_string_place_str(ufbxi_string_pool *pool, ufbx_string *p_str, bool raw)
{
ufbxi_check_err(pool->error, p_str);
return ufbxi_push_string_place(pool, &p_str->data, &p_str->length, raw);
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_string_place_blob(ufbxi_string_pool *pool, ufbx_blob *p_blob, bool raw)
{
if (p_blob->size == 0) {
p_blob->data = NULL;
return 1;
}
p_blob->data = ufbxi_push_string(pool, (const char*)p_blob->data, p_blob->size, &p_blob->size, raw);
ufbxi_check_err(pool->error, p_blob->data);
return 1;
}
// -- String constants
//
// All strings in FBX files are pooled so by having canonical string constant
// addresses we can compare strings to these constants by comparing pointers.
// Keep the list alphabetically sorted!
static const char ufbxi_AllSame[] = "AllSame";
static const char ufbxi_Alphas[] = "Alphas";
static const char ufbxi_AmbientColor[] = "AmbientColor";
static const char ufbxi_AnimationCurveNode[] = "AnimationCurveNode";
static const char ufbxi_AnimationCurve[] = "AnimationCurve";
static const char ufbxi_AnimationLayer[] = "AnimationLayer";
static const char ufbxi_AnimationStack[] = "AnimationStack";
static const char ufbxi_ApertureFormat[] = "ApertureFormat";
static const char ufbxi_ApertureMode[] = "ApertureMode";
static const char ufbxi_AreaLightShape[] = "AreaLightShape";
static const char ufbxi_AspectH[] = "AspectH";
static const char ufbxi_AspectHeight[] = "AspectHeight";
static const char ufbxi_AspectRatioMode[] = "AspectRatioMode";
static const char ufbxi_AspectW[] = "AspectW";
static const char ufbxi_AspectWidth[] = "AspectWidth";
static const char ufbxi_Audio[] = "Audio";
static const char ufbxi_AudioLayer[] = "AudioLayer";
static const char ufbxi_BaseLayer[] = "BaseLayer";
static const char ufbxi_BinaryData[] = "BinaryData";
static const char ufbxi_BindPose[] = "BindPose";
static const char ufbxi_BindingTable[] = "BindingTable";
static const char ufbxi_Binormals[] = "Binormals";
static const char ufbxi_BinormalsIndex[] = "BinormalsIndex";
static const char ufbxi_BinormalsW[] = "BinormalsW";
static const char ufbxi_BlendMode[] = "BlendMode";
static const char ufbxi_BlendModes[] = "BlendModes";
static const char ufbxi_BlendShapeChannel[] = "BlendShapeChannel";
static const char ufbxi_BlendShape[] = "BlendShape";
static const char ufbxi_BlendWeights[] = "BlendWeights";
static const char ufbxi_BoundaryRule[] = "BoundaryRule";
static const char ufbxi_Boundary[] = "Boundary";
static const char ufbxi_ByEdge[] = "ByEdge";
static const char ufbxi_ByPolygonVertex[] = "ByPolygonVertex";
static const char ufbxi_ByPolygon[] = "ByPolygon";
static const char ufbxi_ByVertex[] = "ByVertex";
static const char ufbxi_ByVertice[] = "ByVertice";
static const char ufbxi_Cache[] = "Cache";
static const char ufbxi_CameraProjectionType[] = "CameraProjectionType";
static const char ufbxi_CameraStereo[] = "CameraStereo";
static const char ufbxi_CameraSwitcher[] = "CameraSwitcher";
static const char ufbxi_Camera[] = "Camera";
static const char ufbxi_CastLight[] = "CastLight";
static const char ufbxi_CastShadows[] = "CastShadows";
static const char ufbxi_Channel[] = "Channel";
static const char ufbxi_Character[] = "Character";
static const char ufbxi_Children[] = "Children";
static const char ufbxi_Cluster[] = "Cluster";
static const char ufbxi_CollectionExclusive[] = "CollectionExclusive";
static const char ufbxi_Collection[] = "Collection";
static const char ufbxi_ColorIndex[] = "ColorIndex";
static const char ufbxi_Color[] = "Color";
static const char ufbxi_Colors[] = "Colors";
static const char ufbxi_Cone_angle[] = "Cone angle";
static const char ufbxi_ConeAngle[] = "ConeAngle";
static const char ufbxi_Connections[] = "Connections";
static const char ufbxi_Constraint[] = "Constraint";
static const char ufbxi_Content[] = "Content";
static const char ufbxi_CoordAxisSign[] = "CoordAxisSign";
static const char ufbxi_CoordAxis[] = "CoordAxis";
static const char ufbxi_Count[] = "Count";
static const char ufbxi_Creator[] = "Creator";
static const char ufbxi_CurrentTextureBlendMode[] = "CurrentTextureBlendMode";
static const char ufbxi_CurrentTimeMarker[] = "CurrentTimeMarker";
static const char ufbxi_CustomFrameRate[] = "CustomFrameRate";
static const char ufbxi_DecayType[] = "DecayType";
static const char ufbxi_DefaultCamera[] = "DefaultCamera";
static const char ufbxi_Default[] = "Default";
static const char ufbxi_Definitions[] = "Definitions";
static const char ufbxi_DeformPercent[] = "DeformPercent";
static const char ufbxi_Deformer[] = "Deformer";
static const char ufbxi_DiffuseColor[] = "DiffuseColor";
static const char ufbxi_Dimension[] = "Dimension";
static const char ufbxi_Dimensions[] = "Dimensions";
static const char ufbxi_DisplayLayer[] = "DisplayLayer";
static const char ufbxi_Document[] = "Document";
static const char ufbxi_Documents[] = "Documents";
static const char ufbxi_EdgeCrease[] = "EdgeCrease";
static const char ufbxi_EdgeIndexArray[] = "EdgeIndexArray";
static const char ufbxi_Edges[] = "Edges";
static const char ufbxi_EmissiveColor[] = "EmissiveColor";
static const char ufbxi_Entry[] = "Entry";
static const char ufbxi_FBXHeaderExtension[] = "FBXHeaderExtension";
static const char ufbxi_FBXVersion[] = "FBXVersion";
static const char ufbxi_FKEffector[] = "FKEffector";
static const char ufbxi_FarPlane[] = "FarPlane";
static const char ufbxi_FbxPropertyEntry[] = "FbxPropertyEntry";
static const char ufbxi_FbxSemanticEntry[] = "FbxSemanticEntry";
static const char ufbxi_FieldOfViewX[] = "FieldOfViewX";
static const char ufbxi_FieldOfViewY[] = "FieldOfViewY";
static const char ufbxi_FieldOfView[] = "FieldOfView";
static const char ufbxi_FileName[] = "FileName";
static const char ufbxi_Filename[] = "Filename";
static const char ufbxi_FilmHeight[] = "FilmHeight";
static const char ufbxi_FilmSqueezeRatio[] = "FilmSqueezeRatio";
static const char ufbxi_FilmWidth[] = "FilmWidth";
static const char ufbxi_FlipNormals[] = "FlipNormals";
static const char ufbxi_FocalLength[] = "FocalLength";
static const char ufbxi_Form[] = "Form";
static const char ufbxi_Freeze[] = "Freeze";
static const char ufbxi_FrontAxisSign[] = "FrontAxisSign";
static const char ufbxi_FrontAxis[] = "FrontAxis";
static const char ufbxi_FullWeights[] = "FullWeights";
static const char ufbxi_GateFit[] = "GateFit";
static const char ufbxi_GeometricRotation[] = "GeometricRotation";
static const char ufbxi_GeometricScaling[] = "GeometricScaling";
static const char ufbxi_GeometricTranslation[] = "GeometricTranslation";
static const char ufbxi_GeometryUVInfo[] = "GeometryUVInfo";
static const char ufbxi_Geometry[] = "Geometry";
static const char ufbxi_GlobalSettings[] = "GlobalSettings";
static const char ufbxi_Hole[] = "Hole";
static const char ufbxi_HotSpot[] = "HotSpot";
static const char ufbxi_IKEffector[] = "IKEffector";
static const char ufbxi_ImageData[] = "ImageData";
static const char ufbxi_Implementation[] = "Implementation";
static const char ufbxi_Indexes[] = "Indexes";
static const char ufbxi_InheritType[] = "InheritType";
static const char ufbxi_InnerAngle[] = "InnerAngle";
static const char ufbxi_Intensity[] = "Intensity";
static const char ufbxi_IsTheNodeInSet[] = "IsTheNodeInSet";
static const char ufbxi_KeyAttrDataFloat[] = "KeyAttrDataFloat";
static const char ufbxi_KeyAttrFlags[] = "KeyAttrFlags";
static const char ufbxi_KeyAttrRefCount[] = "KeyAttrRefCount";
static const char ufbxi_KeyCount[] = "KeyCount";
static const char ufbxi_KeyTime[] = "KeyTime";
static const char ufbxi_KeyValueFloat[] = "KeyValueFloat";
static const char ufbxi_Key[] = "Key";
static const char ufbxi_KnotVectorU[] = "KnotVectorU";
static const char ufbxi_KnotVectorV[] = "KnotVectorV";
static const char ufbxi_KnotVector[] = "KnotVector";
static const char ufbxi_LayerElementBinormal[] = "LayerElementBinormal";
static const char ufbxi_LayerElementColor[] = "LayerElementColor";
static const char ufbxi_LayerElementEdgeCrease[] = "LayerElementEdgeCrease";
static const char ufbxi_LayerElementHole[] = "LayerElementHole";
static const char ufbxi_LayerElementMaterial[] = "LayerElementMaterial";
static const char ufbxi_LayerElementNormal[] = "LayerElementNormal";
static const char ufbxi_LayerElementPolygonGroup[] = "LayerElementPolygonGroup";
static const char ufbxi_LayerElementSmoothing[] = "LayerElementSmoothing";
static const char ufbxi_LayerElementTangent[] = "LayerElementTangent";
static const char ufbxi_LayerElementUV[] = "LayerElementUV";
static const char ufbxi_LayerElementVertexCrease[] = "LayerElementVertexCrease";
static const char ufbxi_LayerElementVisibility[] = "LayerElementVisibility";
static const char ufbxi_LayerElement[] = "LayerElement";
static const char ufbxi_Layer[] = "Layer";
static const char ufbxi_LayeredTexture[] = "LayeredTexture";
static const char ufbxi_Lcl_Rotation[] = "Lcl Rotation";
static const char ufbxi_Lcl_Scaling[] = "Lcl Scaling";
static const char ufbxi_Lcl_Translation[] = "Lcl Translation";
static const char ufbxi_LeftCamera[] = "LeftCamera";
static const char ufbxi_LightType[] = "LightType";
static const char ufbxi_Light[] = "Light";
static const char ufbxi_LimbLength[] = "LimbLength";
static const char ufbxi_LimbNode[] = "LimbNode";
static const char ufbxi_Limb[] = "Limb";
static const char ufbxi_Line[] = "Line";
static const char ufbxi_Link[] = "Link";
static const char ufbxi_LocalStart[] = "LocalStart";
static const char ufbxi_LocalStop[] = "LocalStop";
static const char ufbxi_LocalTime[] = "LocalTime";
static const char ufbxi_LodGroup[] = "LodGroup";
static const char ufbxi_MappingInformationType[] = "MappingInformationType";
static const char ufbxi_Marker[] = "Marker";
static const char ufbxi_MaterialAssignation[] = "MaterialAssignation";
static const char ufbxi_Material[] = "Material";
static const char ufbxi_Materials[] = "Materials";
static const char ufbxi_Matrix[] = "Matrix";
static const char ufbxi_Media[] = "Media";
static const char ufbxi_Mesh[] = "Mesh";
static const char ufbxi_Model[] = "Model";
static const char ufbxi_Name[] = "Name";
static const char ufbxi_NearPlane[] = "NearPlane";
static const char ufbxi_NodeAttributeName[] = "NodeAttributeName";
static const char ufbxi_NodeAttribute[] = "NodeAttribute";
static const char ufbxi_Node[] = "Node";
static const char ufbxi_Normals[] = "Normals";
static const char ufbxi_NormalsIndex[] = "NormalsIndex";
static const char ufbxi_NormalsW[] = "NormalsW";
static const char ufbxi_Null[] = "Null";
static const char ufbxi_NurbsCurve[] = "NurbsCurve";
static const char ufbxi_NurbsSurfaceOrder[] = "NurbsSurfaceOrder";
static const char ufbxi_NurbsSurface[] = "NurbsSurface";
static const char ufbxi_Nurbs[] = "Nurbs";
static const char ufbxi_OO[] = "OO\0";
static const char ufbxi_OP[] = "OP\0";
static const char ufbxi_ObjectMetaData[] = "ObjectMetaData";
static const char ufbxi_ObjectType[] = "ObjectType";
static const char ufbxi_Objects[] = "Objects";
static const char ufbxi_Order[] = "Order";
static const char ufbxi_OriginalUnitScaleFactor[] = "OriginalUnitScaleFactor";
static const char ufbxi_OriginalUpAxis[] = "OriginalUpAxis";
static const char ufbxi_OriginalUpAxisSign[] = "OriginalUpAxisSign";
static const char ufbxi_OrthoZoom[] = "OrthoZoom";
static const char ufbxi_OuterAngle[] = "OuterAngle";
static const char ufbxi_PO[] = "PO\0";
static const char ufbxi_PP[] = "PP\0";
static const char ufbxi_PointsIndex[] = "PointsIndex";
static const char ufbxi_Points[] = "Points";
static const char ufbxi_PolygonGroup[] = "PolygonGroup";
static const char ufbxi_PolygonIndexArray[] = "PolygonIndexArray";
static const char ufbxi_PolygonVertexIndex[] = "PolygonVertexIndex";
static const char ufbxi_PoseNode[] = "PoseNode";
static const char ufbxi_Pose[] = "Pose";
static const char ufbxi_PostRotation[] = "PostRotation";
static const char ufbxi_PreRotation[] = "PreRotation";
static const char ufbxi_PreviewDivisionLevels[] = "PreviewDivisionLevels";
static const char ufbxi_Properties60[] = "Properties60";
static const char ufbxi_Properties70[] = "Properties70";
static const char ufbxi_PropertyTemplate[] = "PropertyTemplate";
static const char ufbxi_R[] = "R\0\0";
static const char ufbxi_ReferenceStart[] = "ReferenceStart";
static const char ufbxi_ReferenceStop[] = "ReferenceStop";
static const char ufbxi_ReferenceTime[] = "ReferenceTime";
static const char ufbxi_RelativeFileName[] = "RelativeFileName";
static const char ufbxi_RelativeFilename[] = "RelativeFilename";
static const char ufbxi_RenderDivisionLevels[] = "RenderDivisionLevels";
static const char ufbxi_RightCamera[] = "RightCamera";
static const char ufbxi_RootNode[] = "RootNode";
static const char ufbxi_Root[] = "Root";
static const char ufbxi_RotationAccumulationMode[] = "RotationAccumulationMode";
static const char ufbxi_RotationOffset[] = "RotationOffset";
static const char ufbxi_RotationOrder[] = "RotationOrder";
static const char ufbxi_RotationPivot[] = "RotationPivot";
static const char ufbxi_Rotation[] = "Rotation";
static const char ufbxi_S[] = "S\0\0";
static const char ufbxi_ScaleAccumulationMode[] = "ScaleAccumulationMode";
static const char ufbxi_ScalingOffset[] = "ScalingOffset";
static const char ufbxi_ScalingPivot[] = "ScalingPivot";
static const char ufbxi_Scaling[] = "Scaling";
static const char ufbxi_SceneInfo[] = "SceneInfo";
static const char ufbxi_SelectionNode[] = "SelectionNode";
static const char ufbxi_SelectionSet[] = "SelectionSet";
static const char ufbxi_ShadingModel[] = "ShadingModel";
static const char ufbxi_Shape[] = "Shape";
static const char ufbxi_Shininess[] = "Shininess";
static const char ufbxi_Show[] = "Show";
static const char ufbxi_Size[] = "Size";
static const char ufbxi_Skin[] = "Skin";
static const char ufbxi_SkinningType[] = "SkinningType";
static const char ufbxi_Smoothing[] = "Smoothing";
static const char ufbxi_Smoothness[] = "Smoothness";
static const char ufbxi_SnapOnFrameMode[] = "SnapOnFrameMode";
static const char ufbxi_SpecularColor[] = "SpecularColor";
static const char ufbxi_Step[] = "Step";
static const char ufbxi_SubDeformer[] = "SubDeformer";
static const char ufbxi_T[] = "T\0\0";
static const char ufbxi_Take[] = "Take";
static const char ufbxi_Takes[] = "Takes";
static const char ufbxi_Tangents[] = "Tangents";
static const char ufbxi_TangentsIndex[] = "TangentsIndex";
static const char ufbxi_TangentsW[] = "TangentsW";
static const char ufbxi_Texture[] = "Texture";
static const char ufbxi_Texture_alpha[] = "Texture alpha";
static const char ufbxi_TextureId[] = "TextureId";
static const char ufbxi_TextureRotationPivot[] = "TextureRotationPivot";
static const char ufbxi_TextureScalingPivot[] = "TextureScalingPivot";
static const char ufbxi_TextureUV[] = "TextureUV";
static const char ufbxi_TextureUVVerticeIndex[] = "TextureUVVerticeIndex";
static const char ufbxi_Thumbnail[] = "Thumbnail";
static const char ufbxi_TimeMarker[] = "TimeMarker";
static const char ufbxi_TimeMode[] = "TimeMode";
static const char ufbxi_TimeProtocol[] = "TimeProtocol";
static const char ufbxi_TimeSpanStart[] = "TimeSpanStart";
static const char ufbxi_TimeSpanStop[] = "TimeSpanStop";
static const char ufbxi_TransformLink[] = "TransformLink";
static const char ufbxi_Transform[] = "Transform";
static const char ufbxi_Translation[] = "Translation";
static const char ufbxi_TrimNurbsSurface[] = "TrimNurbsSurface";
static const char ufbxi_Type[] = "Type";
static const char ufbxi_TypedIndex[] = "TypedIndex";
static const char ufbxi_UVIndex[] = "UVIndex";
static const char ufbxi_UVSet[] = "UVSet";
static const char ufbxi_UVSwap[] = "UVSwap";
static const char ufbxi_UV[] = "UV\0";
static const char ufbxi_UnitScaleFactor[] = "UnitScaleFactor";
static const char ufbxi_UpAxisSign[] = "UpAxisSign";
static const char ufbxi_UpAxis[] = "UpAxis";
static const char ufbxi_Version5[] = "Version5";
static const char ufbxi_VertexCacheDeformer[] = "VertexCacheDeformer";
static const char ufbxi_VertexCrease[] = "VertexCrease";
static const char ufbxi_VertexCreaseIndex[] = "VertexCreaseIndex";
static const char ufbxi_VertexIndexArray[] = "VertexIndexArray";
static const char ufbxi_Vertices[] = "Vertices";
static const char ufbxi_Video[] = "Video";
static const char ufbxi_Visibility[] = "Visibility";
static const char ufbxi_Weight[] = "Weight";
static const char ufbxi_Weights[] = "Weights";
static const char ufbxi_WrapModeU[] = "WrapModeU";
static const char ufbxi_WrapModeV[] = "WrapModeV";
static const char ufbxi_X[] = "X\0\0";
static const char ufbxi_Y[] = "Y\0\0";
static const char ufbxi_Z[] = "Z\0\0";
static const char ufbxi_d_X[] = "d|X";
static const char ufbxi_d_Y[] = "d|Y";
static const char ufbxi_d_Z[] = "d|Z";
static const ufbx_string ufbxi_strings[] = {
{ ufbxi_AllSame, 7 },
{ ufbxi_Alphas, 6 },
{ ufbxi_AmbientColor, 12 },
{ ufbxi_AnimationCurve, 14 },
{ ufbxi_AnimationCurveNode, 18 },
{ ufbxi_AnimationLayer, 14 },
{ ufbxi_AnimationStack, 14 },
{ ufbxi_ApertureFormat, 14 },
{ ufbxi_ApertureMode, 12 },
{ ufbxi_AreaLightShape, 14 },
{ ufbxi_AspectH, 7 },
{ ufbxi_AspectHeight, 12 },
{ ufbxi_AspectRatioMode, 15 },
{ ufbxi_AspectW, 7 },
{ ufbxi_AspectWidth, 11 },
{ ufbxi_Audio, 5 },
{ ufbxi_AudioLayer, 10 },
{ ufbxi_BaseLayer, 9 },
{ ufbxi_BinaryData, 10 },
{ ufbxi_BindPose, 8 },
{ ufbxi_BindingTable, 12 },
{ ufbxi_Binormals, 9 },
{ ufbxi_BinormalsIndex, 14 },
{ ufbxi_BinormalsW, 10 },
{ ufbxi_BlendMode, 9 },
{ ufbxi_BlendModes, 10 },
{ ufbxi_BlendShape, 10 },
{ ufbxi_BlendShapeChannel, 17 },
{ ufbxi_BlendWeights, 12 },
{ ufbxi_Boundary, 8 },
{ ufbxi_BoundaryRule, 12 },
{ ufbxi_ByEdge, 6 },
{ ufbxi_ByPolygon, 9 },
{ ufbxi_ByPolygonVertex, 15 },
{ ufbxi_ByVertex, 8 },
{ ufbxi_ByVertice, 9 },
{ ufbxi_Cache, 5 },
{ ufbxi_Camera, 6 },
{ ufbxi_CameraProjectionType, 20 },
{ ufbxi_CameraStereo, 12 },
{ ufbxi_CameraSwitcher, 14 },
{ ufbxi_CastLight, 9 },
{ ufbxi_CastShadows, 11 },
{ ufbxi_Channel, 7 },
{ ufbxi_Character, sizeof(ufbxi_Character) - 1 },
{ ufbxi_Children, 8 },
{ ufbxi_Cluster, 7 },
{ ufbxi_Collection, 10 },
{ ufbxi_CollectionExclusive, 19 },
{ ufbxi_Color, 5 },
{ ufbxi_ColorIndex, 10 },
{ ufbxi_Colors, 6 },
{ ufbxi_Cone_angle, 10 },
{ ufbxi_ConeAngle, 9 },
{ ufbxi_Connections, 11 },
{ ufbxi_Constraint, sizeof(ufbxi_Constraint) - 1 },
{ ufbxi_Content, 7 },
{ ufbxi_CoordAxis, 9 },
{ ufbxi_CoordAxisSign, 13 },
{ ufbxi_Count, 5 },
{ ufbxi_Creator, 7 },
{ ufbxi_CurrentTextureBlendMode, 23 },
{ ufbxi_CurrentTimeMarker, 17 },
{ ufbxi_CustomFrameRate, 15 },
{ ufbxi_DecayType, 9 },
{ ufbxi_Default, 7 },
{ ufbxi_DefaultCamera, 13 },
{ ufbxi_Definitions, 11 },
{ ufbxi_DeformPercent, 13 },
{ ufbxi_Deformer, 8 },
{ ufbxi_DiffuseColor, 12 },
{ ufbxi_Dimension, 9 },
{ ufbxi_Dimensions, 10 },
{ ufbxi_DisplayLayer, 12 },
{ ufbxi_Document, 8 },
{ ufbxi_Documents, 9 },
{ ufbxi_EdgeCrease, 10 },
{ ufbxi_EdgeIndexArray, 14 },
{ ufbxi_Edges, 5 },
{ ufbxi_EmissiveColor, 13 },
{ ufbxi_Entry, 5 },
{ ufbxi_FBXHeaderExtension, 18 },
{ ufbxi_FBXVersion, 10 },
{ ufbxi_FKEffector, 10 },
{ ufbxi_FarPlane, 8 },
{ ufbxi_FbxPropertyEntry, 16 },
{ ufbxi_FbxSemanticEntry, 16 },
{ ufbxi_FieldOfView, 11 },
{ ufbxi_FieldOfViewX, 12 },
{ ufbxi_FieldOfViewY, 12 },
{ ufbxi_FileName, 8 },
{ ufbxi_Filename, 8 },
{ ufbxi_FilmHeight, 10 },
{ ufbxi_FilmSqueezeRatio, 16 },
{ ufbxi_FilmWidth, 9 },
{ ufbxi_FlipNormals, 11 },
{ ufbxi_FocalLength, 11 },
{ ufbxi_Form, 4 },
{ ufbxi_Freeze, 6 },
{ ufbxi_FrontAxis, 9 },
{ ufbxi_FrontAxisSign, 13 },
{ ufbxi_FullWeights, 11 },
{ ufbxi_GateFit, 7 },
{ ufbxi_GeometricRotation, 17 },
{ ufbxi_GeometricScaling, 16 },
{ ufbxi_GeometricTranslation, 20 },
{ ufbxi_Geometry, 8 },
{ ufbxi_GeometryUVInfo, 14 },
{ ufbxi_GlobalSettings, 14 },
{ ufbxi_Hole, 4 },
{ ufbxi_HotSpot, 7 },
{ ufbxi_IKEffector, 10 },
{ ufbxi_ImageData, 9 },
{ ufbxi_Implementation, 14 },
{ ufbxi_Indexes, 7 },
{ ufbxi_InheritType, 11 },
{ ufbxi_InnerAngle, 10 },
{ ufbxi_Intensity, 9 },
{ ufbxi_IsTheNodeInSet, 14 },
{ ufbxi_Key, 3 },
{ ufbxi_KeyAttrDataFloat, 16 },
{ ufbxi_KeyAttrFlags, 12 },
{ ufbxi_KeyAttrRefCount, 15 },
{ ufbxi_KeyCount, 8 },
{ ufbxi_KeyTime, 7 },
{ ufbxi_KeyValueFloat, 13 },
{ ufbxi_KnotVector, 10 },
{ ufbxi_KnotVectorU, 11 },
{ ufbxi_KnotVectorV, 11 },
{ ufbxi_Layer, 5 },
{ ufbxi_LayerElement, 12 },
{ ufbxi_LayerElementBinormal, 20 },
{ ufbxi_LayerElementColor, 17 },
{ ufbxi_LayerElementEdgeCrease, 22 },
{ ufbxi_LayerElementHole, 16 },
{ ufbxi_LayerElementMaterial, 20 },
{ ufbxi_LayerElementNormal, 18 },
{ ufbxi_LayerElementPolygonGroup, 24 },
{ ufbxi_LayerElementSmoothing, 21 },
{ ufbxi_LayerElementTangent, 19 },
{ ufbxi_LayerElementUV, 14 },
{ ufbxi_LayerElementVertexCrease, 24 },
{ ufbxi_LayerElementVisibility, 22 },
{ ufbxi_LayeredTexture, 14 },
{ ufbxi_Lcl_Rotation, 12 },
{ ufbxi_Lcl_Scaling, 11 },
{ ufbxi_Lcl_Translation, 15 },
{ ufbxi_LeftCamera, 10 },
{ ufbxi_Light, 5 },
{ ufbxi_LightType, 9 },
{ ufbxi_Limb, 4 },
{ ufbxi_LimbLength, 10 },
{ ufbxi_LimbNode, 8 },
{ ufbxi_Line, 4 },
{ ufbxi_Link, 4 },
{ ufbxi_LocalStart, 10 },
{ ufbxi_LocalStop, 9 },
{ ufbxi_LocalTime, 9 },
{ ufbxi_LodGroup, 8 },
{ ufbxi_MappingInformationType, 22 },
{ ufbxi_Marker, 6 },
{ ufbxi_Material, 8 },
{ ufbxi_MaterialAssignation, 19 },
{ ufbxi_Materials, 9 },
{ ufbxi_Matrix, 6 },
{ ufbxi_Media, 5 },
{ ufbxi_Mesh, 4 },
{ ufbxi_Model, 5 },
{ ufbxi_Name, 4 },
{ ufbxi_NearPlane, 9 },
{ ufbxi_Node, 4 },
{ ufbxi_NodeAttribute, 13 },
{ ufbxi_NodeAttributeName, 17 },
{ ufbxi_Normals, 7 },
{ ufbxi_NormalsIndex, 12 },
{ ufbxi_NormalsW, 8 },
{ ufbxi_Null, 4 },
{ ufbxi_Nurbs, 5 },
{ ufbxi_NurbsCurve, 10 },
{ ufbxi_NurbsSurface, 12 },
{ ufbxi_NurbsSurfaceOrder, 17 },
{ ufbxi_OO, 2 },
{ ufbxi_OP, 2 },
{ ufbxi_ObjectMetaData, 14 },
{ ufbxi_ObjectType, 10 },
{ ufbxi_Objects, 7 },
{ ufbxi_Order, 5 },
{ ufbxi_OriginalUnitScaleFactor, 23 },
{ ufbxi_OriginalUpAxis, 14 },
{ ufbxi_OriginalUpAxisSign, 18 },
{ ufbxi_OrthoZoom, 9 },
{ ufbxi_OuterAngle, 10 },
{ ufbxi_PO, 2 },
{ ufbxi_PP, 2 },
{ ufbxi_Points, 6 },
{ ufbxi_PointsIndex, 11 },
{ ufbxi_PolygonGroup, 12 },
{ ufbxi_PolygonIndexArray, 17 },
{ ufbxi_PolygonVertexIndex, 18 },
{ ufbxi_Pose, 4 },
{ ufbxi_PoseNode, 8 },
{ ufbxi_PostRotation, 12 },
{ ufbxi_PreRotation, 11 },
{ ufbxi_PreviewDivisionLevels, 21 },
{ ufbxi_Properties60, 12 },
{ ufbxi_Properties70, 12 },
{ ufbxi_PropertyTemplate, 16 },
{ ufbxi_R, 1 },
{ ufbxi_ReferenceStart, 14 },
{ ufbxi_ReferenceStop, 13 },
{ ufbxi_ReferenceTime, 13 },
{ ufbxi_RelativeFileName, 16 },
{ ufbxi_RelativeFilename, 16 },
{ ufbxi_RenderDivisionLevels, 20 },
{ ufbxi_RightCamera, 11 },
{ ufbxi_Root, 4 },
{ ufbxi_RootNode, 8 },
{ ufbxi_Rotation, 8 },
{ ufbxi_RotationAccumulationMode, 24 },
{ ufbxi_RotationOffset, 14 },
{ ufbxi_RotationOrder, 13 },
{ ufbxi_RotationPivot, 13 },
{ ufbxi_S, 1 },
{ ufbxi_ScaleAccumulationMode, 21 },
{ ufbxi_Scaling, 7 },
{ ufbxi_ScalingOffset, 13 },
{ ufbxi_ScalingPivot, 12 },
{ ufbxi_SceneInfo, 9 },
{ ufbxi_SelectionNode, 13 },
{ ufbxi_SelectionSet, 12 },
{ ufbxi_ShadingModel, 12 },
{ ufbxi_Shape, 5 },
{ ufbxi_Shininess, 9 },
{ ufbxi_Show, 4 },
{ ufbxi_Size, 4 },
{ ufbxi_Skin, 4 },
{ ufbxi_SkinningType, 12 },
{ ufbxi_Smoothing, 9 },
{ ufbxi_Smoothness, 10 },
{ ufbxi_SnapOnFrameMode, 15 },
{ ufbxi_SpecularColor, 13 },
{ ufbxi_Step, 4 },
{ ufbxi_SubDeformer, 11 },
{ ufbxi_T, 1 },
{ ufbxi_Take, 4 },
{ ufbxi_Takes, 5 },
{ ufbxi_Tangents, 8 },
{ ufbxi_TangentsIndex, 13 },
{ ufbxi_TangentsW, 9 },
{ ufbxi_Texture, 7 },
{ ufbxi_Texture_alpha, 13 },
{ ufbxi_TextureId, 9 },
{ ufbxi_TextureRotationPivot, 20 },
{ ufbxi_TextureScalingPivot, 19 },
{ ufbxi_TextureUV, 9 },
{ ufbxi_TextureUVVerticeIndex, 21 },
{ ufbxi_Thumbnail, 9 },
{ ufbxi_TimeMarker, 10 },
{ ufbxi_TimeMode, 8 },
{ ufbxi_TimeProtocol, 12 },
{ ufbxi_TimeSpanStart, 13 },
{ ufbxi_TimeSpanStop, 12 },
{ ufbxi_Transform, 9 },
{ ufbxi_TransformLink, 13 },
{ ufbxi_Translation, 11 },
{ ufbxi_TrimNurbsSurface, 16 },
{ ufbxi_Type, 4 },
{ ufbxi_TypedIndex, 10 },
{ ufbxi_UV, 2 },
{ ufbxi_UVIndex, 7 },
{ ufbxi_UVSet, 5 },
{ ufbxi_UVSwap, 6 },
{ ufbxi_UnitScaleFactor, 15 },
{ ufbxi_UpAxis, 6 },
{ ufbxi_UpAxisSign, 10 },
{ ufbxi_Version5, 8 },
{ ufbxi_VertexCacheDeformer, 19 },
{ ufbxi_VertexCrease, 12 },
{ ufbxi_VertexCreaseIndex, 17 },
{ ufbxi_VertexIndexArray, 16 },
{ ufbxi_Vertices, 8 },
{ ufbxi_Video, 5 },
{ ufbxi_Visibility, 10 },
{ ufbxi_Weight, 6 },
{ ufbxi_Weights, 7 },
{ ufbxi_WrapModeU, 9 },
{ ufbxi_WrapModeV, 9 },
{ ufbxi_X, 1 },
{ ufbxi_Y, 1 },
{ ufbxi_Z, 1 },
{ ufbxi_d_X, 3 },
{ ufbxi_d_Y, 3 },
{ ufbxi_d_Z, 3 },
};
static const ufbx_vec3 ufbxi_one_vec3 = { 1.0f, 1.0f, 1.0f };
#define UFBXI_PI ((ufbx_real)3.14159265358979323846)
#define UFBXI_DPI (3.14159265358979323846)
#define UFBXI_DEG_TO_RAD ((ufbx_real)(UFBXI_PI / 180.0))
#define UFBXI_RAD_TO_DEG ((ufbx_real)(180.0 / UFBXI_PI))
#define UFBXI_DEG_TO_RAD_DOUBLE (UFBXI_DPI / 180.0)
#define UFBXI_RAD_TO_DEG_DOUBLE (180.0 / UFBXI_DPI)
#define UFBXI_MM_TO_INCH ((ufbx_real)0.0393700787)
ufbx_inline ufbx_vec3 ufbxi_add3(ufbx_vec3 a, ufbx_vec3 b) {
ufbx_vec3 v = { a.x + b.x, a.y + b.y, a.z + b.z };
return v;
}
ufbx_inline ufbx_vec3 ufbxi_sub3(ufbx_vec3 a, ufbx_vec3 b) {
ufbx_vec3 v = { a.x - b.x, a.y - b.y, a.z - b.z };
return v;
}
ufbx_inline ufbx_vec3 ufbxi_mul3(ufbx_vec3 a, ufbx_real b) {
ufbx_vec3 v = { a.x * b, a.y * b, a.z * b };
return v;
}
ufbx_inline ufbx_vec3 ufbxi_lerp3(ufbx_vec3 a, ufbx_vec3 b, ufbx_real t) {
ufbx_real u = 1.0f - t;
ufbx_vec3 v = { a.x*u + b.x*t, a.y*u + b.y*t, a.z*u + b.z*t };
return v;
}
ufbx_inline ufbx_real ufbxi_dot3(ufbx_vec3 a, ufbx_vec3 b) {
return a.x*b.x + a.y*b.y + a.z*b.z;
}
ufbx_inline ufbx_real ufbxi_length3(ufbx_vec3 v)
{
return (ufbx_real)ufbx_sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
}
ufbx_inline ufbx_real ufbxi_min3(ufbx_vec3 v)
{
return ufbxi_min_real(ufbxi_min_real(v.x, v.y), v.z);
}
ufbx_inline ufbx_vec3 ufbxi_cross3(ufbx_vec3 a, ufbx_vec3 b) {
ufbx_vec3 v = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x };
return v;
}
ufbx_inline ufbx_vec3 ufbxi_normalize3(ufbx_vec3 a) {
ufbx_real len = (ufbx_real)ufbx_sqrt(ufbxi_dot3(a, a));
if (len > UFBX_EPSILON) {
return ufbxi_mul3(a, (ufbx_real)1.0 / len);
} else {
ufbx_vec3 zero = { (ufbx_real)0 };
return zero;
}
}
static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalize3(const ufbx_vec3 *a) {
return ufbxi_normalize3(*a);
}
static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalized_cross3(const ufbx_vec3 *a, const ufbx_vec3 *b) {
return ufbxi_normalize3(ufbxi_cross3(*a, *b));
}
// -- Threading
typedef struct ufbxi_task ufbxi_task;
typedef struct ufbxi_thread ufbxi_thread;
typedef struct ufbxi_thread_pool ufbxi_thread_pool;
typedef bool ufbxi_task_fn(ufbxi_task *task);
struct ufbxi_task {
void *data;
const char *error;
};
typedef struct {
ufbxi_task task;
ufbxi_task_fn *fn;
} ufbxi_task_imp;
typedef struct {
uint32_t max_index;
uint32_t wait_index;
} ufbxi_task_group;
struct ufbxi_thread_pool {
ufbx_thread_opts opts;
ufbxi_allocator *ator;
ufbx_error *error;
void *user_ptr;
bool enabled;
bool failed;
const char *error_desc;
uint32_t start_index;
uint32_t execute_index;
uint32_t wait_index;
ufbxi_task_group groups[UFBX_THREAD_GROUP_COUNT];
uint32_t group;
uint32_t num_tasks;
ufbxi_task_imp *tasks;
};
static void ufbxi_thread_pool_execute(ufbxi_thread_pool *pool, uint32_t index)
{
ufbxi_task_imp *imp = &pool->tasks[index % pool->num_tasks];
if (imp->fn(&imp->task)) {
imp->task.error = NULL;
} else if (!imp->task.error) {
imp->task.error = "";
}
}
ufbxi_noinline static void ufbxi_thread_pool_update_finished(ufbxi_thread_pool *pool, uint32_t max_index)
{
while (pool->wait_index < max_index) {
ufbxi_task_imp *task = &pool->tasks[pool->wait_index % pool->num_tasks];
if (!pool->failed && task->task.error) {
pool->failed = true;
pool->error_desc = task->task.error;
}
pool->wait_index += 1;
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_imp(ufbxi_thread_pool *pool, uint32_t group, bool can_fail)
{
uint32_t max_index = pool->groups[group].max_index;
if (pool->groups[group].wait_index < max_index) {
pool->opts.pool.wait_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, group, max_index);
pool->groups[group].wait_index = max_index;
}
ufbxi_thread_pool_update_finished(pool, max_index);
if (pool->failed && can_fail) {
ufbx_error *error = pool->error;
if (pool->error_desc) {
error->description.data = pool->error_desc;
error->description.length = strlen(pool->error_desc);
}
ufbxi_fail_err(error, "Task failed");
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_group(ufbxi_thread_pool *pool)
{
ufbxi_check_err(pool->error, ufbxi_thread_pool_wait_imp(pool, pool->group, true));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_all(ufbxi_thread_pool *pool)
{
for (uint32_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) {
ufbxi_check_err(pool->error, ufbxi_thread_pool_wait_imp(pool, pool->group, true));
pool->group = (pool->group + 1) % UFBX_THREAD_GROUP_COUNT;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_init(ufbxi_thread_pool *pool, ufbx_error *error, ufbxi_allocator *ator, const ufbx_thread_opts *opts)
{
if (!(opts->pool.run_fn && opts->pool.wait_fn)) return 1;
pool->enabled = true;
uint32_t num_tasks = (uint32_t)ufbxi_min_sz(opts->num_tasks, INT32_MAX);
if (num_tasks == 0) {
num_tasks = 2048;
}
pool->opts = *opts;
if (pool->opts.pool.init_fn) {
ufbx_thread_pool_info info; // ufbxi_uninit
info.max_concurrent_tasks = num_tasks;
ufbxi_check_err(error, pool->opts.pool.init_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, &info));
}
pool->ator = ator;
pool->error = error;
pool->num_tasks = num_tasks;
pool->tasks = ufbxi_alloc(ator, ufbxi_task_imp, num_tasks);
ufbxi_check_err(error, pool->tasks);
return 1;
}
ufbxi_noinline static void ufbxi_thread_pool_free(ufbxi_thread_pool *pool)
{
if (!pool->enabled) return;
// Wait for all pending tasks
for (uint32_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) {
pool->group = (pool->group + 1) % UFBX_THREAD_GROUP_COUNT;
ufbxi_ignore(ufbxi_thread_pool_wait_imp(pool, pool->group, false));
}
if (pool->opts.pool.free_fn) {
pool->opts.pool.free_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool);
}
ufbxi_free(pool->ator, ufbxi_task_imp, pool->tasks, pool->num_tasks);
}
ufbxi_nodiscard ufbxi_noinline static uint32_t ufbxi_thread_pool_available_tasks(ufbxi_thread_pool *pool)
{
return pool->num_tasks - (pool->start_index - pool->wait_index);
}
ufbxi_noinline static void ufbxi_thread_pool_flush_group(ufbxi_thread_pool *pool)
{
uint32_t group = pool->group;
uint32_t start_index = pool->execute_index;
uint32_t count = pool->start_index - start_index;
if (count > 0) {
if (pool->opts.pool.run_fn) {
pool->opts.pool.run_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, group, start_index, count);
}
pool->groups[group].max_index = start_index + count;
pool->execute_index = start_index + count;
}
pool->group = (group + 1) % UFBX_THREAD_GROUP_COUNT;
}
ufbxi_nodiscard ufbxi_noinline static ufbxi_task *ufbxi_thread_pool_create_task(ufbxi_thread_pool *pool, ufbxi_task_fn *fn)
{
uint32_t index = pool->start_index;
if (index - pool->wait_index >= pool->num_tasks) {
if (index - pool->wait_index >= pool->num_tasks) {
// No space left
return NULL;
}
} else if (index == INT32_MAX) {
// TODO: Expand to 64 bits if possible?
return NULL;
}
ufbxi_task_imp *imp = &pool->tasks[index % pool->num_tasks];
if (index < pool->num_tasks) {
memset(imp, 0, sizeof(ufbxi_task_imp));
}
imp->fn = fn;
return &imp->task;
}
static void ufbxi_thread_pool_run_task(ufbxi_thread_pool *pool, ufbxi_task *task)
{
(void)task;
uint32_t index = pool->start_index;
ufbx_assert(task == &pool->tasks[index % pool->num_tasks].task);
pool->start_index = index + 1;
}
// -- Type definitions
typedef struct ufbxi_node ufbxi_node;
typedef enum {
UFBXI_VALUE_NONE,
UFBXI_VALUE_NUMBER,
UFBXI_VALUE_STRING,
UFBXI_VALUE_ARRAY,
} ufbxi_value_type;
typedef union {
struct { double f; int64_t i; }; // < if `UFBXI_PROP_NUMBER`
ufbxi_sanitized_string s; // < if `UFBXI_PROP_STRING`
} ufbxi_value;
typedef struct {
void *data; // < Pointer to `size` bool/int32_t/int64_t/float/double elements
size_t size; // < Number of elements
char type; // < FBX type code: b/i/l/f/d
} ufbxi_value_array;
struct ufbxi_node {
const char *name; // < Name of the node (pooled, compare with == to ufbxi_* strings)
uint32_t num_children; // < Number of child nodes
uint8_t name_len; // < Length of `name` in bytes
// If `value_type_mask == UFBXI_PROP_ARRAY` then the node is an array
// (`array` field is valid) otherwise the node has N values in `vals`
// where the type of each value is stored in 2 bits per value from LSB.
// ie. `vals[ix]` type is `(value_type_mask >> (ix*2)) & 0x3`
uint16_t value_type_mask;
ufbxi_node *children;
union {
ufbxi_value_array *array; // if `prop_type_mask == UFBXI_PROP_ARRAY`
ufbxi_value *vals; // otherwise
};
};
typedef struct ufbxi_refcount ufbxi_refcount;
struct ufbxi_refcount {
ufbxi_refcount *parent;
void *align_0;
uint32_t self_magic;
uint32_t type_magic;
ufbxi_buf buf;
ufbxi_allocator ator;
uint64_t zero_pad_pre[8];
ufbxi_atomic_counter refcount;
uint64_t zero_pad_post[8];
};
static ufbxi_noinline void ufbxi_init_ref(ufbxi_refcount *refcount, uint32_t magic, ufbxi_refcount *parent);
static ufbxi_noinline void ufbxi_retain_ref(ufbxi_refcount *refcount);
#define ufbxi_get_imp(type, ptr) ((type*)((char*)ptr - sizeof(ufbxi_refcount)))
typedef struct {
ufbxi_refcount refcount;
ufbx_scene scene;
uint32_t magic;
ufbxi_buf string_buf;
} ufbxi_scene_imp;
ufbx_static_assert(scene_imp_offset, offsetof(ufbxi_scene_imp, scene) == sizeof(ufbxi_refcount));
typedef struct {
ufbxi_refcount refcount;
ufbx_mesh mesh;
uint32_t magic;
} ufbxi_mesh_imp;
ufbx_static_assert(mesh_imp_offset, offsetof(ufbxi_mesh_imp, mesh) == sizeof(ufbxi_refcount));
typedef struct {
// Semantic string data and length eg. for a string token
// this string doesn't include the quotes.
char *str_data;
size_t str_len;
size_t str_cap;
// Type of the token, either single character such as '{' or ':'
// or one of UFBXI_ASCII_* defines.
char type;
// Sign for integer if negative.
bool negative;
// Parsed semantic value
union {
double f64;
int64_t i64;
size_t name_len;
} value;
} ufbxi_ascii_token;
typedef struct {
size_t max_token_length;
const char *src;
const char *src_yield;
const char *src_end;
bool read_first_comment;
bool found_version;
bool parse_as_f32;
bool src_is_retained;
ufbxi_buf *retain_buf;
ufbxi_buf *src_buf;
ufbxi_ascii_token prev_token;
ufbxi_ascii_token token;
} ufbxi_ascii;
typedef struct {
const char *type;
ufbx_string sub_type;
ufbx_props props;
} ufbxi_template;
typedef struct {
uint64_t fbx_id;
uint32_t element_id;
uint32_t user_id;
} ufbxi_fbx_id_entry;
typedef struct {
uint64_t node_fbx_id;
uint64_t attr_fbx_id;
} ufbxi_fbx_attr_entry;
// Temporary connection before we resolve the element pointers
typedef struct {
uint64_t src, dst;
ufbx_string src_prop;
ufbx_string dst_prop;
} ufbxi_tmp_connection;
typedef struct {
uint64_t fbx_id;
ufbx_string name;
ufbx_props props;
ufbx_dom_node *dom_node;
} ufbxi_element_info;
typedef struct {
uint64_t bone_fbx_id;
ufbx_matrix bone_to_world;
} ufbxi_tmp_bone_pose;
typedef struct {
ufbx_string prop_name;
uint32_t *face_texture;
size_t num_faces;
bool all_same;
} ufbxi_tmp_mesh_texture;
typedef struct {
ufbxi_tmp_mesh_texture *texture_arr;
size_t texture_count;
} ufbxi_mesh_extra;
typedef struct {
int32_t material_id;
int32_t texture_id;
ufbx_string prop_name;
} ufbxi_tmp_material_texture;
typedef struct {
int32_t *blend_modes;
size_t num_blend_modes;
ufbx_real *alphas;
size_t num_alphas;
} ufbxi_texture_extra;
typedef enum {
UFBXI_OBJ_ATTRIB_POSITION,
UFBXI_OBJ_ATTRIB_UV,
UFBXI_OBJ_ATTRIB_NORMAL,
UFBXI_OBJ_ATTRIB_COLOR,
} ufbxi_obj_attrib;
#define UFBXI_OBJ_NUM_ATTRIBS 3
#define UFBXI_OBJ_NUM_ATTRIBS_EXT 4
typedef struct {
uint64_t min_ix, max_ix;
} ufbxi_obj_index_range;
typedef struct {
size_t num_faces;
size_t num_indices;
ufbxi_obj_index_range vertex_range[UFBXI_OBJ_NUM_ATTRIBS];
ufbx_node *fbx_node;
ufbx_mesh *fbx_mesh;
uint64_t fbx_node_id;
uint64_t fbx_mesh_id;
uint32_t usemtl_base;
uint32_t num_groups;
} ufbxi_obj_mesh;
typedef struct {
const char *name;
uint32_t local_id;
uint32_t mesh_id;
} ufbxi_obj_group_entry;
typedef struct {
uint64_t *indices;
size_t num_left;
} ufbxi_obj_fast_indices;
// Temporary pointer to a `ufbx_anim_stack` by name used to patch start/stop
// time from "Takes" if necessary.
typedef struct {
const char *name;
ufbx_anim_stack *stack;
} ufbxi_tmp_anim_stack;
typedef struct {
ufbx_string absolute_filename;
ufbx_blob content;
} ufbxi_file_content;
typedef struct {
// Current line and tokens.
// NOTE: `line` and `tokens` are not NULL-terminated nor UTF-8!
// `line` is guaranteed to be terminated by a `\n`
ufbx_string line;
ufbx_string *tokens;
size_t tokens_cap;
size_t num_tokens;
ufbxi_obj_fast_indices fast_indices[UFBXI_OBJ_NUM_ATTRIBS];
size_t vertex_count[UFBXI_OBJ_NUM_ATTRIBS_EXT];
ufbxi_buf tmp_vertices[UFBXI_OBJ_NUM_ATTRIBS_EXT];
ufbxi_buf tmp_indices[UFBXI_OBJ_NUM_ATTRIBS_EXT];
ufbxi_buf tmp_color_valid;
ufbxi_buf tmp_faces;
ufbxi_buf tmp_face_smoothing;
ufbxi_buf tmp_face_group;
ufbxi_buf tmp_face_group_infos;
ufbxi_buf tmp_face_material;
ufbxi_buf tmp_meshes;
ufbxi_buf tmp_props;
ufbxi_map group_map;
size_t read_progress;
ufbxi_obj_mesh *mesh;
uint64_t usemtl_fbx_id;
uint32_t usemtl_index;
ufbx_string usemtl_name;
uint32_t face_material;
uint32_t face_group;
bool has_face_group;
bool face_smoothing;
bool has_face_smoothing;
bool has_vertex_color;
size_t mrgb_vertex_count;
bool eof;
bool initialized;
ufbx_blob mtllib_relative_path;
ufbx_material **tmp_materials;
size_t tmp_materials_cap;
ufbx_string object;
ufbx_string group;
bool material_dirty;
bool object_dirty;
bool group_dirty;
bool face_group_dirty;
} ufbxi_obj_context;
typedef struct {
ufbx_error error;
uint32_t version;
ufbx_exporter exporter;
uint32_t exporter_version;
bool from_ascii;
bool local_big_endian;
bool file_big_endian;
bool sure_fbx;
bool retain_mesh_parts;
bool read_legacy_settings;
uint32_t double_parse_flags;
ufbx_load_opts opts;
// IO
uint64_t data_offset;
ufbx_read_fn *read_fn;
ufbx_skip_fn *skip_fn;
ufbx_close_fn *close_fn;
void *read_user;
char *read_buffer;
size_t read_buffer_size;
const char *data_begin;
const char *data;
size_t yield_size;
size_t data_size;
// Allocators
ufbxi_allocator ator_result;
ufbxi_allocator ator_tmp;
// Temporary maps
ufbxi_map prop_type_map; // < `ufbxi_prop_type_name` Property type to enum
ufbxi_map fbx_id_map; // < `ufbxi_fbx_id_entry` FBX ID to local ID
ufbxi_map texture_file_map; // < `ufbxi_texture_file_entry` absolute raw filename to element ID
ufbxi_map anim_stack_map; // < `ufbxi_tmp_anim_stack` anim stacks by name before finalization
// 6x00 specific maps
ufbxi_map fbx_attr_map; // < `ufbxi_fbx_attr_entry` Node ID to attrib ID
ufbxi_map node_prop_set; // < `const char*` Node property names
// DOM nodes
ufbxi_map dom_node_map; // < `const char*` Node property names
// Temporary array
char *tmp_arr;
size_t tmp_arr_size;
char *swap_arr;
size_t swap_arr_size;
// Generated index buffers
size_t max_zero_indices;
size_t max_consecutive_indices;
// Temporary buffers
ufbxi_buf tmp;
ufbxi_buf tmp_parse;
ufbxi_buf tmp_stack;
ufbxi_buf tmp_connections;
ufbxi_buf tmp_node_ids;
ufbxi_buf tmp_elements;
ufbxi_buf tmp_element_offsets;
ufbxi_buf tmp_element_fbx_ids;
ufbxi_buf tmp_element_ptrs;
ufbxi_buf tmp_typed_element_offsets[UFBX_ELEMENT_TYPE_COUNT];
ufbxi_buf tmp_mesh_textures;
ufbxi_buf tmp_full_weights;
ufbxi_buf tmp_dom_nodes;
ufbxi_buf tmp_element_id;
ufbxi_buf tmp_ascii_spans;
ufbxi_buf tmp_thread_parse[UFBX_THREAD_GROUP_COUNT];
size_t tmp_element_byte_offset;
ufbxi_template *templates;
size_t num_templates;
ufbx_dom_node *dom_parse_toplevel;
size_t dom_parse_num_children;
uint32_t *p_element_id;
// String pool
ufbxi_string_pool string_pool;
// Result buffers, these are retained in `ufbx_scene` returned to user.
ufbxi_buf result;
// Top-level state
ufbxi_node *top_nodes;
size_t top_nodes_len, top_nodes_cap;
bool parsed_to_end;
// "Focused" top-level node and child index, if `top_child_index == SIZE_MAX`
// the children are parsed on demand.
ufbxi_node *top_node;
size_t top_child_index;
ufbxi_node top_child;
bool has_next_child;
// Shared consecutive and all-zero index buffers
uint32_t *zero_indices;
uint32_t *consecutive_indices;
// Call progress function periodically
ptrdiff_t progress_timer;
uint64_t progress_bytes_total;
uint64_t latest_progress_bytes;
size_t progress_interval;
// Extra data on the side of elements
void **element_extra_arr;
size_t element_extra_cap;
// Temporary per-element flags
uint8_t *tmp_element_flag;
ufbxi_ascii ascii;
bool has_geometry_transform_nodes;
bool has_scale_helper_nodes;
bool retain_vertex_w;
ufbx_mirror_axis mirror_axis;
ufbxi_node root;
ufbx_scene scene;
ufbxi_scene_imp *scene_imp;
ufbx_inflate_retain *inflate_retain;
// Per-mesh consecutive indices used by `ufbxi_flip_winding()`.
uint32_t *tmp_mesh_consecutive_indices;
uint64_t root_id;
uint32_t num_elements;
ufbxi_node legacy_node;
uint64_t legacy_implicit_anim_layer_id;
ufbxi_file_content *file_content;
size_t num_file_content;
int64_t ktime_sec;
double ktime_sec_double;
bool eof;
ufbxi_obj_context obj;
ufbx_matrix axis_matrix;
ufbx_real unit_scale;
ufbxi_warnings warnings;
bool deferred_failure;
bool parse_threaded;
ufbxi_thread_pool thread_pool;
} ufbxi_context;
static ufbxi_noinline int ufbxi_fail_imp(ufbxi_context *uc, const char *cond, const char *func, uint32_t line)
{
return ufbxi_fail_imp_err(&uc->error, cond, func, line);
}
#define ufbxi_check(cond) if (ufbxi_unlikely(!ufbxi_trace(cond))) return ufbxi_fail_imp(uc, ufbxi_cond_str(cond), ufbxi_function, ufbxi_line)
#define ufbxi_check_return(cond, ret) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp(uc, ufbxi_cond_str(cond), ufbxi_function, ufbxi_line); return ret; } } while (0)
#define ufbxi_fail(desc) return ufbxi_fail_imp(uc, desc, ufbxi_function, ufbxi_line)
#define ufbxi_fail_return(desc, ret) do { ufbxi_fail_imp(uc, desc, ufbxi_function, ufbxi_line); return ret; } while (0)
#define ufbxi_check_msg(cond, msg) if (ufbxi_unlikely(!ufbxi_trace(cond))) return ufbxi_fail_imp(uc, ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line)
#define ufbxi_check_return_msg(cond, ret, msg) do { if (ufbxi_unlikely(!ufbxi_trace(cond))) { ufbxi_fail_imp(uc, ufbxi_error_msg(ufbxi_cond_str(cond), msg), ufbxi_function, ufbxi_line); return ret; } } while (0)
#define ufbxi_fail_msg(desc, msg) return ufbxi_fail_imp(uc, ufbxi_error_msg(desc, msg), ufbxi_function, ufbxi_line)
#define ufbxi_warnf(type, ...) ufbxi_warnf_imp(&uc->warnings, type, ~0u, __VA_ARGS__)
#define ufbxi_warnf_tag(type, element_id, ...) ufbxi_warnf_imp(&uc->warnings, type, (element_id), __VA_ARGS__)
// -- Progress
static ufbxi_forceinline uint64_t ufbxi_get_read_offset(ufbxi_context *uc)
{
return uc->data_offset + ufbxi_to_size(uc->data - uc->data_begin);
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_report_progress(ufbxi_context *uc)
{
if (!uc->opts.progress_cb.fn) return 1;
uint64_t read_offset = ufbxi_get_read_offset(uc);
uc->latest_progress_bytes = read_offset;
ufbx_progress progress = { 0 };
progress.bytes_read = read_offset;
progress.bytes_total = uc->progress_bytes_total;
if (progress.bytes_total < progress.bytes_read) {
progress.bytes_total = progress.bytes_read;
}
uc->progress_timer = 1024;
uint32_t result = (uint32_t)uc->opts.progress_cb.fn(uc->opts.progress_cb.user, &progress);
ufbx_assert(result == UFBX_PROGRESS_CONTINUE || result == UFBX_PROGRESS_CANCEL);
ufbxi_check_msg(result != UFBX_PROGRESS_CANCEL, "Cancelled");
return 1;
}
// TODO: Remove `ufbxi_unused` when it's not needed anymore
ufbxi_unused ufbxi_nodiscard static ufbxi_forceinline int ufbxi_progress(ufbxi_context *uc, size_t work_units)
{
if (!uc->opts.progress_cb.fn) return 1;
ptrdiff_t left = uc->progress_timer - (ptrdiff_t)work_units;
uc->progress_timer = left;
if (left > 0) return 1;
return ufbxi_report_progress(uc);
}
// -- IO
static ufbxi_noinline const char *ufbxi_refill(ufbxi_context *uc, size_t size, bool require_size)
{
ufbx_assert(uc->data_size < size);
ufbxi_check_return(!uc->eof, NULL);
if (require_size) {
ufbxi_check_return_msg(uc->read_fn || uc->data_size > 0, NULL, "Empty file");
ufbxi_check_return_msg(uc->read_fn, NULL, "Truncated file");
} else if (!uc->read_fn) {
uc->eof = true;
return uc->data;
}
void *data_to_free = NULL;
size_t size_to_free = 0;
// Grow the read buffer if necessary, data is copied over below with the
// usual path so the free is deferred (`size_to_free`, `data_to_free`)
if (size > uc->read_buffer_size) {
size_t new_size = ufbxi_max_sz(size, uc->opts.read_buffer_size);
new_size = ufbxi_max_sz(new_size, uc->read_buffer_size * 2);
size_to_free = uc->read_buffer_size;
data_to_free = uc->read_buffer;
char *new_buffer = ufbxi_alloc(&uc->ator_tmp, char, new_size);
ufbxi_check_return(new_buffer, NULL);
uc->read_buffer = new_buffer;
uc->read_buffer_size = new_size;
}
// Copy the remains of the previous buffer to the beginning of the new one
size_t data_size = uc->data_size;
if (data_size > 0) {
ufbx_assert(uc->read_buffer != NULL && uc->data != NULL);
memmove(uc->read_buffer, uc->data, data_size);
}
if (size_to_free) {
ufbxi_free(&uc->ator_tmp, char, data_to_free, size_to_free);
}
// Fill the rest of the buffer with user data
size_t data_capacity = uc->read_buffer_size;
while (data_size < data_capacity) {
size_t to_read = data_capacity - data_size;
size_t read_result = uc->read_fn(uc->read_user, uc->read_buffer + data_size, to_read);
ufbxi_check_return_msg(read_result != SIZE_MAX, NULL, "IO error");
ufbxi_check_return(read_result <= to_read, NULL);
data_size += read_result;
if (read_result == 0) {
uc->eof = true;
break;
}
}
if (require_size) {
if (uc->data_offset == 0) {
ufbxi_check_return_msg(data_size > 0, NULL, "Empty file");
}
ufbxi_check_return_msg(data_size >= size, NULL, "Truncated file");
}
uc->data_offset += ufbxi_to_size(uc->data - uc->data_begin);
uc->data_begin = uc->data = uc->read_buffer;
uc->data_size = data_size;
return uc->read_buffer;
}
static ufbxi_forceinline void ufbxi_pause_progress(ufbxi_context *uc)
{
uc->data_size += uc->yield_size;
uc->yield_size = 0;
}
static ufbxi_noinline int ufbxi_resume_progress(ufbxi_context *uc)
{
uc->yield_size = ufbxi_min_sz(uc->data_size, uc->progress_interval);
uc->data_size -= uc->yield_size;
if (ufbxi_get_read_offset(uc) - uc->latest_progress_bytes >= uc->progress_interval) {
ufbxi_check(ufbxi_report_progress(uc));
}
return 1;
}
static ufbxi_noinline const char *ufbxi_yield(ufbxi_context *uc, size_t size)
{
const char *ret;
uc->data_size += uc->yield_size;
if (uc->data_size >= size) {
ret = uc->data;
} else {
ret = ufbxi_refill(uc, size, true);
}
uc->yield_size = ufbxi_min_sz(uc->data_size, ufbxi_max_sz(size, uc->progress_interval));
uc->data_size -= uc->yield_size;
ufbxi_check_return(ufbxi_report_progress(uc), NULL);
return ret;
}
static ufbxi_forceinline const char *ufbxi_peek_bytes(ufbxi_context *uc, size_t size)
{
if (uc->yield_size >= size) {
return uc->data;
} else {
return ufbxi_yield(uc, size);
}
}
static ufbxi_forceinline const char *ufbxi_read_bytes(ufbxi_context *uc, size_t size)
{
// Refill the current buffer if necessary
const char *ret;
if (uc->yield_size >= size) {
ret = uc->data;
} else {
ret = ufbxi_yield(uc, size);
if (!ret) return NULL;
}
// Advance the read position inside the current buffer
uc->yield_size -= size;
uc->data = ret + size;
return ret;
}
static ufbxi_forceinline void ufbxi_consume_bytes(ufbxi_context *uc, size_t size)
{
// Bytes must have been checked first with `ufbxi_peek_bytes()`
ufbx_assert(size <= uc->yield_size);
uc->yield_size -= size;
uc->data += size;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_skip_bytes(ufbxi_context *uc, uint64_t size)
{
if (uc->skip_fn) {
ufbxi_pause_progress(uc);
if (size > uc->data_size) {
size -= uc->data_size;
uc->data += uc->data_size;
uc->data_size = 0;
uc->data_offset += size;
while (size >= UFBXI_MAX_SKIP_SIZE) {
size -= UFBXI_MAX_SKIP_SIZE;
ufbxi_check_msg(uc->skip_fn(uc->read_user, UFBXI_MAX_SKIP_SIZE - 1), "Truncated file");
// Check that we can read at least one byte in case the file is broken
// and causes us to seek indefinitely forwards as `fseek()` does not
// report if we hit EOF...
char single_byte[1]; // ufbxi_uninit
size_t num_read = uc->read_fn(uc->read_user, single_byte, 1);
ufbxi_check_msg(num_read <= 1, "IO error");
ufbxi_check_msg(num_read == 1, "Truncated file");
}
if (size > 0) {
ufbxi_check_msg(uc->skip_fn(uc->read_user, (size_t)size), "Truncated file");
}
} else {
uc->data += (size_t)size;
uc->data_size -= (size_t)size;
}
ufbxi_check(ufbxi_resume_progress(uc));
} else {
// Read and discard bytes in reasonable chunks
uint64_t skip_size = ufbxi_max64(uc->read_buffer_size, uc->opts.read_buffer_size);
while (size > 0) {
uint64_t to_skip = ufbxi_min64(size, skip_size);
ufbxi_check(ufbxi_read_bytes(uc, (size_t)to_skip));
size -= to_skip;
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_to(ufbxi_context *uc, void *dst, size_t size)
{
char *ptr = (char*)dst;
ufbxi_pause_progress(uc);
// Copy data from the current buffer first
size_t len = ufbxi_min_sz(uc->data_size, size);
memcpy(ptr, uc->data, len);
uc->data += len;
uc->data_size -= len;
ptr += len;
size -= len;
// If there's data left to copy try to read from user IO
if (size > 0) {
uc->data_offset += ufbxi_to_size(uc->data - uc->data_begin);
uc->data_begin = uc->data = NULL;
uc->data_size = 0;
ufbxi_check(uc->read_fn);
while (size > 0) {
size_t read_result = uc->read_fn(uc->read_user, ptr, size);
ufbxi_check_msg(read_result != SIZE_MAX, "IO error");
ufbxi_check(read_result != 0);
ptr += read_result;
size -= read_result;
uc->data_offset += read_result;
}
}
ufbxi_check(ufbxi_resume_progress(uc));
return 1;
}
// -- File IO
static ufbxi_noinline void ufbxi_init_ator(ufbx_error *error, ufbxi_allocator *ator, const ufbx_allocator_opts *opts, const char *name)
{
ufbx_allocator_opts zero_opts;
if (!opts) {
memset(&zero_opts, 0, sizeof(zero_opts));
opts = &zero_opts;
}
// `opts` is either passed in or `zero_opts`.
// cppcheck-suppress uninitvar
ator->ator = *opts;
ator->error = error;
ator->max_size = opts->memory_limit ? opts->memory_limit : SIZE_MAX;
ator->max_allocs = opts->allocation_limit ? opts->allocation_limit : SIZE_MAX;
ator->huge_size = opts->huge_threshold ? opts->huge_threshold : 0x100000;
ator->chunk_max = opts->max_chunk_size ? opts->max_chunk_size : 0x1000000;
ator->name = name;
}
static ufbxi_noinline FILE *ufbxi_fopen(const char *path, size_t path_len, ufbxi_allocator *tmp_ator)
{
#if !defined(UFBX_STANDARD_C) && defined(_WIN32)
wchar_t wpath_buf[256];
wchar_t *wpath = NULL;
if (path_len == SIZE_MAX) {
path_len = strlen(path);
}
if (path_len < ufbxi_arraycount(wpath_buf) - 1) {
wpath = wpath_buf;
} else {
wpath = ufbxi_alloc(tmp_ator, wchar_t, path_len + 1);
if (!wpath) return NULL;
}
// Convert UTF-8 to UTF-16 but allow stray surrogate pairs as the Windows
// file system encoding allows them as well..
size_t wlen = 0;
for (size_t i = 0; i < path_len; ) {
uint32_t code = UINT32_MAX;
char c = path[i++];
if ((c & 0x80) == 0) {
code = (uint32_t)c;
} else if ((c & 0xe0) == 0xc0) {
code = (uint32_t)(c & 0x1f);
if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f);
} else if ((c & 0xf0) == 0xe0) {
code = (uint32_t)(c & 0x0f);
if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f);
if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f);
} else if ((c & 0xf8) == 0xf0) {
code = (uint32_t)(c & 0x07);
if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f);
if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f);
if (i < path_len) code = code << 6 | (uint32_t)(path[i++] & 0x3f);
}
if (code < 0x10000) {
wpath[wlen++] = (wchar_t)code;
} else {
code -= 0x10000;
wpath[wlen++] = (wchar_t)(0xd800 + (code >> 10));
wpath[wlen++] = (wchar_t)(0xdc00 + (code & 0x3ff));
}
}
wpath[wlen] = 0;
FILE *file = NULL;
#if UFBXI_MSC_VER >= 1400
if (_wfopen_s(&file, wpath, L"rb") != 0) {
file = NULL;
}
#else
file = _wfopen(wpath, L"rb");
#endif
if (wpath != wpath_buf) {
ufbxi_free(tmp_ator, wchar_t, wpath, path_len + 1);
}
return file;
#else
if (path_len == SIZE_MAX) {
return fopen(path, "rb");
}
char copy_buf[256]; // ufbxi_uninit
char *copy = NULL;
if (path_len < ufbxi_arraycount(copy_buf) - 1) {
copy = copy_buf;
} else {
copy = ufbxi_alloc(tmp_ator, char, path_len + 1);
if (!copy) return NULL;
}
memcpy(copy, path, path_len);
copy[path_len] = '\0';
FILE *file = fopen(copy, "rb");
if (copy != copy_buf) {
ufbxi_free(tmp_ator, char, copy, path_len + 1);
}
return file;
#endif
}
static uint64_t ufbxi_ftell(FILE *file)
{
#if !defined(UFBX_STANDARD_C) && defined(UFBX_HAS_FTELLO)
off_t result = ftello(file);
if (result >= 0) return (uint64_t)result;
#elif !defined(UFBX_STANDARD_C) && defined(_MSC_VER)
int64_t result = _ftelli64(file);
if (result >= 0) return (uint64_t)result;
#else
long result = ftell(file);
if (result >= 0) return (uint64_t)result;
#endif
return UINT64_MAX;
}
static size_t ufbxi_file_read(void *user, void *data, size_t max_size)
{
FILE *file = (FILE*)user;
if (ferror(file)) return SIZE_MAX;
return fread(data, 1, max_size, file);
}
static bool ufbxi_file_skip(void *user, size_t size)
{
FILE *file = (FILE*)user;
ufbx_assert(size <= UFBXI_MAX_SKIP_SIZE);
if (fseek(file, (long)size, SEEK_CUR) != 0) return false;
if (ferror(file)) return false;
return true;
}
static void ufbxi_file_close(void *user)
{
FILE *file = (FILE*)user;
fclose(file);
}
typedef struct {
const void *data;
size_t size;
size_t position;
ufbx_close_memory_cb close_cb;
// Own allocation information
size_t self_size;
ufbxi_allocator ator;
ufbx_error error;
char data_copy[];
} ufbxi_memory_stream;
static size_t ufbxi_memory_read(void *user, void *data, size_t max_size)
{
ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user;
size_t to_read = ufbxi_min_sz(stream->size - stream->position, max_size);
memcpy(data, (const char*)stream->data + stream->position, to_read);
stream->position += to_read;
return to_read;
}
static bool ufbxi_memory_skip(void *user, size_t size)
{
ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user;
if (stream->size - stream->position < size) return false;
stream->position += size;
return true;
}
static void ufbxi_memory_close(void *user)
{
ufbxi_memory_stream *stream = (ufbxi_memory_stream*)user;
if (stream->close_cb.fn) {
stream->close_cb.fn(stream->close_cb.user, (void*)stream->data, stream->size);
}
ufbxi_allocator ator = stream->ator;
ufbxi_free(&ator, char, stream, stream->self_size);
ufbxi_free_ator(&ator);
}
// -- XML
#if UFBXI_FEATURE_XML
typedef struct ufbxi_xml_tag ufbxi_xml_tag;
typedef struct ufbxi_xml_attrib ufbxi_xml_attrib;
typedef struct ufbxi_xml_document ufbxi_xml_document;
struct ufbxi_xml_attrib {
ufbx_string name;
ufbx_string value;
};
struct ufbxi_xml_tag {
ufbx_string name;
ufbx_string text;
ufbxi_xml_attrib *attribs;
size_t num_attribs;
ufbxi_xml_tag *children;
size_t num_children;
};
struct ufbxi_xml_document {
ufbxi_xml_tag *root;
ufbxi_buf buf;
};
typedef struct {
ufbx_error error;
ufbxi_allocator *ator;
ufbxi_buf tmp_stack;
ufbxi_buf result;
ufbxi_xml_document *doc;
ufbx_read_fn *read_fn;
void *read_user;
char *tok;
size_t tok_cap;
size_t tok_len;
const char *pos, *pos_end;
char data[4096];
bool io_error;
} ufbxi_xml_context;
enum {
UFBXI_XML_CTYPE_WHITESPACE = 0x1,
UFBXI_XML_CTYPE_SINGLE_QUOTE = 0x2,
UFBXI_XML_CTYPE_DOUBLE_QUOTE = 0x4,
UFBXI_XML_CTYPE_NAME_END = 0x8,
UFBXI_XML_CTYPE_TAG_START = 0x10,
UFBXI_XML_CTYPE_END_OF_FILE = 0x20,
};
// Generated by `misc/gen_xml_ctype.py`
static const uint8_t ufbxi_xml_ctype[256] = {
32,0,0,0,0,0,0,0,0,9,9,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
9,0,12,0,0,0,0,10,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,16,8,8,8,
};
static ufbxi_noinline void ufbxi_xml_refill(ufbxi_xml_context *xc)
{
size_t num = xc->read_fn(xc->read_user, xc->data, sizeof(xc->data));
if (num == SIZE_MAX || num < sizeof(xc->data)) xc->io_error = true;
if (num < sizeof(xc->data)) {
xc->data[num++] = '\0';
}
xc->pos = xc->data;
xc->pos_end = xc->data + num;
}
static ufbxi_forceinline void ufbxi_xml_advance(ufbxi_xml_context *xc)
{
if (++xc->pos == xc->pos_end) ufbxi_xml_refill(xc);
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_xml_push_token_char(ufbxi_xml_context *xc, char c)
{
if (xc->tok_len == xc->tok_cap || UFBXI_IS_REGRESSION) {
ufbxi_check_err(&xc->error, ufbxi_grow_array(xc->ator, &xc->tok, &xc->tok_cap, xc->tok_len + 1));
}
xc->tok[xc->tok_len++] = c;
return 1;
}
static ufbxi_noinline int ufbxi_xml_accept(ufbxi_xml_context *xc, char ch)
{
if (*xc->pos == ch) {
ufbxi_xml_advance(xc);
return 1;
} else {
return 0;
}
}
static ufbxi_noinline void ufbxi_xml_skip_while(ufbxi_xml_context *xc, uint32_t ctypes)
{
while (ufbxi_xml_ctype[(uint8_t)*xc->pos] & ctypes) {
ufbxi_xml_advance(xc);
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_xml_skip_until_string(ufbxi_xml_context *xc, ufbx_string *dst, const char *suffix)
{
xc->tok_len = 0;
size_t match_len = 0, ix = 0, suffix_len = strlen(suffix);
char buf[16] = { 0 };
size_t wrap_mask = sizeof(buf) - 1;
ufbx_assert(suffix_len < sizeof(buf));
for (;;) {
char c = *xc->pos;
ufbxi_check_err_msg(&xc->error, c != 0, "Truncated file");
ufbxi_xml_advance(xc);
if (ix >= suffix_len) {
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, buf[(ix - suffix_len) & wrap_mask]));
}
buf[ix++ & wrap_mask] = c;
for (match_len = 0; match_len < suffix_len; match_len++) {
if (buf[(ix - suffix_len + match_len) & wrap_mask] != suffix[match_len]) {
break;
}
}
if (match_len == suffix_len) break;
}
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0'));
if (dst) {
dst->length = xc->tok_len - 1;
dst->data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok);
ufbxi_check_err(&xc->error, dst->data);
}
return 1;
}
static ufbxi_noinline int ufbxi_xml_read_until(ufbxi_xml_context *xc, ufbx_string *dst, uint32_t ctypes)
{
xc->tok_len = 0;
for (;;) {
char c = *xc->pos;
if (c == '&') {
size_t entity_begin = xc->tok_len;
for (;;) {
ufbxi_xml_advance(xc);
c = *xc->pos;
ufbxi_check_err(&xc->error, c != '\0');
if (c == ';') break;
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, c));
}
ufbxi_xml_advance(xc);
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0'));
char *entity = xc->tok + entity_begin;
xc->tok_len = entity_begin;
if (entity[0] == '#') {
unsigned long code = 0;
if (entity[1] == 'x') {
code = strtoul(entity + 2, NULL, 16);
} else {
code = strtoul(entity + 1, NULL, 10);
}
char bytes[5] = { 0 };
if (code < 0x80) {
bytes[0] = (char)code;
} else if (code < 0x800) {
bytes[0] = (char)(0xc0 | (code>>6));
bytes[1] = (char)(0x80 | (code & 0x3f));
} else if (code < 0x10000) {
bytes[0] = (char)(0xe0 | (code>>12));
bytes[1] = (char)(0x80 | ((code>>6) & 0x3f));
bytes[2] = (char)(0x80 | (code & 0x3f));
} else {
bytes[0] = (char)(0xf0 | (code>>18));
bytes[1] = (char)(0x80 | ((code>>12) & 0x3f));
bytes[2] = (char)(0x80 | ((code>>6) & 0x3f));
bytes[3] = (char)(0x80 | (code & 0x3f));
}
for (char *b = bytes; *b; b++) {
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, *b));
}
} else {
char ch = '\0';
if (!strcmp(entity, "lt")) ch = '<';
else if (!strcmp(entity, "quot")) ch = '"';
else if (!strcmp(entity, "amp")) ch = '&';
else if (!strcmp(entity, "apos")) ch = '\'';
else if (!strcmp(entity, "gt")) ch = '>';
if (ch) {
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, ch));
}
}
} else {
if ((ufbxi_xml_ctype[(uint8_t)c] & ctypes) != 0) break;
ufbxi_check_err_msg(&xc->error, c != 0, "Truncated file");
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, c));
ufbxi_xml_advance(xc);
}
}
ufbxi_check_err(&xc->error, ufbxi_xml_push_token_char(xc, '\0'));
if (dst) {
dst->length = xc->tok_len - 1;
dst->data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok);
ufbxi_check_err(&xc->error, dst->data);
}
return 1;
}
// Recursion limited by check at the start
static ufbxi_noinline int ufbxi_xml_parse_tag(ufbxi_xml_context *xc, size_t depth, bool *p_closing, const char *opening)
ufbxi_recursive_function(int, ufbxi_xml_parse_tag, (xc, depth, p_closing, opening), UFBXI_MAX_XML_DEPTH + 1,
(ufbxi_xml_context *xc, size_t depth, bool *p_closing, const char *opening))
{
ufbxi_check_err(&xc->error, depth < UFBXI_MAX_XML_DEPTH);
if (!ufbxi_xml_accept(xc, '<')) {
if (*xc->pos == '\0') {
*p_closing = true;
} else {
ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, NULL, UFBXI_XML_CTYPE_TAG_START | UFBXI_XML_CTYPE_END_OF_FILE));
bool has_text = false;
for (size_t i = 0; i < xc->tok_len; i++) {
if ((ufbxi_xml_ctype[(uint8_t)xc->tok[i]] & UFBXI_XML_CTYPE_WHITESPACE) == 0) {
has_text = true;
break;
}
}
if (has_text) {
ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1);
ufbxi_check_err(&xc->error, tag);
tag->name.data = ufbxi_empty_char;
tag->text.length = xc->tok_len - 1;
tag->text.data = ufbxi_push_copy(&xc->result, char, xc->tok_len, xc->tok);
ufbxi_check_err(&xc->error, tag->text.data);
}
}
return 1;
}
if (ufbxi_xml_accept(xc, '/')) {
ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, NULL, UFBXI_XML_CTYPE_NAME_END));
ufbxi_check_err(&xc->error, opening && !strcmp(xc->tok, opening));
ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE);
if (!ufbxi_xml_accept(xc, '>')) return 0;
*p_closing = true;
return 1;
} else if (ufbxi_xml_accept(xc, '!')) {
if (ufbxi_xml_accept(xc, '[')) {
for (const char *ch = "CDATA["; *ch; ch++) {
if (!ufbxi_xml_accept(xc, *ch)) return 0;
}
ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1);
ufbxi_check_err(&xc->error, tag);
ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, &tag->text, "]]>"));
tag->name.data = ufbxi_empty_char;
} else if (ufbxi_xml_accept(xc, '-')) {
if (!ufbxi_xml_accept(xc, '-')) return 0;
ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, "-->"));
} else {
// TODO: !DOCTYPE
ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, ">"));
}
return 1;
} else if (ufbxi_xml_accept(xc, '?')) {
ufbxi_check_err(&xc->error, ufbxi_xml_skip_until_string(xc, NULL, "?>"));
return 1;
}
ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_tag, 1);
ufbxi_check_err(&xc->error, tag);
ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &tag->name, UFBXI_XML_CTYPE_NAME_END));
tag->text.data = ufbxi_empty_char;
bool has_children = false;
size_t num_attribs = 0;
for (;;) {
ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE);
if (ufbxi_xml_accept(xc, '/')) {
if (!ufbxi_xml_accept(xc, '>')) return 0;
break;
} else if (ufbxi_xml_accept(xc, '>')) {
has_children = true;
break;
} else {
ufbxi_xml_attrib *attrib = ufbxi_push_zero(&xc->tmp_stack, ufbxi_xml_attrib, 1);
ufbxi_check_err(&xc->error, attrib);
ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &attrib->name, UFBXI_XML_CTYPE_NAME_END));
ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE);
if (!ufbxi_xml_accept(xc, '=')) return 0;
ufbxi_xml_skip_while(xc, UFBXI_XML_CTYPE_WHITESPACE);
uint32_t quote_ctype = 0;
if (ufbxi_xml_accept(xc, '"')) {
quote_ctype = UFBXI_XML_CTYPE_DOUBLE_QUOTE;
} else if (ufbxi_xml_accept(xc, '\'')) {
quote_ctype = UFBXI_XML_CTYPE_SINGLE_QUOTE;
} else {
ufbxi_fail_err(&xc->error, "Bad attrib value");
}
ufbxi_check_err(&xc->error, ufbxi_xml_read_until(xc, &attrib->value, quote_ctype));
ufbxi_xml_advance(xc);
num_attribs++;
}
}
tag->num_attribs = num_attribs;
tag->attribs = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_attrib, num_attribs);
ufbxi_check_err(&xc->error, tag->attribs);
if (has_children) {
size_t children_begin = xc->tmp_stack.num_items;
for (;;) {
bool closing = false;
ufbxi_check_err(&xc->error, ufbxi_xml_parse_tag(xc, depth + 1, &closing, tag->name.data));
if (closing) break;
}
tag->num_children = xc->tmp_stack.num_items - children_begin;
tag->children = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_tag, tag->num_children);
ufbxi_check_err(&xc->error, tag->children);
}
return 1;
}
static ufbxi_noinline int ufbxi_xml_parse_root(ufbxi_xml_context *xc)
{
ufbxi_xml_tag *tag = ufbxi_push_zero(&xc->result, ufbxi_xml_tag, 1);
ufbxi_check_err(&xc->error, tag);
tag->name.data = ufbxi_empty_char;
tag->text.data = ufbxi_empty_char;
for (;;) {
bool closing = false;
ufbxi_check_err(&xc->error, ufbxi_xml_parse_tag(xc, 0, &closing, NULL));
if (closing) break;
}
tag->num_children = xc->tmp_stack.num_items;
tag->children = ufbxi_push_pop(&xc->result, &xc->tmp_stack, ufbxi_xml_tag, tag->num_children);
ufbxi_check_err(&xc->error, tag->children);
xc->doc = ufbxi_push(&xc->result, ufbxi_xml_document, 1);
ufbxi_check_err(&xc->error, xc->doc);
xc->doc->root = tag;
xc->doc->buf = xc->result;
return 1;
}
typedef struct {
ufbxi_allocator *ator;
ufbx_read_fn *read_fn;
void *read_user;
const char *prefix;
size_t prefix_length;
} ufbxi_xml_load_opts;
static ufbxi_noinline ufbxi_xml_document *ufbxi_load_xml(ufbxi_xml_load_opts *opts, ufbx_error *error)
{
ufbxi_xml_context xc = { UFBX_ERROR_NONE };
xc.ator = opts->ator;
xc.read_fn = opts->read_fn;
xc.read_user = opts->read_user;
xc.tmp_stack.ator = xc.ator;
xc.result.ator = xc.ator;
xc.result.unordered = true;
if (opts->prefix_length > 0) {
xc.pos = opts->prefix;
xc.pos_end = opts->prefix + opts->prefix_length;
} else {
ufbxi_xml_refill(&xc);
}
int ok = ufbxi_xml_parse_root(&xc);
ufbxi_buf_free(&xc.tmp_stack);
ufbxi_free(xc.ator, char, xc.tok, xc.tok_cap);
if (ok) {
return xc.doc;
} else {
ufbxi_buf_free(&xc.result);
if (error) {
*error = xc.error;
}
return NULL;
}
}
static ufbxi_noinline void ufbxi_free_xml(ufbxi_xml_document *doc)
{
ufbxi_buf buf = doc->buf;
ufbxi_buf_free(&buf);
}
static ufbxi_noinline ufbxi_xml_tag *ufbxi_xml_find_child(ufbxi_xml_tag *tag, const char *name)
{
ufbxi_for(ufbxi_xml_tag, child, tag->children, tag->num_children) {
if (!strcmp(child->name.data, name)) {
return child;
}
}
return NULL;
}
static ufbxi_noinline ufbxi_xml_attrib *ufbxi_xml_find_attrib(ufbxi_xml_tag *tag, const char *name)
{
ufbxi_for(ufbxi_xml_attrib, attrib, tag->attribs, tag->num_attribs) {
if (!strcmp(attrib->name.data, name)) {
return attrib;
}
}
return NULL;
}
#endif
// -- FBX value type information
static char ufbxi_normalize_array_type(char type, char bool_type) {
switch (type) {
case 'r': return sizeof(ufbx_real) == sizeof(float) ? 'f' : 'd';
case 'b': return bool_type;
default: return type;
}
}
static ufbxi_noinline size_t ufbxi_array_type_size(char type)
{
switch (type) {
case 'r': return sizeof(ufbx_real);
case 'b': return sizeof(bool);
case 'c': return sizeof(uint8_t);
case 'i': return sizeof(int32_t);
case 'l': return sizeof(int64_t);
case 'f': return sizeof(float);
case 'd': return sizeof(double);
case 's': return sizeof(ufbx_string);
case 'S': return sizeof(ufbx_string);
case 'C': return sizeof(ufbx_string);
default: return 1;
}
}
// -- Node operations
static ufbxi_noinline ufbxi_node *ufbxi_find_child(ufbxi_node *node, const char *name)
{
ufbxi_for(ufbxi_node, c, node->children, node->num_children) {
if (c->name == name) return c;
}
return NULL;
}
// Retrieve values from nodes with type codes:
// Any: '_' (ignore)
// NUMBER: 'I' int32_t 'L' int64_t 'F' float 'D' double 'R' ufbxi_real 'B' bool 'Z' size_t
// STRING: 'S' ufbx_string 'C' const char* (checked) 's' ufbx_string 'c' const char * (unchecked) 'b' ufbx_blob
ufbxi_nodiscard ufbxi_forceinline static int ufbxi_get_val_at(ufbxi_node *node, size_t ix, char fmt, void *v)
{
ufbxi_dev_assert(ix < UFBXI_MAX_NON_ARRAY_VALUES);
ufbxi_value_type type = (ufbxi_value_type)((node->value_type_mask >> (ix*2)) & 0x3);
switch (fmt) {
case '_': return 1;
case 'I': if (type == UFBXI_VALUE_NUMBER) { *(int32_t*)v = (int32_t)node->vals[ix].i; return 1; } else return 0;
case 'L': if (type == UFBXI_VALUE_NUMBER) { *(int64_t*)v = (int64_t)node->vals[ix].i; return 1; } else return 0;
case 'F': if (type == UFBXI_VALUE_NUMBER) { *(float*)v = (float)node->vals[ix].f; return 1; } else return 0;
case 'D': if (type == UFBXI_VALUE_NUMBER) { *(double*)v = (double)node->vals[ix].f; return 1; } else return 0;
case 'R': if (type == UFBXI_VALUE_NUMBER) { *(ufbx_real*)v = (ufbx_real)node->vals[ix].f; return 1; } else return 0;
case 'B': if (type == UFBXI_VALUE_NUMBER) { *(bool*)v = node->vals[ix].i != 0; return 1; } else return 0;
case 'Z': if (type == UFBXI_VALUE_NUMBER) { if (node->vals[ix].i < 0) return 0; *(size_t*)v = (size_t)node->vals[ix].i; return 1; } else return 0;
case 'S': if (type == UFBXI_VALUE_STRING) {
ufbxi_sanitized_string src = node->vals[ix].s;
ufbx_string *dst = (ufbx_string*)v;
if (src.utf8_length > 0) {
if (src.utf8_length == UINT32_MAX) return 0;
dst->data = src.raw_data + src.raw_length + 1;
dst->length = src.utf8_length;
} else {
dst->data = src.raw_data;
dst->length = src.raw_length;
}
return 1;
} else return 0;
case 's': if (type == UFBXI_VALUE_STRING) {
ufbxi_sanitized_string src = node->vals[ix].s;
ufbx_string *dst = (ufbx_string*)v;
dst->data = src.raw_data;
dst->length = src.raw_length;
return 1;
} else return 0;
case 'C': if (type == UFBXI_VALUE_STRING) {
ufbxi_sanitized_string src = node->vals[ix].s;
const char **dst = (const char **)v;
if (src.utf8_length > 0) {
if (src.utf8_length == UINT32_MAX) return 0;
*dst = src.raw_data + src.raw_length + 1;
} else {
*dst = src.raw_data;
}
return 1;
} else return 0;
case 'c': if (type == UFBXI_VALUE_STRING) {
ufbxi_sanitized_string src = node->vals[ix].s;
const char **dst = (const char **)v;
*dst = src.raw_data;
return 1;
} else return 0;
case 'b': if (type == UFBXI_VALUE_STRING) {
ufbxi_sanitized_string src = node->vals[ix].s;
ufbx_blob *dst = (ufbx_blob*)v;
dst->data = src.raw_data;
dst->size = src.raw_length;
return 1;
} else return 0;
default:
ufbxi_unreachable("Bad format char");
return 0;
}
}
ufbxi_nodiscard ufbxi_noinline static ufbxi_value_array *ufbxi_get_array(ufbxi_node *node, char fmt)
{
if (node->value_type_mask != UFBXI_VALUE_ARRAY) return NULL;
ufbxi_value_array *array = node->array;
if (fmt != '?') {
fmt = ufbxi_normalize_array_type(fmt, 'b');
if (array->type != fmt) return NULL;
}
return array;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val1(ufbxi_node *node, const char *fmt, void *v0)
{
if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val2(ufbxi_node *node, const char *fmt, void *v0, void *v1)
{
if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0;
if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val3(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2)
{
if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0;
if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0;
if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val4(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2, void *v3)
{
if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0;
if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0;
if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0;
if (!ufbxi_get_val_at(node, 3, fmt[3], v3)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_get_val5(ufbxi_node *node, const char *fmt, void *v0, void *v1, void *v2, void *v3, void *v4)
{
if (!ufbxi_get_val_at(node, 0, fmt[0], v0)) return 0;
if (!ufbxi_get_val_at(node, 1, fmt[1], v1)) return 0;
if (!ufbxi_get_val_at(node, 2, fmt[2], v2)) return 0;
if (!ufbxi_get_val_at(node, 3, fmt[3], v3)) return 0;
if (!ufbxi_get_val_at(node, 4, fmt[4], v4)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_find_val1(ufbxi_node *node, const char *name, const char *fmt, void *v0)
{
ufbxi_node *child = ufbxi_find_child(node, name);
if (!child) return 0;
if (!ufbxi_get_val_at(child, 0, fmt[0], v0)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_find_val2(ufbxi_node *node, const char *name, const char *fmt, void *v0, void *v1)
{
ufbxi_node *child = ufbxi_find_child(node, name);
if (!child) return 0;
if (!ufbxi_get_val_at(child, 0, fmt[0], v0)) return 0;
if (!ufbxi_get_val_at(child, 1, fmt[1], v1)) return 0;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline ufbxi_value_array *ufbxi_find_array(ufbxi_node *node, const char *name, char fmt)
{
ufbxi_node *child = ufbxi_find_child(node, name);
if (!child) return NULL;
return ufbxi_get_array(child, fmt);
}
static ufbxi_node *ufbxi_find_child_strcmp(ufbxi_node *node, const char *name)
{
char leading = name[0];
ufbxi_for(ufbxi_node, c, node->children, node->num_children) {
if (c->name[0] != leading) continue;
if (!strcmp(c->name, name)) return c;
}
return NULL;
}
// -- Element extra data allocation
ufbxi_nodiscard static ufbxi_noinline void *ufbxi_push_element_extra_size(ufbxi_context *uc, uint32_t id, size_t size)
{
if (uc->element_extra_cap <= id) {
size_t old_cap = uc->element_extra_cap;
ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->element_extra_arr, &uc->element_extra_cap, id + 1), NULL);
memset(uc->element_extra_arr + old_cap, 0, (uc->element_extra_cap - old_cap) * sizeof(void*));
}
if (uc->element_extra_arr[id]) return uc->element_extra_arr[id];
void *extra = ufbxi_push_size_zero(&uc->tmp, size, 1);
ufbxi_check_return(extra, NULL);
uc->element_extra_arr[id] = extra;
return extra;
}
static ufbxi_noinline void *ufbxi_get_element_extra(ufbxi_context *uc, uint32_t id)
{
if (id < uc->element_extra_cap) {
return uc->element_extra_arr[id];
} else {
return NULL;
}
}
#define ufbxi_push_element_extra(uc, id, type) (type*)ufbxi_push_element_extra_size((uc), (id), sizeof(type))
// -- Parsing state machine
//
// When reading the file we maintain a coarse representation of the structure so
// that we can resolve array info (type, included in result, etc). Using this info
// we can often read/decompress the contents directly into the right memory area.
typedef enum {
UFBXI_PARSE_ROOT,
UFBXI_PARSE_FBX_HEADER_EXTENSION,
UFBXI_PARSE_SCENE_INFO,
UFBXI_PARSE_THUMBNAIL,
UFBXI_PARSE_DEFINITIONS,
UFBXI_PARSE_OBJECTS,
UFBXI_PARSE_CONNECTIONS,
UFBXI_PARSE_RELATIONS,
UFBXI_PARSE_TAKES,
UFBXI_PARSE_FBX_VERSION,
UFBXI_PARSE_MODEL,
UFBXI_PARSE_GEOMETRY,
UFBXI_PARSE_NODE_ATTRIBUTE,
UFBXI_PARSE_LEGACY_MODEL,
UFBXI_PARSE_LEGACY_MEDIA,
UFBXI_PARSE_LEGACY_VIDEO,
UFBXI_PARSE_LEGACY_SWITCHER,
UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE,
UFBXI_PARSE_REFERENCES,
UFBXI_PARSE_REFERENCE,
UFBXI_PARSE_ANIMATION_CURVE,
UFBXI_PARSE_DEFORMER,
UFBXI_PARSE_ASSOCIATE_MODEL,
UFBXI_PARSE_LEGACY_LINK,
UFBXI_PARSE_POSE,
UFBXI_PARSE_POSE_NODE,
UFBXI_PARSE_TEXTURE,
UFBXI_PARSE_VIDEO,
UFBXI_PARSE_LAYERED_TEXTURE,
UFBXI_PARSE_SELECTION_NODE,
UFBXI_PARSE_COLLECTION,
UFBXI_PARSE_AUDIO,
UFBXI_PARSE_UNKNOWN_OBJECT,
UFBXI_PARSE_LAYER_ELEMENT_NORMAL,
UFBXI_PARSE_LAYER_ELEMENT_BINORMAL,
UFBXI_PARSE_LAYER_ELEMENT_TANGENT,
UFBXI_PARSE_LAYER_ELEMENT_UV,
UFBXI_PARSE_LAYER_ELEMENT_COLOR,
UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE,
UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE,
UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING,
UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY,
UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP,
UFBXI_PARSE_LAYER_ELEMENT_HOLE,
UFBXI_PARSE_LAYER_ELEMENT_MATERIAL,
UFBXI_PARSE_LAYER_ELEMENT_OTHER,
UFBXI_PARSE_GEOMETRY_UV_INFO,
UFBXI_PARSE_SHAPE,
UFBXI_PARSE_TAKE,
UFBXI_PARSE_TAKE_OBJECT,
UFBXI_PARSE_CHANNEL,
UFBXI_PARSE_UNKNOWN,
} ufbxi_parse_state;
typedef enum {
UFBXI_ARRAY_FLAG_RESULT = 0x1, // < Alloacte the array from the result buffer
UFBXI_ARRAY_FLAG_TMP_BUF = 0x2, // < Alloacte the array from the result buffer
UFBXI_ARRAY_FLAG_PAD_BEGIN = 0x4, // < Pad the begin of the array with 4 zero elements to guard from invalid -1 index accesses
UFBXI_ARRAY_FLAG_ACCURATE_F32 = 0x8, // < Must be parsed as bit-accurate 32-bit floats
} ufbxi_array_flags;
typedef struct {
char type; // < FBX type code of the array: b,i,l,f,d (or 'r' meaning ufbx_real '-' ignore, 's'/'S' for strings, 'C' for content)
uint8_t flags; // < Combination of `ufbxi_array_flags`
} ufbxi_array_info;
static ufbxi_noinline ufbxi_parse_state ufbxi_update_parse_state(ufbxi_parse_state parent, const char *name)
{
switch (parent) {
case UFBXI_PARSE_ROOT:
if (name == ufbxi_FBXHeaderExtension) return UFBXI_PARSE_FBX_HEADER_EXTENSION;
if (name == ufbxi_Definitions) return UFBXI_PARSE_DEFINITIONS;
if (name == ufbxi_Objects) return UFBXI_PARSE_OBJECTS;
if (name == ufbxi_Connections) return UFBXI_PARSE_CONNECTIONS;
if (name == ufbxi_Takes) return UFBXI_PARSE_TAKES;
if (name == ufbxi_Model) return UFBXI_PARSE_LEGACY_MODEL;
if (!strcmp(name, "References")) return UFBXI_PARSE_REFERENCES;
if (!strcmp(name, "Relations")) return UFBXI_PARSE_RELATIONS;
if (name == ufbxi_Media) return UFBXI_PARSE_LEGACY_MEDIA;
if (!strcmp(name, "Switcher")) return UFBXI_PARSE_LEGACY_SWITCHER;
if (!strcmp(name, "SceneGenericPersistence")) return UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE;
break;
case UFBXI_PARSE_FBX_HEADER_EXTENSION:
if (name == ufbxi_FBXVersion) return UFBXI_PARSE_FBX_VERSION;
if (name == ufbxi_SceneInfo) return UFBXI_PARSE_SCENE_INFO;
break;
case UFBXI_PARSE_SCENE_INFO:
if (name == ufbxi_Thumbnail) return UFBXI_PARSE_THUMBNAIL;
break;
case UFBXI_PARSE_OBJECTS:
if (name == ufbxi_Model) return UFBXI_PARSE_MODEL;
if (name == ufbxi_Geometry) return UFBXI_PARSE_GEOMETRY;
if (name == ufbxi_NodeAttribute) return UFBXI_PARSE_NODE_ATTRIBUTE;
if (name == ufbxi_AnimationCurve) return UFBXI_PARSE_ANIMATION_CURVE;
if (name == ufbxi_Deformer) return UFBXI_PARSE_DEFORMER;
if (name == ufbxi_Pose) return UFBXI_PARSE_POSE;
if (name == ufbxi_Texture) return UFBXI_PARSE_TEXTURE;
if (name == ufbxi_Video) return UFBXI_PARSE_VIDEO;
if (name == ufbxi_LayeredTexture) return UFBXI_PARSE_LAYERED_TEXTURE;
if (name == ufbxi_SelectionNode) return UFBXI_PARSE_SELECTION_NODE;
if (name == ufbxi_Collection) return UFBXI_PARSE_COLLECTION;
if (name == ufbxi_Audio) return UFBXI_PARSE_AUDIO;
return UFBXI_PARSE_UNKNOWN_OBJECT;
case UFBXI_PARSE_MODEL:
case UFBXI_PARSE_GEOMETRY:
if (name[0] == 'L') {
if (name == ufbxi_LayerElementNormal) return UFBXI_PARSE_LAYER_ELEMENT_NORMAL;
if (name == ufbxi_LayerElementBinormal) return UFBXI_PARSE_LAYER_ELEMENT_BINORMAL;
if (name == ufbxi_LayerElementTangent) return UFBXI_PARSE_LAYER_ELEMENT_TANGENT;
if (name == ufbxi_LayerElementUV) return UFBXI_PARSE_LAYER_ELEMENT_UV;
if (name == ufbxi_LayerElementColor) return UFBXI_PARSE_LAYER_ELEMENT_COLOR;
if (name == ufbxi_LayerElementVertexCrease) return UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE;
if (name == ufbxi_LayerElementEdgeCrease) return UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE;
if (name == ufbxi_LayerElementSmoothing) return UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING;
if (name == ufbxi_LayerElementVisibility) return UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY;
if (name == ufbxi_LayerElementPolygonGroup) return UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP;
if (name == ufbxi_LayerElementHole) return UFBXI_PARSE_LAYER_ELEMENT_HOLE;
if (name == ufbxi_LayerElementMaterial) return UFBXI_PARSE_LAYER_ELEMENT_MATERIAL;
if (!strncmp(name, "LayerElement", 12)) return UFBXI_PARSE_LAYER_ELEMENT_OTHER;
}
if (name == ufbxi_Shape) return UFBXI_PARSE_SHAPE;
break;
case UFBXI_PARSE_DEFORMER:
if (!strcmp(name, "AssociateModel")) return UFBXI_PARSE_ASSOCIATE_MODEL;
break;
case UFBXI_PARSE_LEGACY_MEDIA:
if (name == ufbxi_Video) return UFBXI_PARSE_LEGACY_VIDEO;
break;
case UFBXI_PARSE_LEGACY_VIDEO:
return UFBXI_PARSE_VIDEO;
case UFBXI_PARSE_LEGACY_MODEL:
if (name == ufbxi_GeometryUVInfo) return UFBXI_PARSE_GEOMETRY_UV_INFO;
if (name == ufbxi_Link) return UFBXI_PARSE_LEGACY_LINK;
if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL;
if (name == ufbxi_Shape) return UFBXI_PARSE_SHAPE;
break;
case UFBXI_PARSE_POSE:
if (name == ufbxi_PoseNode) return UFBXI_PARSE_POSE_NODE;
break;
case UFBXI_PARSE_TAKES:
if (name == ufbxi_Take) return UFBXI_PARSE_TAKE;
break;
case UFBXI_PARSE_TAKE:
return UFBXI_PARSE_TAKE_OBJECT;
case UFBXI_PARSE_TAKE_OBJECT:
if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL;
break;
case UFBXI_PARSE_CHANNEL:
if (name == ufbxi_Channel) return UFBXI_PARSE_CHANNEL;
break;
case UFBXI_PARSE_REFERENCES:
return UFBXI_PARSE_REFERENCE;
default:
break;
}
return UFBXI_PARSE_UNKNOWN;
}
static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, const char *name, ufbxi_array_info *info)
{
info->flags = 0;
// Retain all arrays if user wants the DOM representation
if (uc->opts.retain_dom) {
info->flags |= UFBXI_ARRAY_FLAG_RESULT;
}
switch (parent) {
case UFBXI_PARSE_THUMBNAIL:
if (name == ufbxi_ImageData) {
info->type = 'c';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_GEOMETRY:
case UFBXI_PARSE_MODEL:
if (name == ufbxi_Vertices) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_PolygonVertexIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_Edges) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
return true;
} else if (name == ufbxi_Indexes) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_Points) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_KnotVector) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_KnotVectorU) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_KnotVectorV) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_PointsIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_Normals) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
break;
case UFBXI_PARSE_LEGACY_MODEL:
if (name == ufbxi_Vertices) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_Normals) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_Materials) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_PolygonVertexIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_Children) {
info->type = 's';
return true;
}
break;
case UFBXI_PARSE_ANIMATION_CURVE:
if (name == ufbxi_KeyTime) {
info->type = uc->opts.ignore_animation ? '-' : 'l';
return true;
} else if (name == ufbxi_KeyValueFloat) {
info->type = uc->opts.ignore_animation ? '-' : 'r';
return true;
} else if (name == ufbxi_KeyAttrFlags) {
info->type = uc->opts.ignore_animation ? '-' : 'i';
return true;
} else if (name == ufbxi_KeyAttrDataFloat) {
// The float data in a keyframe attribute array is represented as integers
// in versions >= 7200 as some of the elements aren't actually floats (!)
info->type = uc->from_ascii && uc->version >= 7200 ? 'i' : 'f';
if (uc->opts.ignore_animation) info->type = '-';
if (uc->from_ascii && uc->version >= 7200) {
info->flags |= UFBXI_ARRAY_FLAG_ACCURATE_F32;
}
return true;
} else if (name == ufbxi_KeyAttrRefCount) {
info->type = uc->opts.ignore_animation ? '-' : 'i';
return true;
}
break;
case UFBXI_PARSE_TEXTURE:
if (!strcmp(name, "ModelUVTranslation") || !strcmp(name, "ModelUVScaling") || !strcmp(name, "Cropping")) {
info->type = uc->opts.retain_dom ? 'r' : '-';
return true;
}
break;
case UFBXI_PARSE_VIDEO:
if (name == ufbxi_Content) {
info->type = uc->opts.ignore_embedded ? '-' : 'C';
return true;
}
break;
case UFBXI_PARSE_LAYERED_TEXTURE:
if (name == ufbxi_BlendModes) {
info->type = 'i';
info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF;
return true;
} else if (name == ufbxi_Alphas) {
info->type = 'r';
info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF;
return true;
}
break;
case UFBXI_PARSE_SELECTION_NODE:
if (name == ufbxi_VertexIndexArray) {
info->type = 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_EdgeIndexArray) {
info->type = 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_PolygonIndexArray) {
info->type = 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_NORMAL:
if (name == ufbxi_Normals) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_NormalsIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_NormalsW) {
info->type = uc->retain_vertex_w ? 'r' : '-';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_BINORMAL:
if (name == ufbxi_Binormals) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_BinormalsIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_BinormalsW) {
info->type = uc->retain_vertex_w ? 'r' : '-';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_TANGENT:
if (name == ufbxi_Tangents) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_TangentsIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_TangentsW) {
info->type = uc->retain_vertex_w ? 'r' : '-';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_UV:
if (name == ufbxi_UV) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_UVIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_COLOR:
if (name == ufbxi_Colors) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_ColorIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_VERTEX_CREASE:
if (name == ufbxi_VertexCrease) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_VertexCreaseIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_EDGE_CREASE:
if (name == ufbxi_EdgeCrease) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_SMOOTHING:
if (name == ufbxi_Smoothing) {
info->type = uc->opts.ignore_geometry ? '-' : 'b';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_VISIBILITY:
if (name == ufbxi_Visibility) {
info->type = uc->opts.ignore_geometry ? '-' : 'b';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_POLYGON_GROUP:
if (name == ufbxi_PolygonGroup) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_HOLE:
if (name == ufbxi_Hole) {
info->type = uc->opts.ignore_geometry ? '-' : 'b';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_MATERIAL:
if (name == ufbxi_Materials) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_LAYER_ELEMENT_OTHER:
if (name == ufbxi_TextureId) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF;
return true;
} else if (name == ufbxi_UV) {
info->type = uc->opts.retain_dom ? 'r' : '-';
return true;
} else if (name == ufbxi_UVIndex) {
info->type = uc->opts.retain_dom ? 'i' : '-';
return true;
}
break;
case UFBXI_PARSE_GEOMETRY_UV_INFO:
if (name == ufbxi_TextureUV) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
} else if (name == ufbxi_TextureUVVerticeIndex) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
break;
case UFBXI_PARSE_SHAPE:
if (name == ufbxi_Indexes) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
if (name == ufbxi_Vertices) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
if (name == ufbxi_Normals) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN;
return true;
}
break;
case UFBXI_PARSE_DEFORMER:
if (name == ufbxi_Transform) {
info->type = 'r';
return true;
} else if (name == ufbxi_TransformLink) {
info->type = 'r';
return true;
} else if (name == ufbxi_Indexes) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_Weights) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_BlendWeights) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_FullWeights) {
// Ignore blend shape FullWeights as it's used in Blender for vertex groups
// which we don't currently handle. https://developer.blender.org/T90382
// TODO: Should we present this to users anyway somehow?
info->type = 'r';
if (!uc->opts.disable_quirks && uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) {
info->type = '-';
}
info->flags |= UFBXI_ARRAY_FLAG_TMP_BUF;
return true;
} else if (!strcmp(name, "TransformAssociateModel")) {
info->type = uc->opts.retain_dom ? 'r' : '-';
return true;
}
break;
case UFBXI_PARSE_ASSOCIATE_MODEL:
if (name == ufbxi_Transform) {
info->type = uc->opts.retain_dom ? 'r' : '-';
return true;
}
break;
case UFBXI_PARSE_LEGACY_LINK:
if (name == ufbxi_Transform) {
info->type = 'r';
return true;
} else if (name == ufbxi_TransformLink) {
info->type = 'r';
return true;
} else if (name == ufbxi_Indexes) {
info->type = uc->opts.ignore_geometry ? '-' : 'i';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
} else if (name == ufbxi_Weights) {
info->type = uc->opts.ignore_geometry ? '-' : 'r';
info->flags = UFBXI_ARRAY_FLAG_RESULT;
return true;
}
break;
case UFBXI_PARSE_POSE_NODE:
if (name == ufbxi_Matrix) {
info->type = 'r';
return true;
}
break;
case UFBXI_PARSE_CHANNEL:
if (name == ufbxi_Key) {
info->type = uc->opts.ignore_animation ? '-' : 'd';
return true;
}
break;
case UFBXI_PARSE_AUDIO:
if (name == ufbxi_Content) {
info->type = uc->opts.ignore_embedded ? '-' : 'C';
return true;
}
break;
default:
if (name == ufbxi_BinaryData) {
info->type = uc->opts.ignore_embedded ? '-' : 'C';
return true;
}
break;
}
return false;
}
static ufbxi_noinline bool ufbxi_is_raw_string(ufbxi_context *uc, ufbxi_parse_state parent, const char *name, size_t index)
{
(void)index;
switch (parent) {
case UFBXI_PARSE_ROOT:
if (name == ufbxi_Model) return true;
if (!strcmp(name, "FileId")) return true;
break;
case UFBXI_PARSE_FBX_HEADER_EXTENSION:
if (name == ufbxi_SceneInfo) return true;
break;
case UFBXI_PARSE_OBJECTS:
return true;
case UFBXI_PARSE_CONNECTIONS:
case UFBXI_PARSE_RELATIONS:
// Pre-7000 needs raw strings for "Name\x00\x01Type" pairs, post-7000 uses it only
// for properties that are non-raw by default.
return uc->version < 7000;
case UFBXI_PARSE_MODEL:
if (name == ufbxi_NodeAttributeName) return true;
if (name == ufbxi_Name) return true;
break;
case UFBXI_PARSE_VIDEO:
if (name == ufbxi_Content) return true;
break;
case UFBXI_PARSE_TEXTURE:
if (!strcmp(name, "TextureName")) return true;
if (name == ufbxi_Media) return true;
break;
case UFBXI_PARSE_GEOMETRY:
if (name == ufbxi_NodeAttributeName) return true;
if (name == ufbxi_Name) return true;
break;
case UFBXI_PARSE_NODE_ATTRIBUTE:
if (name == ufbxi_NodeAttributeName) return true;
if (name == ufbxi_Name) return true;
break;
case UFBXI_PARSE_POSE_NODE:
if (name == ufbxi_Node) return true;
break;
case UFBXI_PARSE_SELECTION_NODE:
if (name == ufbxi_Node) return true;
break;
case UFBXI_PARSE_UNKNOWN_OBJECT:
if (name == ufbxi_NodeAttributeName) return true;
if (name == ufbxi_Name) return true;
break;
case UFBXI_PARSE_COLLECTION:
if (!strcmp(name, "Member")) return true;
break;
case UFBXI_PARSE_AUDIO:
if (name == ufbxi_Content) return true;
break;
case UFBXI_PARSE_LEGACY_MODEL:
if (name == ufbxi_Material) return true;
if (name == ufbxi_Link) return true;
if (name == ufbxi_Name) return true;
break;
case UFBXI_PARSE_LEGACY_SWITCHER:
if (!strcmp(name, "CameraIndexName")) return true;
break;
case UFBXI_PARSE_LEGACY_SCENE_PERSISTENCE:
if (name == ufbxi_SceneInfo) return true;
break;
case UFBXI_PARSE_REFERENCE:
if (!strcmp(name, "Object")) return true;
break;
case UFBXI_PARSE_TAKE:
if (name == ufbxi_Model) return true;
break;
default:
break;
}
return false;
}
// -- Binary parsing
ufbxi_nodiscard static ufbxi_noinline char *ufbxi_swap_endian(ufbxi_context *uc, const void *src, size_t count, size_t elem_size)
{
ufbxi_dev_assert(elem_size > 1);
size_t total_size = count * elem_size;
ufbxi_check_return(!ufbxi_does_overflow(total_size, count, elem_size), NULL);
if (uc->swap_arr_size < total_size) {
ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->swap_arr, &uc->swap_arr_size, total_size), NULL);
}
char *dst = uc->swap_arr, *d = dst;
const char *s = (const char*)src;
switch (elem_size) {
case 2:
ufbxi_nounroll for (size_t i = 0; i < count; i++) {
d[0] = s[1]; d[1] = s[0];
d += 2; s += 2;
}
break;
case 4:
ufbxi_nounroll for (size_t i = 0; i < count; i++) {
d[0] = s[3]; d[1] = s[2]; d[2] = s[1]; d[3] = s[0];
d += 4; s += 4;
}
break;
case 8:
ufbxi_nounroll for (size_t i = 0; i < count; i++) {
d[0] = s[7]; d[1] = s[6]; d[2] = s[5]; d[3] = s[4];
d[4] = s[3]; d[5] = s[2]; d[6] = s[1]; d[7] = s[0];
d += 8; s += 8;
}
break;
default:
ufbxi_unreachable("Bad endian swap size");
}
return dst;
}
// Swap the endianness of an array typed with a lowercase letter
ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_swap_endian_array(ufbxi_context *uc, const void *src, size_t count, char type)
{
switch (type) {
case 'i': case 'f': return ufbxi_swap_endian(uc, src, count, 4);
case 'l': case 'd': return ufbxi_swap_endian(uc, src, count, 8);
default: return (const char*)src;
}
}
// Swap the endianness of a single value (shallow, swaps string/array header words)
ufbxi_nodiscard static ufbxi_noinline const char *ufbxi_swap_endian_value(ufbxi_context *uc, const void *src, char type)
{
switch (type) {
case 'Y': return ufbxi_swap_endian(uc, src, 1, 2);
case 'I': case 'F': return ufbxi_swap_endian(uc, src, 1, 4);
case 'L': case 'D': return ufbxi_swap_endian(uc, src, 1, 8);
case 'S': case 'R': return ufbxi_swap_endian(uc, src, 1, 4);
case 'i': case 'l': case 'f': case 'd': case 'b': return ufbxi_swap_endian(uc, src, 3, 4);
default: return (const char*)src;
}
}
// Read and convert a post-7000 FBX data array into a different format. `src_type` may be equal to `dst_type`
// if the platform is not binary compatible with the FBX data representation.
ufbxi_nodiscard static ufbxi_noinline int ufbxi_binary_convert_array(ufbxi_context *maybe_uc, char src_type, char dst_type, const void *src, void *dst, size_t size)
{
// TODO: We might want to use the slow path if the machine float/double doesn't match IEEE 754!
// Convert commented out lines under some `#if UFBX_NON_IEE754` define or something.
if (src_type == dst_type) {
ufbx_assert(maybe_uc && maybe_uc->file_big_endian != maybe_uc->local_big_endian);
src = ufbxi_swap_endian_array(maybe_uc, src, size, src_type);
ufbxi_check_err(&maybe_uc->error, src);
memcpy(dst, src, size * ufbxi_array_type_size(dst_type));
return 1;
}
if (maybe_uc && maybe_uc->file_big_endian) {
src = ufbxi_swap_endian_array(maybe_uc, src, size, src_type);
ufbxi_check_err(&maybe_uc->error, src);
}
switch (dst_type)
{
#define ufbxi_convert_loop_fast(m_dst, m_cast, m_size, m_expr) do { \
const char *val = (const char*)src, *val_end = val + size*m_size; \
m_dst *d = (m_dst*)dst; \
while (val != val_end) { *d++ = m_cast(m_expr); val += m_size; } \
} while (0)
#define ufbxi_convert_loop_slow(m_dst, m_cast, m_size, m_expr) do { \
const char *val = (const char*)src, *val_end = val + size*m_size; \
m_dst *d = (m_dst*)dst; \
ufbxi_nounroll while (val != val_end) { *d++ = m_cast(m_expr); val += m_size; } \
} while (0)
case 'c':
switch (src_type) {
// case 'c': ufbxi_convert_loop_fast(char, (char), 1, *val != 0); break;
case 'i': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 4, (uint8_t)ufbxi_read_i32(val)); break;
case 'l': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 8, (uint8_t)ufbxi_read_i64(val)); break;
case 'f': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 4, (uint8_t)ufbxi_read_f32(val)); break;
case 'd': ufbxi_convert_loop_slow(uint8_t, (uint8_t), 8, (uint8_t)ufbxi_read_f64(val)); break;
default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0;
}
break;
case 'i':
switch (src_type) {
case 'c': ufbxi_convert_loop_slow(int32_t, (int32_t), 1, *val); break;
// case 'i': ufbxi_convert_loop_slow(int32_t, (int32_t), 4, ufbxi_read_i32(val)); break;
case 'l': ufbxi_convert_loop_slow(int32_t, (int32_t), 8, ufbxi_read_i64(val)); break;
case 'f': ufbxi_convert_loop_slow(int32_t, ufbxi_f64_to_i32, 4, ufbxi_read_f32(val)); break;
case 'd': ufbxi_convert_loop_slow(int32_t, ufbxi_f64_to_i32, 8, ufbxi_read_f64(val)); break;
default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0;
}
break;
case 'l':
switch (src_type) {
case 'c': ufbxi_convert_loop_slow(int64_t, (int64_t), 1, *val); break;
case 'i': ufbxi_convert_loop_slow(int64_t, (int64_t), 4, ufbxi_read_i32(val)); break;
// case 'l': ufbxi_convert_loop_slow(int64_t, (int64_t), 8, ufbxi_read_i64(val)); break;
case 'f': ufbxi_convert_loop_slow(int64_t, ufbxi_f64_to_i64, 4, ufbxi_read_f32(val)); break;
case 'd': ufbxi_convert_loop_slow(int64_t, ufbxi_f64_to_i64, 8, ufbxi_read_f64(val)); break;
default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0;
}
break;
case 'f':
switch (src_type) {
case 'c': ufbxi_convert_loop_slow(float, (float), 1, *val); break;
case 'i': ufbxi_convert_loop_slow(float, (float), 4, ufbxi_read_i32(val)); break;
case 'l': ufbxi_convert_loop_slow(float, (float), 8, ufbxi_read_i64(val)); break;
// case 'f': ufbxi_convert_loop_slow(float, (float), 4, ufbxi_read_f32(val)); break;
case 'd': ufbxi_convert_loop_fast(float, (float), 8, ufbxi_read_f64(val)); break;
default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0;
}
break;
case 'd':
switch (src_type) {
case 'c': ufbxi_convert_loop_slow(double, (double), 1, *val); break;
case 'i': ufbxi_convert_loop_slow(double, (double), 4, ufbxi_read_i32(val)); break;
case 'l': ufbxi_convert_loop_slow(double, (double), 8, ufbxi_read_i64(val)); break;
case 'f': ufbxi_convert_loop_fast(double, (double), 4, ufbxi_read_f32(val)); break;
// case 'd': ufbxi_convert_loop_slow(double, (double), 8, ufbxi_read_f64(val)); break;
default: if (maybe_uc) ufbxi_fail_err(&maybe_uc->error, "Bad array source type"); return 0;
}
break;
default: return 0;
}
return 1;
}
// Read pre-7000 separate properties as an array.
ufbxi_nodiscard static ufbxi_noinline int ufbxi_binary_parse_multivalue_array(ufbxi_context *uc, char dst_type, void *dst, size_t size, ufbxi_buf *tmp_buf)
{
if (size == 0) return 1;
const char *val;
size_t val_size;
bool file_big_endian = uc->file_big_endian;
#define ufbxi_convert_parse_fast(m_dst, m_type, m_expr) do { \
m_dst *d = (m_dst*)dst; \
for (; base < size; base++) { \
val = ufbxi_peek_bytes(uc, 13); \
ufbxi_check(val); \
if (*val != m_type) break; \
val++; \
*d++ = (m_dst)(m_expr); \
ufbxi_consume_bytes(uc, 1 + sizeof(m_dst)); \
} \
} while (0)
// String array special case
if (dst_type == 's' || dst_type == 'S' || dst_type == 'C') {
bool raw = dst_type == 's';
ufbx_string *d = (ufbx_string*)dst;
for (size_t i = 0; i < size; i++) {
val = ufbxi_peek_bytes(uc, 13);
ufbxi_check(val);
char type = *val++;
ufbxi_check(type == 'S' || type == 'R');
if (file_big_endian) {
val = ufbxi_swap_endian_value(uc, val, type);
ufbxi_check(val);
}
size_t len = ufbxi_read_u32(val);
ufbxi_consume_bytes(uc, 5);
d->data = ufbxi_read_bytes(uc, len);
d->length = len;
ufbxi_check(d->data);
if (dst_type == 'C') {
ufbxi_buf *buf = size == 1 || uc->opts.retain_dom ? &uc->result : tmp_buf;
d->data = ufbxi_push_copy(buf, char, len, d->data);
ufbxi_check(d->data);
} else {
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, d, raw));
}
d++;
}
return 1;
}
// Optimize a couple of common cases
size_t base = 0;
if (!file_big_endian) {
switch (dst_type) {
case 'i': ufbxi_convert_parse_fast(int32_t, 'I', ufbxi_read_i32(val)); break;
case 'l': ufbxi_convert_parse_fast(int64_t, 'L', ufbxi_read_i64(val)); break;
case 'f': ufbxi_convert_parse_fast(float, 'F', ufbxi_read_f32(val)); break;
case 'd': ufbxi_convert_parse_fast(double, 'D', ufbxi_read_f64(val)); break;
default: break; // Fallthrough to rest
}
// Early return if we handled everything
if (base == size) return 1;
}
switch (dst_type)
{
#define ufbxi_convert_parse(m_cast, m_size, m_expr) \
*d++ = m_cast(m_expr); val_size = m_size + 1; \
#define ufbxi_convert_parse_switch(m_dst, m_cast_int, m_cast_float) do { \
m_dst *d = (m_dst*)dst + base; \
for (size_t i = base; i < size; i++) { \
val = ufbxi_peek_bytes(uc, 13); \
ufbxi_check(val); \
char type = *val++; \
if (file_big_endian) { \
val = ufbxi_swap_endian_value(uc, val, type); \
ufbxi_check(val); \
} \
switch (type) { \
case 'C': \
case 'B': ufbxi_convert_parse(m_cast_int, 1, *val); break; \
case 'Y': ufbxi_convert_parse(m_cast_int, 2, ufbxi_read_i16(val)); break; \
case 'I': ufbxi_convert_parse(m_cast_int, 4, ufbxi_read_i32(val)); break; \
case 'L': ufbxi_convert_parse(m_cast_int, 8, ufbxi_read_i64(val)); break; \
case 'F': ufbxi_convert_parse(m_cast_float, 4, ufbxi_read_f32(val)); break; \
case 'D': ufbxi_convert_parse(m_cast_float, 8, ufbxi_read_f64(val)); break; \
default: ufbxi_fail("Bad multivalue array type"); \
} \
ufbxi_consume_bytes(uc, val_size); \
} \
} while (0)
case 'c': ufbxi_convert_parse_switch(uint8_t, (uint8_t), (uint8_t)); break;
case 'i': ufbxi_convert_parse_switch(int32_t, (int32_t), ufbxi_f64_to_i32); break;
case 'l': ufbxi_convert_parse_switch(int64_t, (int64_t), ufbxi_f64_to_i64); break;
case 'f': ufbxi_convert_parse_switch(float, (float), (float)); break;
case 'd': ufbxi_convert_parse_switch(double, (double), (double)); break;
default: return 0;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static void *ufbxi_push_array_data(ufbxi_context *uc, const ufbxi_array_info *info, size_t size, ufbxi_buf *tmp_buf)
{
size_t elem_size = ufbxi_array_type_size(info->type);
uint32_t flags = info->flags;
if (flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) size += 4;
// The array may be pushed either to the result or temporary buffer depending
// if it's already in the right format
ufbxi_buf *arr_buf = tmp_buf;
if (flags & UFBXI_ARRAY_FLAG_RESULT) arr_buf = &uc->result;
else if (flags & UFBXI_ARRAY_FLAG_TMP_BUF) arr_buf = &uc->tmp;
char *data = (char*)ufbxi_push_size(arr_buf, elem_size, size);
ufbxi_check_return(data, NULL);
if (flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) {
memset(data, 0, elem_size * 4);
data += elem_size * 4;
}
return data;
}
ufbxi_noinline static void ufbxi_postprocess_bool_array(char *data, size_t size)
{
ufbxi_for(char, b, (char*)data, size) {
*b = (char)(*b != 0);
}
}
typedef struct {
size_t encoded_size;
size_t src_elem_size;
size_t array_size;
char src_type;
char dst_type;
char arr_type;
const void *encoded_data;
void *decoded_data;
void *dst_data;
ufbx_inflate_retain *inflate_retain;
} ufbxi_deflate_task;
static bool ufbxi_deflate_task_fn(ufbxi_task *task)
{
ufbxi_deflate_task *t = (ufbxi_deflate_task*)task->data;
ufbx_inflate_input input; // ufbxi_uninit
input.total_size = t->encoded_size;
input.data = t->encoded_data;
input.data_size = t->encoded_size;
input.no_header = false;
input.no_checksum = false;
input.internal_fast_bits = 0;
input.progress_cb.fn = NULL;
input.progress_cb.user = NULL;
input.progress_size_before = 0;
input.progress_size_after = 0;
input.progress_interval_hint = 0;
input.buffer = NULL;
input.buffer_size = 0;
input.read_fn = NULL;
input.read_user = NULL;
size_t decoded_data_size = t->src_elem_size * t->array_size;
ptrdiff_t res = ufbx_inflate(t->decoded_data, decoded_data_size, &input, t->inflate_retain);
if (res == -28) {
task->error = "Cancelled";
return false;
} else if (res != (ptrdiff_t)decoded_data_size) {
task->error = "Bad DEFLATE data";
return false;
}
if (t->decoded_data != t->dst_data) {
int ok = ufbxi_binary_convert_array(NULL, t->src_type, t->dst_type, t->decoded_data, t->dst_data, t->array_size);
if (!ok) {
task->error = "Failed to convert array";
return false;
}
}
if (t->arr_type == 'b') {
ufbxi_postprocess_bool_array((char*)t->dst_data, t->array_size);
}
return true;
}
// Recursion limited by check at the start
ufbxi_nodiscard ufbxi_noinline static int ufbxi_binary_parse_node(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive)
ufbxi_recursive_function(int, ufbxi_binary_parse_node, (uc, depth, parent_state, p_end, tmp_buf, recursive), UFBXI_MAX_NODE_DEPTH + 1,
(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive))
{
// https://code.blender.org/2013/08/fbx-binary-file-format-specification
// Parse an FBX document node in the binary format
ufbxi_check(depth < UFBXI_MAX_NODE_DEPTH);
// Parse the node header, post-7500 versions use 64-bit values for most
// header fields.
uint64_t end_offset, num_values64, values_len;
uint8_t name_len;
size_t header_size = (uc->version >= 7500) ? 25 : 13;
const char *header = ufbxi_read_bytes(uc, header_size), *header_words = header;
ufbxi_check(header);
if (uc->version >= 7500) {
if (uc->file_big_endian) {
header_words = ufbxi_swap_endian(uc, header_words, 3, 8);
ufbxi_check(header_words);
}
end_offset = ufbxi_read_u64(header_words + 0);
num_values64 = ufbxi_read_u64(header_words + 8);
values_len = ufbxi_read_u64(header_words + 16);
name_len = ufbxi_read_u8(header + 24);
} else {
if (uc->file_big_endian) {
header_words = ufbxi_swap_endian(uc, header_words, 3, 4);
ufbxi_check(header_words);
}
end_offset = ufbxi_read_u32(header_words + 0);
num_values64 = ufbxi_read_u32(header_words + 4);
values_len = ufbxi_read_u32(header_words + 8);
name_len = ufbxi_read_u8(header + 12);
}
ufbxi_check(num_values64 <= UINT32_MAX);
uint32_t num_values = (uint32_t)num_values64;
// If `end_offset` and `name_len` is zero we treat as the node as a NULL-sentinel
// that terminates a node list.
if (end_offset == 0 && name_len == 0) {
*p_end = true;
return 1;
}
// Update estimated end offset if possible
if (end_offset > uc->progress_bytes_total) {
uc->progress_bytes_total = end_offset;
}
// Push the parsed node into the `tmp_stack` buffer, the nodes will be popped by
// calling code after its done parsing all of it's children.
ufbxi_node *node = ufbxi_push_zero(&uc->tmp_stack, ufbxi_node, 1);
ufbxi_check(node);
// Parse and intern the name to the string pool.
const char *name = ufbxi_read_bytes(uc, name_len);
ufbxi_check(name);
name = ufbxi_push_string(&uc->string_pool, name, name_len, NULL, true);
ufbxi_check(name);
node->name_len = name_len;
node->name = name;
uint64_t values_end_offset = ufbxi_get_read_offset(uc) + values_len;
// Check if the values of the node we're parsing currently should be
// treated as an array.
ufbxi_array_info arr_info;
if (ufbxi_is_array_node(uc, parent_state, name, &arr_info)) {
// Normalize the array type (eg. 'r' to 'f'/'d' depending on the build)
// and get the per-element size of the array.
// Boolean arrays 'b' are normalized to 'c' as they are postprocessed
// below based on `arr_info.type`.
char dst_type = ufbxi_normalize_array_type(arr_info.type, 'c');
ufbxi_value_array *arr = ufbxi_push(tmp_buf, ufbxi_value_array, 1);
ufbxi_check(arr);
node->value_type_mask = UFBXI_VALUE_ARRAY;
node->array = arr;
arr->type = ufbxi_normalize_array_type(arr_info.type, 'b');
// Peek the first bytes of the array. We can always look at least 13 bytes
// ahead safely as valid FBX files must end in a 13/25 byte NULL record.
const char *data = ufbxi_peek_bytes(uc, 13);
ufbxi_check(data);
// Check if the data type is one of the explicit array types (post-7000).
// Otherwise we form the array by concatenating all the normal values of the
// node (pre-7000)
char c = data[0];
// HACK: Override the "type" if either the array is empty or we want to
// specifically ignore the contents.
if (num_values == 0) c = '0';
if (dst_type == '-') c = '-';
bool deferred = false;
if (c=='c' || c=='b' || c=='i' || c=='l' || c =='f' || c=='d') {
const char *arr_words = data + 1;
if (uc->file_big_endian) {
arr_words = ufbxi_swap_endian(uc, arr_words, 3, 4);
ufbxi_check(arr_words);
}
// Parse the array header from the prefix we already peeked above.
char src_type = data[0];
uint32_t size = ufbxi_read_u32(arr_words + 0);
uint32_t encoding = ufbxi_read_u32(arr_words + 4);
uint32_t encoded_size = ufbxi_read_u32(arr_words + 8);
ufbxi_consume_bytes(uc, 13);
// Normalize the source type as well, but don't convert UFBX-specific
// 'r' to 'f'/'d', but fail later instead.
if (src_type != 'r') src_type = ufbxi_normalize_array_type(src_type, 'c');
size_t src_elem_size = ufbxi_array_type_size(src_type);
size_t decoded_data_size = src_elem_size * size;
// Allocate `size` elements for the array.
char *arr_data = (char*)ufbxi_push_array_data(uc, &arr_info, size, tmp_buf);
ufbxi_check(arr_data);
uint64_t arr_begin = ufbxi_get_read_offset(uc);
ufbxi_check(UINT64_MAX - encoded_size > arr_begin);
uint64_t arr_end = arr_begin + encoded_size;
if (arr_end > uc->progress_bytes_total) {
uc->progress_bytes_total = arr_end;
}
// Threading
if (uc->parse_threaded && encoding == 1 && encoded_size >= UFBXI_MIN_THREADED_DEFLATE_BYTES && !uc->file_big_endian && !uc->local_big_endian) {
ufbxi_task *task = ufbxi_thread_pool_create_task(&uc->thread_pool, &ufbxi_deflate_task_fn);
if (task) {
ufbxi_deflate_task *t = ufbxi_push_zero(tmp_buf, ufbxi_deflate_task, 1);
ufbxi_check(t);
ufbxi_inflate_init_retain(uc->inflate_retain);
t->src_elem_size = src_elem_size;
t->encoded_size = encoded_size;
t->array_size = size;
t->src_type = src_type;
t->dst_type = dst_type;
t->arr_type = arr->type;
t->dst_data = arr_data;
t->inflate_retain = uc->inflate_retain;
if (!uc->read_fn) {
// From memory, no need to copy
t->encoded_data = uc->data;
} else {
void *encoded_data = ufbxi_push(tmp_buf, char, encoded_size);
ufbxi_check(encoded_data);
ufbxi_check(ufbxi_read_to(uc, encoded_data, encoded_size));
t->encoded_data = encoded_data;
}
if (src_type != dst_type) {
t->decoded_data = ufbxi_push_size(tmp_buf, src_elem_size, size);
ufbxi_check(t->decoded_data);
} else {
t->decoded_data = arr_data;
}
task->data = t;
ufbxi_thread_pool_run_task(&uc->thread_pool, task);
deferred = true;
}
}
// If the source and destination types are equal and our build is binary-compatible
// with the FBX format we can read the decoded data directly into the array buffer.
// Otherwise we need a temporary buffer to decode the array into before conversion.
void *decoded_data = arr_data;
if (!deferred && (src_type != dst_type || uc->local_big_endian != uc->file_big_endian)) {
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, decoded_data_size));
decoded_data = uc->tmp_arr;
}
if (deferred) {
// Nop
} else if (encoding == 0) {
// Encoding 0: Plain binary data.
ufbxi_check(encoded_size == decoded_data_size);
// If the array is contained in the current read buffer and we need to convert
// the data anyway we can use the read buffer as the decoded array source, otherwise
// do a plain byte copy to the array/conversion buffer.
if (uc->yield_size + uc->data_size >= encoded_size && decoded_data != arr_data) {
// Yield right after this if we crossed the yield threshold
if (encoded_size > uc->yield_size) {
uc->data_size += uc->yield_size;
uc->yield_size = encoded_size;
uc->data_size -= uc->yield_size;
}
decoded_data = (void*)uc->data;
ufbxi_consume_bytes(uc, encoded_size);
} else {
ufbxi_check(ufbxi_read_to(uc, decoded_data, encoded_size));
}
} else if (encoding == 1) {
// Encoding 1: DEFLATE
ufbxi_pause_progress(uc);
// Inflate the data from the user-provided IO buffer / read callbacks
ufbx_inflate_input input;
input.total_size = encoded_size;
input.data = uc->data;
input.data_size = uc->data_size;
input.no_header = false;
input.no_checksum = false;
input.internal_fast_bits = 0;
if (uc->opts.progress_cb.fn) {
input.progress_cb = uc->opts.progress_cb;
input.progress_size_before = arr_begin;
input.progress_size_after = uc->progress_bytes_total - arr_end;
input.progress_interval_hint = uc->progress_interval;
} else {
input.progress_cb.fn = NULL;
input.progress_cb.user = NULL;
input.progress_size_before = 0;
input.progress_size_after = 0;
input.progress_interval_hint = 0;
}
// If the encoded array is larger than the data we have currently buffered
// we need to allow `ufbx_inflate()` to read from the IO callback. We can
// let `ufbx_inflate()` freely clobber our `read_buffer` as all the data
// in the buffer will be consumed. `ufbx_inflate()` always reads exactly
// the amount of bytes needed so we can continue reading from `read_fn` as
// usual (given that we clear the `uc->data/_size` buffer below).
// NOTE: We _cannot_ share `read_buffer` if we plan to read later from it
// as `ufbx_inflate()` overwrites parts of it with zeroes.
if (encoded_size > input.data_size) {
input.buffer = uc->read_buffer;
input.buffer_size = uc->read_buffer_size;
input.read_fn = uc->read_fn;
input.read_user = uc->read_user;
uc->data_offset += encoded_size - input.data_size;
uc->data += input.data_size;
uc->data_size = 0;
} else {
input.buffer = NULL;
input.buffer_size = 0;
input.read_fn = NULL;
input.read_user = NULL;
uc->data += encoded_size;
uc->data_size -= encoded_size;
ufbxi_check(ufbxi_resume_progress(uc));
}
ptrdiff_t res = ufbx_inflate(decoded_data, decoded_data_size, &input, uc->inflate_retain);
ufbxi_check_msg(res != -28, "Cancelled");
ufbxi_check_msg(res == (ptrdiff_t)decoded_data_size, "Bad DEFLATE data");
} else {
ufbxi_fail("Bad array encoding");
}
// Convert the decoded array if necessary.
if (!deferred && decoded_data != arr_data) {
ufbxi_check(ufbxi_binary_convert_array(uc, src_type, dst_type, decoded_data, arr_data, size));
}
arr->data = arr_data;
arr->size = size;
} else if (c == '0' || c == '-') {
// Ignore the array
arr->type = c == '-' ? '-' : dst_type;
arr->data = (char*)ufbxi_zero_size_buffer + 32;
arr->size = 0;
} else {
// Allocate `num_values` elements for the array and parse single values into it.
char *arr_data = (char*)ufbxi_push_array_data(uc, &arr_info, num_values, tmp_buf);
ufbxi_check(arr_data);
ufbxi_check(ufbxi_binary_parse_multivalue_array(uc, dst_type, arr_data, num_values, tmp_buf));
arr->data = arr_data;
arr->size = num_values;
}
// Post-process boolean arrays
if (!deferred && arr_info.type == 'b') {
ufbxi_postprocess_bool_array((char*)arr->data, arr->size);
}
} else {
// Parse up to UFBXI_MAX_NON_ARRAY_VALUES as plain values
num_values = ufbxi_min32(num_values, UFBXI_MAX_NON_ARRAY_VALUES);
ufbxi_value *vals = ufbxi_push(tmp_buf, ufbxi_value, num_values);
ufbxi_check(vals);
node->vals = vals;
uint32_t type_mask = 0;
for (size_t i = 0; i < (size_t)num_values; i++) {
// The file must end in a 13/25 byte NULL record, so we can peek
// up to 13 bytes safely here.
const char *data = ufbxi_peek_bytes(uc, 13);
ufbxi_check(data);
const char *value = data + 1;
char type = data[0];
if (uc->file_big_endian) {
value = ufbxi_swap_endian_value(uc, value, type);
ufbxi_check(value);
}
switch (type) {
case 'C': case 'B': case 'Z':
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2);
vals[i].f = (double)(vals[i].i = (int64_t)(uint8_t)value[0]);
ufbxi_consume_bytes(uc, 2);
break;
case 'Y':
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2);
vals[i].f = (double)(vals[i].i = ufbxi_read_i16(value));
ufbxi_consume_bytes(uc, 3);
break;
case 'I':
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2);
vals[i].f = (double)(vals[i].i = ufbxi_read_i32(value));
ufbxi_consume_bytes(uc, 5);
break;
case 'L':
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2);
vals[i].f = (double)(vals[i].i = ufbxi_read_i64(value));
ufbxi_consume_bytes(uc, 9);
break;
case 'F':
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2);
vals[i].i = ufbxi_f64_to_i64(vals[i].f = ufbxi_read_f32(value));
ufbxi_consume_bytes(uc, 5);
break;
case 'D':
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2);
vals[i].i = ufbxi_f64_to_i64(vals[i].f = ufbxi_read_f64(value));
ufbxi_consume_bytes(uc, 9);
break;
case 'S': case 'R':
{
uint32_t length = ufbxi_read_u32(value);
ufbxi_consume_bytes(uc, 5);
const char *str = ufbxi_read_bytes(uc, length);
ufbxi_check(str);
if (length == 0) {
vals[i].s.raw_data = ufbxi_empty_char;
vals[i].s.raw_length = 0;
vals[i].s.utf8_length = 0;
} else {
bool non_ascii = false;
uint32_t hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii);
bool raw = !non_ascii || ufbxi_is_raw_string(uc, parent_state, name, i);
ufbxi_check(ufbxi_push_sanitized_string(&uc->string_pool, &vals[i].s, str, length, hash, raw));
// Mark the data as invalid UTF-8
if (non_ascii && raw) vals[i].s.utf8_length = UINT32_MAX;
}
type_mask |= (uint32_t)UFBXI_VALUE_STRING << (i*2);
}
break;
// Treat arrays as non-values and skip them
case 'c': case 'b': case 'i': case 'l': case 'f': case 'd':
{
uint32_t encoded_size = ufbxi_read_u32(value + 8);
ufbxi_consume_bytes(uc, 13);
ufbxi_check(ufbxi_skip_bytes(uc, encoded_size));
}
break;
default:
ufbxi_fail("Bad value type");
}
}
node->value_type_mask = (uint16_t)type_mask;
}
// Skip over remaining values if necessary if we for example truncated
// the list of values or if there are values after an array
uint64_t offset = ufbxi_get_read_offset(uc);
ufbxi_check(offset <= values_end_offset);
if (offset < values_end_offset) {
ufbxi_check(ufbxi_skip_bytes(uc, values_end_offset - offset));
}
if (recursive) {
// Recursively parse the children of this node. Update the parse state
// to provide context for child node parsing.
ufbxi_parse_state parse_state = ufbxi_update_parse_state(parent_state, node->name);
uint32_t num_children = 0;
for (;;) {
// Stop at end offset
uint64_t current_offset = ufbxi_get_read_offset(uc);
if (current_offset >= end_offset) {
ufbxi_check(current_offset == end_offset || end_offset == 0);
break;
}
bool end = false;
ufbxi_check(ufbxi_binary_parse_node(uc, depth + 1, parse_state, &end, tmp_buf, true));
if (end) break;
num_children++;
}
// Pop children from `tmp_stack` to a contiguous array
node->num_children = num_children;
if (num_children > 0) {
node->children = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node, num_children);
ufbxi_check(node->children);
}
} else {
uint64_t current_offset = ufbxi_get_read_offset(uc);
uc->has_next_child = (current_offset < end_offset);
}
return 1;
}
#define UFBXI_BINARY_MAGIC_SIZE 22
#define UFBXI_BINARY_HEADER_SIZE 27
static const char ufbxi_binary_magic[] = "Kaydara FBX Binary \x00\x1a";
// -- ASCII parsing
#define UFBXI_ASCII_END '\0'
#define UFBXI_ASCII_NAME 'N'
#define UFBXI_ASCII_BARE_WORD 'B'
#define UFBXI_ASCII_INT 'I'
#define UFBXI_ASCII_FLOAT 'F'
#define UFBXI_ASCII_STRING 'S'
static ufbxi_noinline char ufbxi_ascii_refill(ufbxi_context *uc)
{
ufbxi_ascii *ua = &uc->ascii;
uc->data_offset += ufbxi_to_size(ua->src - uc->data_begin);
if (uc->read_fn) {
char *dst_buffer = NULL;
size_t dst_size = 0;
if (ua->retain_buf != NULL) {
dst_size = uc->opts.read_buffer_size;
dst_buffer = ufbxi_push(ua->retain_buf, char, dst_size);
ufbxi_check_return(dst_buffer, '\0');
ua->src_is_retained = true;
ua->src_buf = ua->retain_buf;
} else {
// Grow the read buffer if necessary
if (uc->read_buffer_size < uc->opts.read_buffer_size) {
size_t new_size = uc->opts.read_buffer_size;
ufbxi_check_return(ufbxi_grow_array(&uc->ator_tmp, &uc->read_buffer, &uc->read_buffer_size, new_size), '\0');
}
dst_buffer = uc->read_buffer;
dst_size = uc->read_buffer_size;
ua->src_is_retained = false;
ua->src_buf = NULL;
}
// Read user data, return '\0' on EOF
// TODO: Very unoptimal for non-full-size reads in some cases
size_t num_read = uc->read_fn(uc->read_user, dst_buffer, dst_size);
ufbxi_check_return_msg(num_read != SIZE_MAX, '\0', "IO error");
ufbxi_check_return(num_read <= uc->read_buffer_size, '\0');
if (num_read == 0) return '\0';
uc->data = uc->data_begin = ua->src = dst_buffer;
ua->src_end = dst_buffer + num_read;
return *ua->src;
} else {
// If the user didn't specify a `read_fn()` treat anything
// past the initial data buffer as EOF.
uc->data = uc->data_begin = ua->src = "";
ua->src_end = ua->src + 1;
return '\0';
}
}
static ufbxi_noinline char ufbxi_ascii_yield(ufbxi_context *uc)
{
ufbxi_ascii *ua = &uc->ascii;
char ret;
if (ua->src == ua->src_end) {
ret = ufbxi_ascii_refill(uc);
} else {
ret = *ua->src;
}
if (ufbxi_to_size(ua->src_end - ua->src) < uc->progress_interval) {
ua->src_yield = ua->src_end;
} else {
ua->src_yield = ua->src + uc->progress_interval;
}
// TODO: Unify these properly
uc->data = ua->src;
ufbxi_check_return(ufbxi_report_progress(uc), '\0');
return ret;
}
static ufbxi_forceinline char ufbxi_ascii_peek(ufbxi_context *uc)
{
ufbxi_ascii *ua = &uc->ascii;
if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc);
return *ua->src;
}
static ufbxi_forceinline char ufbxi_ascii_next(ufbxi_context *uc)
{
ufbxi_ascii *ua = &uc->ascii;
if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc);
ua->src++;
if (ua->src == ua->src_yield) return ufbxi_ascii_yield(uc);
return *ua->src;
}
static ufbxi_noinline uint32_t ufbxi_ascii_parse_version(ufbxi_context *uc)
{
uint8_t digits[3];
uint32_t num_digits = 0;
char c = ufbxi_ascii_next(uc);
const char fmt[] = " FBX ?.?.?";
uint32_t ix = 0;
while (num_digits < 3) {
char ref = fmt[ix++];
switch (ref) {
// Digit
case '?':
if (c < '0' || c > '9') return 0;
digits[num_digits++] = (uint8_t)(c - '0');
c = ufbxi_ascii_next(uc);
break;
// Whitespace
case ' ':
while (c == ' ' || c == '\t') {
c = ufbxi_ascii_next(uc);
}
break;
// Literal character
default:
if (c != ref) return 0;
c = ufbxi_ascii_next(uc);
break;
}
}
if (num_digits != 3) return 0;
return 1000u*(uint32_t)digits[0] + 100u*(uint32_t)digits[1] + 10u*(uint32_t)digits[2];
}
static const uint32_t ufbxi_space_mask =
(1u << ((uint32_t)' ' - 1)) |
(1u << ((uint32_t)'\t' - 1)) |
(1u << ((uint32_t)'\r' - 1)) |
(1u << ((uint32_t)'\n' - 1)) ;
ufbx_static_assert(space_codepoint,
(uint32_t)' ' <= 32u && (uint32_t)'\t' <= 32u &&
(uint32_t)'\r' <= 32u && (uint32_t)'\n' <= 32u);
static ufbxi_forceinline bool ufbxi_is_space(char c)
{
uint32_t v = (uint32_t)(uint8_t)c - 1;
return v < 32 && ((ufbxi_space_mask >> v) & 0x1) != 0;
}
static ufbxi_noinline char ufbxi_ascii_skip_whitespace(ufbxi_context *uc)
{
ufbxi_ascii *ua = &uc->ascii;
// Ignore whitespace
char c = ufbxi_ascii_peek(uc);
for (;;) {
while (ufbxi_is_space(c)) {
c = ufbxi_ascii_next(uc);
}
// Line comment
if (c == ';') {
bool read_magic = false;
// FBX ASCII files begin with a magic comment of form "; FBX 7.7.0 project file"
// Try to extract the version number from the magic comment
if (!ua->read_first_comment) {
ua->read_first_comment = true;
uint32_t version = ufbxi_ascii_parse_version(uc);
if (version) {
uc->version = version;
ua->found_version = true;
read_magic = true;
}
}
c = ufbxi_ascii_next(uc);
while (c != '\n' && c != '\0') {
c = ufbxi_ascii_next(uc);
}
c = ufbxi_ascii_next(uc);
// Try to determine if this is a Blender 6100 ASCII file
if (read_magic) {
if (c == ';') {
char line[32];
size_t line_len = 0;
c = ufbxi_ascii_next(uc);
while (c != '\n' && c != '\0') {
if (line_len < sizeof(line)) {
line[line_len++] = c;
}
c = ufbxi_ascii_next(uc);
}
if (line_len >= 19 && !memcmp(line, " Created by Blender", 19)) {
uc->exporter = UFBX_EXPORTER_BLENDER_ASCII;
}
}
}
} else {
break;
}
}
return c;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_ascii_push_token_char(ufbxi_context *uc, ufbxi_ascii_token *token, char c)
{
// Grow the string data buffer if necessary
if (token->str_len == token->str_cap) {
size_t len = ufbxi_max_sz(token->str_len + 1, 256);
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &token->str_data, &token->str_cap, len));
}
token->str_data[token->str_len++] = c;
return 1;
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_ascii_push_token_string(ufbxi_context *uc, ufbxi_ascii_token *token, const char *data, size_t length)
{
// Grow the string data buffer if necessary
if (token->str_len + length >= token->str_cap) {
size_t len = ufbxi_max_sz(token->str_len + length, 256);
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &token->str_data, &token->str_cap, len));
}
memcpy(token->str_data + token->str_len, data, length);
token->str_len += length;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_skip_until(ufbxi_context *uc, char dst)
{
ufbxi_ascii *ua = &uc->ascii;
for (;;) {
size_t buffered = ufbxi_to_size(ua->src_yield - ua->src);
const char *match = (const char*)memchr(ua->src, dst, buffered);
if (match) {
ua->src = match;
break;
} else {
ua->src += buffered;
}
if (buffered == 0) {
char c = ufbxi_ascii_yield(uc);
ufbxi_check(c != '\0');
}
}
return 1;
}
typedef struct {
const char *source;
size_t length;
} ufbxi_ascii_span;
ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_store_array(ufbxi_context *uc, ufbxi_buf *tmp_buf)
{
ufbxi_ascii *ua = &uc->ascii;
ua->retain_buf = tmp_buf;
for (;;) {
size_t buffered = ufbxi_to_size(ua->src_yield - ua->src);
if (buffered == 0) {
char c = ufbxi_ascii_yield(uc);
ufbxi_check(c != '\0');
continue;
}
const char *begin = ua->src, *end;
const char *match = (const char*)memchr(begin, '}', buffered);
if (match) {
end = match;
} else {
end = begin + buffered;
}
ua->src = end;
size_t length = ufbxi_to_size(end - begin);
ufbxi_ascii_span *span = ufbxi_push(&uc->tmp_ascii_spans, ufbxi_ascii_span, 1);
ufbxi_check(span);
// Store the trailing '}' for parsing
if (match) length += 1;
span->length = length;
if (ua->src_is_retained || !uc->read_fn) {
span->source = begin;
} else {
span->source = ufbxi_push_copy(tmp_buf, char, length, begin);
ufbxi_check(span->source);
}
if (match) break;
}
ua->retain_buf = NULL;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_try_ignore_string(ufbxi_context *uc, ufbxi_ascii_token *token)
{
ufbxi_ascii *ua = &uc->ascii;
char c = ufbxi_ascii_skip_whitespace(uc);
token->str_len = 0;
if (c == '"') {
// Replace `prev_token` with `token` but swap the buffers so `token` uses
// the now-unused string buffer of the old `prev_token`.
char *swap_data = ua->prev_token.str_data;
size_t swap_cap = ua->prev_token.str_cap;
ua->prev_token = ua->token;
ua->token.str_data = swap_data;
ua->token.str_cap = swap_cap;
token->type = UFBXI_ASCII_STRING;
// Skip opening quote
ufbxi_ascii_next(uc);
ufbxi_check(ufbxi_ascii_skip_until(uc, '"'));
// Skip closing quote
ufbxi_ascii_next(uc);
return true;
}
return false;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_next_token(ufbxi_context *uc, ufbxi_ascii_token *token)
{
ufbxi_ascii *ua = &uc->ascii;
// Replace `prev_token` with `token` but swap the buffers so `token` uses
// the now-unused string buffer of the old `prev_token`.
char *swap_data = ua->prev_token.str_data;
size_t swap_cap = ua->prev_token.str_cap;
ua->prev_token = ua->token;
ua->token.str_data = swap_data;
ua->token.str_cap = swap_cap;
char c = ufbxi_ascii_skip_whitespace(uc);
token->str_len = 0;
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
token->type = UFBXI_ASCII_BARE_WORD;
while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9') || c == '_') {
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c));
c = ufbxi_ascii_next(uc);
}
// Skip whitespace to find if there's a following ':'
c = ufbxi_ascii_skip_whitespace(uc);
if (c == ':') {
token->value.name_len = token->str_len;
token->type = UFBXI_ASCII_NAME;
ufbxi_ascii_next(uc);
}
} else if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') {
token->type = UFBXI_ASCII_INT;
token->negative = c == '-';
while ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') {
if (c == '.' || c == 'e' || c == 'E') {
token->type = UFBXI_ASCII_FLOAT;
}
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c));
c = ufbxi_ascii_next(uc);
}
if (c == '#') {
ufbxi_check(token->type == UFBXI_ASCII_FLOAT);
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c));
c = ufbxi_ascii_next(uc);
bool is_inf = c == 'I' || c == 'i';
while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c));
c = ufbxi_ascii_next(uc);
}
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, '\0'));
if (is_inf) {
token->value.f64 = token->str_data[0] == '-' ? -UFBX_INFINITY : UFBX_INFINITY;
} else {
token->value.f64 = UFBX_NAN;
}
} else {
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, '\0'));
char *end;
if (token->type == UFBXI_ASCII_INT) {
token->value.i64 = ufbxi_parse_int64(token->str_data, &end);
ufbxi_check(end == token->str_data + token->str_len - 1);
} else if (token->type == UFBXI_ASCII_FLOAT) {
if (ua->parse_as_f32) {
token->value.f64 = strtof(token->str_data, &end);
} else {
token->value.f64 = ufbxi_parse_double(token->str_data, token->str_len, &end, uc->double_parse_flags);
}
ufbxi_check(end == token->str_data + token->str_len - 1);
}
}
} else if (c == '"') {
token->type = UFBXI_ASCII_STRING;
c = ufbxi_ascii_next(uc);
while (c != '"') {
// Optimized string parsing for non-special characters
if (ua->src + 1 < ua->src_yield) {
const char *begin = ua->src;
const char *end = ua->src_yield;
const char *quot = (const char*)memchr(begin, '"', ufbxi_to_size(end - begin));
if (quot) end = quot;
const char *esc = (const char*)memchr(begin, '&', ufbxi_to_size(end - begin));
if (esc) end = esc;
if (begin < end) {
ufbxi_check(ufbxi_ascii_push_token_string(uc, token, begin, ufbxi_to_size(end - begin)));
ua->src = end;
c = ufbxi_ascii_peek(uc);
continue;
}
}
// Escape XML-like elements, funny enough there is no way to escape '&' itself, there is no `&amp`.
// '&quot;' -> '"'
// '&cr;' -> '\r'
// '&lf;' -> '\n'
if (c == '&') {
const char *entity = NULL;
char replacement = '\0';
c = ufbxi_ascii_next(uc);
switch (c) {
case 'q':
entity = "&quot;";
replacement = '"';
break;
case 'c':
entity = "&cr;";
replacement = '\r';
break;
case 'l':
entity = "&lf;";
replacement = '\n';
break;
default:
// As '&' is not escaped in any way just map '&' -> '&'
entity = "&";
replacement = '&';
break;
}
size_t step = 1;
ufbxi_dev_assert(entity && *entity);
// `entity` is a NULL terminated string longer than a single character
// cppcheck-suppress arrayIndexOutOfBounds
for (; entity[step]; step++) {
if (c != entity[step]) break;
c = ufbxi_ascii_next(uc);
}
if (entity[step] == '\0') {
// Full match: Push the replacement character
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, replacement));
} else {
// Partial match: Push the prefix we have skipped already
for (size_t i = 0; i < step; i++) {
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, entity[i]));
}
}
continue;
}
ufbxi_check(c != '\0');
ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c));
c = ufbxi_ascii_next(uc);
}
// Skip closing quote
ufbxi_ascii_next(uc);
} else {
// Single character token
token->type = c;
ufbxi_ascii_next(uc);
}
return 1;
}
ufbxi_nodiscard static int ufbxi_ascii_accept(ufbxi_context *uc, char type)
{
ufbxi_ascii *ua = &uc->ascii;
if (ua->token.type == type) {
ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token));
return 1;
} else {
return 0;
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_int_array(ufbxi_context *uc, char type, size_t *p_num_read)
{
ufbxi_ascii *ua = &uc->ascii;
if (ua->parse_as_f32) return 1;
size_t initial_items = uc->tmp_stack.num_items;
int64_t val;
if (ua->token.type == UFBXI_ASCII_INT) {
val = ua->token.value.i64;
} else {
return 1;
}
const char *src = ua->src;
const char *end = ua->src_yield;
const char *src_scan = src;
for (;;) {
// Skip '\s*,\s*' between array elements. If we don't find a comma after an element
// don't push it as we can't be 100% certain whether it's a part of the array.
while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++;
if (src_scan == end || *src_scan != ',') break;
src_scan++;
while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++;
// Found comma, commit to the position and push the previous value to the array
src = src_scan;
if (type == 'i') {
int32_t *v = ufbxi_push_fast(&uc->tmp_stack, int32_t, 1);
ufbxi_check(v);
*v = (int32_t)val;
} else if (type == 'l') {
int64_t *v = ufbxi_push_fast(&uc->tmp_stack, int64_t, 1);
ufbxi_check(v);
*v = (int64_t)val;
}
// Try to parse the next value, we don't commit this until we find a comma after it above.
size_t left = ufbxi_to_size(end - src_scan);
if (left < 32) break;
val = ufbxi_parse_int64(src_scan, (char**)&src_scan);
if (!src_scan) break;
}
// Resume conventional parsing if we moved `src`.
if (src != ua->src) {
ua->src = src;
ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token));
}
*p_num_read = uc->tmp_stack.num_items - initial_items;
return 1;
}
typedef struct {
void *arr_data;
char arr_type;
size_t arr_size;
const ufbxi_ascii_span *spans;
size_t num_spans;
size_t offset;
} ufbxi_ascii_array_task;
ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_floats(ufbxi_ascii_array_task *t, const char *src, const char *src_end, uint32_t parse_flags)
{
size_t offset = t->offset;
float *dst_float = t->arr_type == 'f' ? (float*)t->arr_data + offset : NULL;
double *dst_double = t->arr_type == 'd' ? (double*)t->arr_data + offset : NULL;
ufbx_assert(dst_float || dst_double);
const char *src_begin = src;
while (src != src_end) {
while (ufbxi_is_space(*src)) src++;
// Try to parse the next value, we don't commit this until we find a comma after it above.
char *num_end = NULL;
double val = ufbxi_parse_double(src, ufbxi_to_size(src_end - src), &num_end, parse_flags);
if (!num_end) return src_begin;
src = num_end;
while (ufbxi_is_space(*src)) src++;
if (*src != ',') break;
src++;
src_begin = src;
if (offset >= t->arr_size) return NULL;
if (dst_double) {
*dst_double++ = val;
} else {
*dst_float++ = (float)val;
}
offset++;
}
t->offset = offset;
return src_begin;
}
ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_ints(ufbxi_ascii_array_task *t, const char *src, const char *src_end)
{
size_t offset = t->offset;
int32_t *dst32 = t->arr_type == 'i' ? (int32_t*)t->arr_data + offset : NULL;
int64_t *dst64 = t->arr_type == 'l' ? (int64_t*)t->arr_data + offset : NULL;
ufbx_assert(dst32 || dst64);
const char *src_begin = src;
while (src != src_end) {
while (ufbxi_is_space(*src)) src++;
int64_t val = ufbxi_parse_int64(src, (char**)&src);
if (!src) return NULL;
while (ufbxi_is_space(*src)) src++;
if (*src != ',') break;
src++;
src_begin = src;
if (offset >= t->arr_size) return NULL;
if (dst32) {
*dst32++ = (int32_t)val;
} else {
*dst64++ = val;
}
offset++;
}
t->offset = offset;
return src_begin;
}
ufbxi_noinline static const char *ufbxi_ascii_array_task_parse(ufbxi_ascii_array_task *t, const char *src, const char *src_end)
{
if (t->arr_type == 'f' || t->arr_type == 'd') {
uint32_t flags = ufbxi_parse_double_init_flags();
return ufbxi_ascii_array_task_parse_floats(t, src, src_end, flags);
} else {
return ufbxi_ascii_array_task_parse_ints(t, src, src_end);
}
}
typedef enum {
UFBXI_ASCII_SCAN_STATE_VALUE,
UFBXI_ASCII_SCAN_STATE_WHITESPACE,
UFBXI_ASCII_SCAN_STATE_COMMENT,
UFBXI_ASCII_SCAN_STATE_COMMA,
} ufbxi_ascii_scan_state;
ufbxi_noinline static bool ufbxi_ascii_array_task_imp(ufbxi_ascii_array_task *t)
{
// Temporary buffer for parsing between spans
char buffer[128]; // ufbxi_uninit
size_t buffer_len = 0;
bool buffer_value = false;
ufbxi_ascii_scan_state state = UFBXI_ASCII_SCAN_STATE_WHITESPACE;
ufbxi_for(const ufbxi_ascii_span, span, t->spans, t->num_spans) {
const char *src = span->source;
const char *end = src + span->length;
while (src != end) {
// State machine for skipping whitespace and comments, potentially
// between multiple spans.
while (src != end) {
char c = *src;
if (state == UFBXI_ASCII_SCAN_STATE_VALUE) {
if (buffer_len >= sizeof(buffer) - 1) return false;
if (c == '"') {
return false;
} else if (c == ';' || ufbxi_is_space(c)) {
state = UFBXI_ASCII_SCAN_STATE_WHITESPACE;
buffer[buffer_len] = ' ';
buffer_len++;
} else if (c == ',' || c == '}') {
state = UFBXI_ASCII_SCAN_STATE_COMMA;
buffer[buffer_len] = ',';
buffer_len++;
src++;
break;
} else {
buffer_value = true;
buffer[buffer_len] = c;
buffer_len++;
src++;
}
} else if (state == UFBXI_ASCII_SCAN_STATE_WHITESPACE) {
if (c == ';') {
state = UFBXI_ASCII_SCAN_STATE_COMMENT;
} else if (ufbxi_is_space(c)) {
src++;
} else {
state = UFBXI_ASCII_SCAN_STATE_VALUE;
}
} else if (state == UFBXI_ASCII_SCAN_STATE_COMMENT) {
if (c == '\n') {
state = UFBXI_ASCII_SCAN_STATE_WHITESPACE;
} else {
src++;
}
} else if (state == UFBXI_ASCII_SCAN_STATE_COMMA) {
state = UFBXI_ASCII_SCAN_STATE_WHITESPACE;
}
}
if (state == UFBXI_ASCII_SCAN_STATE_COMMA) {
// Parse a value from the buffer
if (buffer_value) {
const char *buffer_end = ufbxi_ascii_array_task_parse(t, buffer, buffer + buffer_len);
if (buffer_end == NULL || buffer_end == buffer) {
return false;
}
}
// If not at end, we are past the last comma, so try to find a
// safe range to parse.
if (src != end) {
const char *parse_end = end;
while (parse_end > src) {
if (parse_end[-1] == ',') break;
parse_end--;
}
if (src < parse_end) {
src = ufbxi_ascii_array_task_parse(t, src, parse_end);
if (src == NULL) return false;
}
}
buffer_len = 0;
buffer_value = false;
}
}
}
if (t->offset != t->arr_size) return false;
return true;
}
ufbxi_noinline static bool ufbxi_ascii_array_task_fn(ufbxi_task *task)
{
ufbxi_ascii_array_task *t = (ufbxi_ascii_array_task *)task->data;
if (!ufbxi_ascii_array_task_imp(t)) {
task->error = "Threaded ASCII parse error";
return false;
}
return true;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_ascii_read_float_array(ufbxi_context *uc, char type, size_t *p_num_read)
{
ufbxi_ascii *ua = &uc->ascii;
if (ua->parse_as_f32) return 1;
double val;
if (ua->token.type == UFBXI_ASCII_FLOAT) {
val = ua->token.value.f64;
} else if (ua->token.type == UFBXI_ASCII_INT) {
double fsign = !ua->token.value.i64 && ua->token.negative ? -1.0 : 1.0;
val = (double)ua->token.value.i64 * fsign;
} else {
return 1;
}
const char *src = ua->src;
const char *end = ua->src_yield;
uint32_t parse_flags = uc->double_parse_flags | UFBXI_PARSE_DOUBLE_VERIFY_LENGTH;
size_t initial_items = uc->tmp_stack.num_items;
const char *src_scan = src;
for (;;) {
// Skip '\s*,\s*' between array elements. If we don't find a comma after an element
// don't push it as we can't be 100% certain whether it's a part of the array.
while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++;
if (src_scan == end || *src_scan != ',') break;
src_scan++;
while (src_scan != end && ufbxi_is_space(*src_scan)) src_scan++;
// Found comma, commit to the position and push the previous value to the array
src = src_scan;
if (type == 'd') {
double *v = ufbxi_push_fast(&uc->tmp_stack, double, 1);
ufbxi_check(v);
*v = (double)val;
} else if (type == 'f') {
float *v = ufbxi_push_fast(&uc->tmp_stack, float, 1);
ufbxi_check(v);
*v = (float)val;
}
// TODO: Collect ASCII numbers to deferred parse integer/string segments
// Try to parse the next value, we don't commit this until we find a comma after it above.
char *num_end = NULL;
size_t left = ufbxi_to_size(end - src_scan);
if (left < 64) break;
val = ufbxi_parse_double(src_scan, left - 2, &num_end, parse_flags);
if (!num_end || num_end == src_scan) {
break;
}
src_scan = num_end;
}
// Resume conventional parsing if we moved `src`.
if (src != ua->src) {
ua->src = src;
ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token));
}
*p_num_read = uc->tmp_stack.num_items - initial_items;
return 1;
}
// Recursion limited by check at the start
ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_parse_node(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive)
ufbxi_recursive_function(int, ufbxi_ascii_parse_node, (uc, depth, parent_state, p_end, tmp_buf, recursive), UFBXI_MAX_NODE_DEPTH + 1,
(ufbxi_context *uc, uint32_t depth, ufbxi_parse_state parent_state, bool *p_end, ufbxi_buf *tmp_buf, bool recursive))
{
ufbxi_ascii *ua = &uc->ascii;
if (ua->token.type == '}') {
ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token));
*p_end = true;
return 1;
}
if (ua->token.type == UFBXI_ASCII_END) {
ufbxi_check_msg(depth == 0, "Truncated file");
*p_end = true;
return 1;
}
// Parse the name eg. "Node:" token and intern the name
ufbxi_check(depth < UFBXI_MAX_NODE_DEPTH);
if (!uc->sure_fbx && depth == 0 && ua->token.type != UFBXI_ASCII_NAME) {
ufbxi_fail_msg("Expected a 'Name:' token", "Not an FBX file");
}
ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_NAME));
size_t name_len = ua->prev_token.value.name_len;
ufbxi_check(name_len <= 0xff);
const char *name = ufbxi_push_string(&uc->string_pool, ua->prev_token.str_data, ua->prev_token.str_len, NULL, true);
ufbxi_check(name);
// Push the parsed node into the `tmp_stack` buffer, the nodes will be popped by
// calling code after its done parsing all of it's children.
ufbxi_node *node = ufbxi_push_zero(&uc->tmp_stack, ufbxi_node, 1);
ufbxi_check(node);
node->name = name;
node->name_len = (uint8_t)name_len;
bool in_ascii_array = false;
uint32_t num_values = 0;
uint32_t type_mask = 0;
int arr_type = 0;
ufbxi_buf *arr_buf = NULL;
size_t arr_elem_size = 0;
// Check if the values of the node we're parsing currently should be
// treated as an array.
ufbxi_array_info arr_info;
if (ufbxi_is_array_node(uc, parent_state, name, &arr_info)) {
uint32_t flags = arr_info.flags;
arr_type = ufbxi_normalize_array_type(arr_info.type, 'b');
arr_buf = tmp_buf;
if (flags & UFBXI_ARRAY_FLAG_RESULT) arr_buf = &uc->result;
else if (flags & UFBXI_ARRAY_FLAG_TMP_BUF) arr_buf = &uc->tmp;
ufbxi_value_array *arr = ufbxi_push(tmp_buf, ufbxi_value_array, 1);
ufbxi_check(arr);
node->value_type_mask = UFBXI_VALUE_ARRAY;
node->array = arr;
arr->type = (char)arr_type;
// Parse array values using strtof() if the array destination is 32-bit float
// since KeyAttrDataFloat packs integer data (!) into floating point values so we
// should try to be as exact as possible.
if (arr_info.flags & UFBXI_ARRAY_FLAG_ACCURATE_F32) {
ua->parse_as_f32 = true;
}
arr_elem_size = ufbxi_array_type_size((char)arr_type);
if (arr_type != '-') {
// Force alignment for array contents: This allows us to use `ufbxi_push_fast()`
// in fast parsing functions.
ufbxi_check(ufbxi_push_size_zero(&uc->tmp_stack, 8, 1));
// Pad with 4 zero elements to make indexing with `-1` safe.
if ((flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) != 0) {
ufbxi_check(ufbxi_push_size_zero(&uc->tmp_stack, arr_elem_size, 4));
num_values += 4;
}
}
}
// Some fields in ASCII may have leading commas eg. `Content: , "base64-string"`
if (ua->token.type == ',') {
// HACK: If we are parsing an "array" that should be ignored, ie. `Content` when
// `opts.ignore_embedded == true` try to skip the next token string if possible.
if (arr_type == '-') {
if (!ufbxi_ascii_try_ignore_string(uc, &ua->token)) {
ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token));
}
} else {
ufbxi_check(ufbxi_ascii_next_token(uc, &ua->token));
}
}
ufbxi_parse_state parse_state = ufbxi_update_parse_state(parent_state, node->name);
ufbxi_value vals[UFBXI_MAX_NON_ARRAY_VALUES];
uint32_t deferred_size = 0;
// NOTE: Infinite loop to allow skipping the comma parsing via `continue`.
for (;;) {
ufbxi_ascii_token *tok = &ua->prev_token;
if (arr_type) {
size_t num_read = 0;
if (arr_type == 'f' || arr_type == 'd') {
ufbxi_check(ufbxi_ascii_read_float_array(uc, (char)arr_type, &num_read));
} else if (arr_type == 'i' || arr_type == 'l') {
ufbxi_check(ufbxi_ascii_read_int_array(uc, (char)arr_type, &num_read));
}
ufbxi_check(UINT32_MAX - num_values > num_read);
num_values += (uint32_t)num_read;
}
if (ufbxi_ascii_accept(uc, UFBXI_ASCII_STRING)) {
if (arr_type) {
if (arr_type == 's' || arr_type == 'S' || arr_type == 'C') {
bool raw = arr_type == 's';
ufbx_string *v = ufbxi_push(&uc->tmp_stack, ufbx_string, 1);
ufbxi_check(v);
v->data = tok->str_data;
v->length = tok->str_len;
if (arr_type == 'C') {
ufbxi_buf *buf = uc->opts.retain_dom ? &uc->result : tmp_buf;
v->data = ufbxi_push_copy(buf, char, v->length, v->data);
ufbxi_check(v->data);
} else {
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, v, raw));
}
} else {
// Ignore strings in non-string arrays, decrement `num_values` as it will be
// incremented after the loop iteration is done to ignore it.
num_values--;
}
} else if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) {
type_mask |= (uint32_t)UFBXI_VALUE_STRING << (num_values*2);
ufbxi_value *v = &vals[num_values];
const char *str = tok->str_data;
size_t length = tok->str_len;
ufbxi_check(str);
if (length == 0) {
v->s.raw_data = ufbxi_empty_char;
v->s.raw_length = 0;
v->s.utf8_length = 0;
} else {
bool non_ascii = false;
uint32_t hash = ufbxi_hash_string_check_ascii(str, length, &non_ascii);
bool raw = !non_ascii || ufbxi_is_raw_string(uc, parent_state, name, num_values);
ufbxi_check(ufbxi_push_sanitized_string(&uc->string_pool, &v->s, str, length, hash, raw));
if (non_ascii && raw) v->s.utf8_length = UINT32_MAX;
}
}
} else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_INT)) {
int64_t val = tok->value.i64;
ufbx_real fsign = !val && tok->negative ? (ufbx_real)-1.0f : (ufbx_real)1.0f;
switch (arr_type) {
case 0:
// Parse version from comment if there was no magic comment
if (!ua->found_version && parse_state == UFBXI_PARSE_FBX_VERSION && num_values == 0) {
if (val >= 6000 && val <= 10000) {
ua->found_version = true;
uc->version = (uint32_t)val;
}
}
if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) {
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2);
ufbxi_value *v = &vals[num_values];
// False positive: `v->f` and `v->i` do not overlap in the union.
// cppcheck-suppress overlappingWriteUnion
v->f = (double)(v->i = val) * (double)fsign;
}
break;
case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break;
case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break;
case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = (int32_t)val; } break;
case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = (int64_t)val; } break;
case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val * (float)fsign; } break;
case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val * (double)fsign; } break;
case '-': num_values--; break;
default:
ufbxi_fail("Bad array dst type");
}
} else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_FLOAT)) {
double val = tok->value.f64;
switch (arr_type) {
case 0:
if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) {
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2);
ufbxi_value *v = &vals[num_values];
// False positive: `v->f` and `v->i` do not overlap in the union.
// cppcheck-suppress overlappingWriteUnion
v->i = ufbxi_f64_to_i64(v->f = val);
}
break;
case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break;
case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break;
case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = ufbxi_f64_to_i32(val); } break;
case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = ufbxi_f64_to_i64(val); } break;
case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val; } break;
case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val; } break;
case '-': num_values--; break;
default:
ufbxi_fail("Bad array dst type");
}
} else if (ufbxi_ascii_accept(uc, UFBXI_ASCII_BARE_WORD)) {
int64_t val = 0;
if (tok->str_len >= 1) {
val = (int64_t)tok->str_data[0];
}
switch (arr_type) {
case 0:
if (num_values < UFBXI_MAX_NON_ARRAY_VALUES) {
type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (num_values*2);
ufbxi_value *v = &vals[num_values];
// False positive: `v->f` and `v->i` do not overlap in the union.
// cppcheck-suppress overlappingWriteUnion
v->f = (double)(v->i = val);
}
break;
case 'b': { bool *v = ufbxi_push(&uc->tmp_stack, bool, 1); ufbxi_check(v); *v = val != 0; } break;
case 'c': { uint8_t *v = ufbxi_push(&uc->tmp_stack, uint8_t, 1); ufbxi_check(v); *v = (uint8_t)val; } break;
case 'i': { int32_t *v = ufbxi_push(&uc->tmp_stack, int32_t, 1); ufbxi_check(v); *v = (int32_t)val; } break;
case 'l': { int64_t *v = ufbxi_push(&uc->tmp_stack, int64_t, 1); ufbxi_check(v); *v = (int64_t)val; } break;
case 'f': { float *v = ufbxi_push(&uc->tmp_stack, float, 1); ufbxi_check(v); *v = (float)val; } break;
case 'd': { double *v = ufbxi_push(&uc->tmp_stack, double, 1); ufbxi_check(v); *v = (double)val; } break;
case '-': num_values--; break;
default:
ufbxi_fail("Bad array dst type");
}
} else if (ufbxi_ascii_accept(uc, '*')) {
// Parse a post-7000 ASCII array eg. "*3 { 1,2,3 }"
ufbxi_check(!in_ascii_array);
ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_INT));
int64_t count = ua->prev_token.value.i64;
if (ufbxi_ascii_accept(uc, '{')) {
ufbxi_check(ufbxi_ascii_accept(uc, UFBXI_ASCII_NAME));
in_ascii_array = true;
// Optimized array skipping and threaded parsing
if (arr_type == '-') {
ufbxi_check(ufbxi_ascii_skip_until(uc, '}'));
} else if (uc->parse_threaded && !uc->opts.force_single_thread_ascii_parsing
&& !ua->parse_as_f32
&& (arr_type == 'i' || arr_type == 'l' || arr_type == 'f' || arr_type == 'd')) {
// Don't bother with small arrays due to fixed overhead
if (count >= UFBXI_MIN_THREADED_ASCII_VALUES && count <= UINT32_MAX) {
deferred_size = (uint32_t)count - 1;
ufbxi_check(ufbxi_ascii_store_array(uc, tmp_buf));
}
}
}
// NOTE: This `continue` skips incrementing `num_values` and parsing
// a comma, continuing to parse the values in the array.
continue;
} else {
break;
}
// Add value and keep parsing if there's a comma. This part may be
// skipped if we enter an array block.
num_values++;
ufbxi_check(num_values < UINT32_MAX);
if (!ufbxi_ascii_accept(uc, ',')) break;
}
// Close the ASCII array if we are in one
if (in_ascii_array) {
ufbxi_check(ufbxi_ascii_accept(uc, '}'));
}
ua->parse_as_f32 = false;
if (arr_type) {
if (arr_type == '-') {
node->array->data = NULL;
node->array->size = 0;
} else {
void *arr_data = NULL;
if (deferred_size > 0) {
arr_data = ufbxi_push_size(arr_buf, arr_elem_size, num_values + deferred_size);
// Pop any previously pushed values
if (num_values > 0) {
ufbxi_pop_size(&uc->tmp_stack, arr_elem_size, num_values, arr_data, false);
}
} else {
arr_data = ufbxi_push_pop_size(arr_buf, &uc->tmp_stack, arr_elem_size, num_values);
}
ufbxi_check(arr_data);
if (arr_info.flags & UFBXI_ARRAY_FLAG_PAD_BEGIN) {
node->array->data = (char*)arr_data + 4*arr_elem_size;
node->array->size = num_values + deferred_size - 4;
} else {
node->array->data = arr_data;
node->array->size = num_values + deferred_size;
}
// Pop alignment helper
ufbxi_pop_size(&uc->tmp_stack, 8, 1, NULL, false);
// Deferred parsing
if (deferred_size > 0) {
size_t num_spans = uc->tmp_ascii_spans.num_items;
ufbxi_ascii_span *spans = ufbxi_push_pop(tmp_buf, &uc->tmp_ascii_spans, ufbxi_ascii_span, num_spans);
ufbxi_check(spans);
ufbxi_ascii_array_task t; // ufbxi_uninit
t.arr_data = (char*)arr_data + num_values * arr_elem_size;
t.arr_type = (char)arr_type;
t.arr_size = deferred_size;
t.num_spans = num_spans;
t.spans = spans;
t.offset = 0;
// TODO: Split these further
ufbxi_task *task = ufbxi_thread_pool_create_task(&uc->thread_pool, &ufbxi_ascii_array_task_fn);
if (task) {
task->data = ufbxi_push_copy(tmp_buf, ufbxi_ascii_array_task, 1, &t);
ufbxi_check(task->data);
ufbxi_thread_pool_run_task(&uc->thread_pool, task);
} else {
ufbxi_check_msg(ufbxi_ascii_array_task_imp(&t), "Threaded ASCII parse error");
}
}
}
} else {
num_values = ufbxi_min32(num_values, UFBXI_MAX_NON_ARRAY_VALUES);
node->value_type_mask = (uint16_t)type_mask;
node->vals = ufbxi_push_copy(tmp_buf, ufbxi_value, num_values, vals);
ufbxi_check(node->vals);
}
// Recursively parse the children of this node. Update the parse state
// to provide context for child node parsing.
if (ufbxi_ascii_accept(uc, '{')) {
if (recursive) {
size_t num_children = 0;
for (;;) {
bool end = false;
ufbxi_check(ufbxi_ascii_parse_node(uc, depth + 1, parse_state, &end, tmp_buf, recursive));
if (end) break;
num_children++;
}
// Pop children from `tmp_stack` to a contiguous array
node->children = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node, num_children);
ufbxi_check(node->children);
node->num_children = (uint32_t)num_children;
}
uc->has_next_child = true;
} else {
uc->has_next_child = false;
}
return 1;
}
// -- DOM retention
typedef struct {
uintptr_t node_ptr;
ufbx_dom_node *dom_node;
} ufbxi_dom_mapping;
ufbxi_nodiscard static ufbxi_noinline ufbx_dom_node *ufbxi_get_dom_node_imp(ufbxi_context *uc, ufbxi_node *node)
{
if (!node) return NULL;
ufbxi_dom_mapping mapping = { (uintptr_t)node, NULL };
uint32_t hash = ufbxi_hash_uptr(mapping.node_ptr);
ufbxi_dom_mapping *result = ufbxi_map_find(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping);
return result ? result->dom_node : NULL;
}
ufbxi_nodiscard static ufbxi_forceinline ufbx_dom_node *ufbxi_get_dom_node(ufbxi_context *uc, ufbxi_node *node)
{
if (!uc->opts.retain_dom) return NULL;
return ufbxi_get_dom_node_imp(uc, node);
}
// Recursion limited by check in ufbxi_[binary/ascii]_parse_node()
ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_dom_node(ufbxi_context *uc, ufbxi_node *node, ufbx_dom_node **p_dom_node)
ufbxi_recursive_function(int, ufbxi_retain_dom_node, (uc, node, p_dom_node), UFBXI_MAX_NODE_DEPTH + 1,
(ufbxi_context *uc, ufbxi_node *node, ufbx_dom_node **p_dom_node))
{
ufbx_dom_node *dst = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1);
ufbxi_check(dst);
ufbxi_check(ufbxi_push_copy(&uc->tmp_dom_nodes, ufbx_dom_node*, 1, &dst));
if (p_dom_node) {
*p_dom_node = dst;
}
dst->name.data = node->name;
dst->name.length = node->name_len;
{
ufbxi_dom_mapping mapping = { (uintptr_t)node, NULL };
uint32_t hash = ufbxi_hash_uptr(mapping.node_ptr);
ufbxi_dom_mapping *result = ufbxi_map_find(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping);
if (!result) {
result = ufbxi_map_insert(&uc->dom_node_map, ufbxi_dom_mapping, hash, &mapping);
ufbxi_check(result);
}
result->node_ptr = (uintptr_t)node;
result->dom_node = dst;
}
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst->name, false));
if (node->value_type_mask == UFBXI_VALUE_ARRAY) {
ufbxi_value_array *arr = node->array;
ufbx_dom_value *val = ufbxi_push_zero(&uc->result, ufbx_dom_value, 1);
ufbxi_check(val);
dst->values.data = val;
dst->values.count = 1;
size_t elem_size = ufbxi_array_type_size(arr->type);
val->value_str.data = ufbxi_empty_char;
val->value_blob.data = arr->data;
val->value_blob.size = arr->size * elem_size;
val->value_float = (double)(val->value_int = (int64_t)arr->size);
switch (arr->type) {
case 'c': val->type = UFBX_DOM_VALUE_ARRAY_I8; break;
case 'b': val->type = UFBX_DOM_VALUE_ARRAY_I8; break;
case 'i': val->type = UFBX_DOM_VALUE_ARRAY_I32; break;
case 'l': val->type = UFBX_DOM_VALUE_ARRAY_I64; break;
case 'f': val->type = UFBX_DOM_VALUE_ARRAY_F32; break;
case 'd': val->type = UFBX_DOM_VALUE_ARRAY_F64; break;
case 's': val->type = UFBX_DOM_VALUE_ARRAY_RAW_STRING; break;
case 'C': val->type = UFBX_DOM_VALUE_ARRAY_RAW_STRING; break;
case '-': val->type = UFBX_DOM_VALUE_ARRAY_IGNORED; break;
default: ufbxi_fail("Bad array type");
}
} else {
size_t ix;
for (ix = 0; ix < UFBXI_MAX_NON_ARRAY_VALUES; ix++) {
uint32_t mask = (node->value_type_mask >> (2*ix)) & 0x3;
if (!mask) break;
ufbx_dom_value *val = ufbxi_push_zero(&uc->tmp_stack, ufbx_dom_value, 1);
ufbxi_check(val);
val->value_str.data = ufbxi_empty_char;
if (mask == UFBXI_VALUE_STRING) {
val->type = UFBX_DOM_VALUE_STRING;
ufbxi_ignore(ufbxi_get_val_at(node, ix, 'S', &val->value_str));
ufbxi_ignore(ufbxi_get_val_at(node, ix, 'b', &val->value_blob));
} else {
ufbx_assert(mask == UFBXI_VALUE_NUMBER);
val->type = UFBX_DOM_VALUE_NUMBER;
val->value_int = node->vals[ix].i;
val->value_float = node->vals[ix].f;
}
}
dst->values.count = ix;
dst->values.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_dom_value, ix);
ufbxi_check(dst->values.data);
}
if (node->num_children > 0) {
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
ufbxi_check(ufbxi_retain_dom_node(uc, child, NULL));
}
dst->children.count = node->num_children;
dst->children.data = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, node->num_children);
ufbxi_check(dst->children.data);
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_toplevel(ufbxi_context *uc, ufbxi_node *node)
{
if (uc->dom_parse_num_children > 0) {
ufbx_dom_node **children = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, uc->dom_parse_num_children);
ufbxi_check(children);
uc->dom_parse_toplevel->children.data = children;
uc->dom_parse_toplevel->children.count = uc->dom_parse_num_children;
uc->dom_parse_num_children = 0;
}
if (node) {
ufbxi_check(ufbxi_retain_dom_node(uc, node, &uc->dom_parse_toplevel));
} else {
uc->dom_parse_toplevel = NULL;
// Called with NULL argument to finish retaining DOM, collect the final nodes to `ufbx_scene`.
size_t num_top_nodes = uc->tmp_dom_nodes.num_items;
ufbx_dom_node **nodes = ufbxi_push_pop(&uc->result, &uc->tmp_dom_nodes, ufbx_dom_node*, num_top_nodes);
ufbxi_check(nodes);
ufbx_dom_node *dom_root = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1);
ufbxi_check(dom_root);
dom_root->name.data = ufbxi_empty_char;
dom_root->children.data = nodes;
dom_root->children.count = num_top_nodes;
uc->scene.dom_root = dom_root;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_retain_toplevel_child(ufbxi_context *uc, ufbxi_node *child)
{
ufbx_assert(uc->dom_parse_toplevel);
ufbxi_check(ufbxi_retain_dom_node(uc, child, NULL));
uc->dom_parse_num_children++;
return 1;
}
// -- General parsing
static ufbxi_noinline bool ufbxi_next_line(ufbx_string *line, ufbx_string *buf, bool skip_space)
{
if (buf->length == 0) return false;
const char *newline = (const char*)memchr(buf->data, '\n', buf->length);
size_t length = newline ? ufbxi_to_size(newline - buf->data) + 1 : buf->length;
line->data = buf->data;
line->length = length;
buf->data += length;
buf->length -= length;
if (skip_space) {
while (line->length > 0 && ufbxi_is_space(line->data[0])) {
line->data++;
line->length--;
}
while (line->length > 0 && ufbxi_is_space(line->data[line->length - 1])) {
line->length--;
}
}
return true;
}
// Recursion limited by compile time patterns
static ufbxi_noinline const char *ufbxi_match_skip(const char *fmt, bool alternation)
ufbxi_recursive_function(const char *, ufbxi_match_skip, (fmt, alternation), 4,
(const char *fmt, bool alternation))
{
for (;;) {
char c = *fmt++;
switch (c) {
case '(':
fmt = ufbxi_match_skip(fmt, false) + 1;
break;
case '\\':
fmt++;
break;
case '[':
c = *fmt;
while (c != ']') {
c = *fmt++;
if (c == '\\') {
c = *fmt++;
}
}
fmt++;
break;
case '|':
if (alternation) return fmt - 1;
break;
case ')':
case '\0':
return fmt - 1;
default: break;
}
}
}
// Recursion limited by compile time patterns
static ufbxi_noinline bool ufbxi_match_imp(const char **p_str, const char *end, const char **p_fmt)
ufbxi_recursive_function(bool, ufbxi_match_imp, (p_str, end, p_fmt), 4,
(const char **p_str, const char *end, const char **p_fmt))
{
const char *str_original_begin = *p_str;
const char *str = str_original_begin;
const char *fmt_begin = *p_fmt;
const char *fmt = fmt_begin;
bool case_insensitive = false;
size_t count = 0;
for (;;) {
char c = *fmt++;
if (!c) {
*p_str = str;
*p_fmt = fmt - 1;
return true;
}
const char *str_begin = str;
char ref = str != end ? *str : '\0';
if (case_insensitive) {
if (ref >= 'A' && ref <= 'Z') {
ref = (char)((int)(ref - 'A') + 'a');
}
}
bool ok = false;
switch (c) {
case '\\': {
const char *macro = NULL;
c = *fmt++;
switch (c) {
case 'd':
macro = "[0-9]";
break;
case 'F':
macro = "[\\-+]?[0-9]+(\\.[0-9]+)?([eE][\\-+]?[0-9]+)?";
break;
case 's':
if (ufbxi_is_space(ref)) {
ok = true;
str++;
}
break;
case 'S':
if (!ufbxi_is_space(ref)) {
ok = true;
str++;
}
break;
case 'c':
case 'C':
case_insensitive = c == 'c';
ok = true;
break;
default:
if (ref == c) {
ok = true;
str++;
}
break;
}
if (macro) {
ok = ufbxi_match_imp(&str, end, &macro);
}
} break;
case '[': {
while (fmt[0] != ']') {
if (fmt[0] == '\\') {
if (ref == fmt[1]) ok = true;
fmt += 2;
} else if (fmt[1] == '-') {
if (ref >= fmt[0] && ref <= fmt[2]) {
ok = true;
}
fmt += 3;
} else {
if (ref == fmt[0]) ok = true;
fmt += 1;
}
}
fmt++;
if (ok) str++;
} break;
case '(':
if (ufbxi_match_imp(&str, end, &fmt)) {
ok = true;
}
break;
case '|':
fmt = ufbxi_match_skip(fmt, false);
ok = true;
break;
case ')':
*p_str = str;
*p_fmt = fmt;
return true;
case '.':
if (ref != '\0') {
ok = true;
str++;
}
break;
default:
if (c == ref) {
str++;
ok = true;
}
break;
}
bool did_fail = false;
c = *fmt;
switch (c) {
case '*':
fmt++;
if (ok) {
fmt = fmt_begin;
count++;
continue;
}
break;
case '+':
fmt++;
if (ok) {
fmt = fmt_begin;
count++;
continue;
} else if (count == 0) {
did_fail = true;
}
break;
case '?':
fmt++;
break;
default:
did_fail = !ok;
break;
}
if (did_fail) {
fmt = ufbxi_match_skip(fmt, true);
if (*fmt == '|') {
fmt++;
str = str_original_begin;
} else {
*p_fmt = ufbxi_match_skip(fmt, false) + 1;
return false;
}
} else {
if (!ok) {
str = str_begin;
}
}
fmt_begin = fmt;
count = 0;
}
}
static ufbxi_noinline bool ufbxi_match(const ufbx_string *str, const char *fmt)
{
const char *ptr = str->data, *end = str->data + str->length;
if (ufbxi_match_imp(&ptr, end, &fmt)) {
return ptr == end;
} else {
return false;
}
}
static ufbxi_noinline bool ufbxi_is_format(const char *data, size_t size, ufbx_file_format format)
{
ufbx_string line, buf = { data, size };
if (format == UFBX_FILE_FORMAT_FBX) {
if (size >= UFBXI_BINARY_MAGIC_SIZE && !memcmp(data, ufbxi_binary_magic, UFBXI_BINARY_MAGIC_SIZE)) {
return true;
}
while (ufbxi_next_line(&line, &buf, true)) {
if (ufbxi_match(&line, ";\\s*FBX\\s*\\d+\\.\\d+\\.\\d+\\s*project\\s+file")) return true;
if (ufbxi_match(&line, "FBXHeaderExtension:.*")) return true;
}
} else if (format == UFBX_FILE_FORMAT_OBJ) {
while (ufbxi_next_line(&line, &buf, true)) {
const char *pattern =
"(vn?\\s+\\F|vt)\\s+\\F\\s+\\F.*" "|"
"f\\s+[\\-/0-9]+\\s+[\\-/0-9]+\\s*[\\-/0-9]+.*" "|"
"(usemtl|mtllib)\\s+\\S.*";
if (ufbxi_match(&line, pattern)) return true;
}
} else if (format == UFBX_FILE_FORMAT_MTL) {
while (ufbxi_next_line(&line, &buf, true)) {
const char *pattern =
"newmtl\\s+\\S.*";
if (ufbxi_match(&line, pattern)) return true;
}
} else {
ufbxi_unreachable("Unhandled format");
}
return false;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_determine_format(ufbxi_context *uc)
{
ufbx_file_format format = uc->opts.file_format;
if (format == UFBX_FILE_FORMAT_UNKNOWN && !uc->opts.no_format_from_content) {
ufbxi_pause_progress(uc);
size_t lookahead = UFBXI_MIN_FILE_FORMAT_LOOKAHEAD;
while (format == UFBX_FILE_FORMAT_UNKNOWN && lookahead <= uc->opts.file_format_lookahead) {
if (lookahead > uc->data_size) {
if (uc->eof) break;
ufbxi_check(ufbxi_refill(uc, lookahead, false));
}
size_t data_size = ufbxi_min_sz(lookahead, uc->data_size);
ufbxi_check_msg(data_size > 0, "Empty file");
for (uint32_t fmt = UFBX_FILE_FORMAT_FBX; fmt < UFBX_FILE_FORMAT_COUNT; fmt++) {
if (ufbxi_is_format(uc->data, data_size, (ufbx_file_format)fmt)) {
format = (ufbx_file_format)fmt;
break;
}
}
if (lookahead >= uc->opts.file_format_lookahead) {
break;
} else if (lookahead < SIZE_MAX / 2) {
lookahead = ufbxi_min_sz(lookahead * 2, uc->opts.file_format_lookahead);
} else {
lookahead = SIZE_MAX;
}
}
ufbxi_check(ufbxi_resume_progress(uc));
}
if (format == UFBX_FILE_FORMAT_UNKNOWN && !uc->opts.no_format_from_extension) {
if (uc->opts.filename.length > 0) {
ufbx_string extension = uc->opts.filename;
for (size_t i = extension.length; i > 0; i--) {
if (extension.data[i - 1] == '.') {
extension.data += i - 1;
extension.length -= i - 1;
break;
}
}
if (ufbxi_match(&extension, "\\c\\.fbx")) {
format = UFBX_FILE_FORMAT_FBX;
} else if (ufbxi_match(&extension, "\\c\\.obj")) {
format = UFBX_FILE_FORMAT_OBJ;
} else if (ufbxi_match(&extension, "\\c\\.mtl")) {
format = UFBX_FILE_FORMAT_MTL;
}
}
}
ufbxi_check_msg(format != UFBX_FILE_FORMAT_UNKNOWN, "Unrecognized file format");
uc->scene.metadata.file_format = format;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_begin_parse(ufbxi_context *uc)
{
const char *header = ufbxi_peek_bytes(uc, UFBXI_BINARY_HEADER_SIZE);
ufbxi_check(header);
// If the file starts with the binary magic parse it as binary, otherwise
// treat it as an ASCII file.
if (!memcmp(header, ufbxi_binary_magic, UFBXI_BINARY_MAGIC_SIZE)) {
// The byte after the magic indicates endianness
char endian = header[UFBXI_BINARY_MAGIC_SIZE + 0];
uc->file_big_endian = endian != 0;
// Read the version directly from the header
const char *version_word = header + UFBXI_BINARY_MAGIC_SIZE + 1;
if (uc->file_big_endian) {
version_word = ufbxi_swap_endian(uc, version_word, 1, 4);
ufbxi_check(version_word);
}
uc->version = ufbxi_read_u32(version_word);
// This is quite probably an FBX file..
uc->sure_fbx = true;
ufbxi_consume_bytes(uc, UFBXI_BINARY_HEADER_SIZE);
} else {
uc->from_ascii = true;
// Use the current read buffer as the initial parse buffer
memset(&uc->ascii, 0, sizeof(uc->ascii));
uc->ascii.src = uc->data;
uc->ascii.src_yield = uc->data + uc->yield_size;
uc->ascii.src_end = uc->data + uc->data_size + uc->yield_size;
// Initialize the first token
ufbxi_check(ufbxi_ascii_next_token(uc, &uc->ascii.token));
// Default to version 7400 if not found in header
if (uc->version > 0) {
uc->sure_fbx = true;
} else {
if (!uc->opts.strict) uc->version = 7400;
ufbxi_check_msg(uc->version > 0, "Not an FBX file");
}
}
return 1;
}
ufbxi_nodiscard static int ufbxi_parse_toplevel_child_imp(ufbxi_context *uc, ufbxi_parse_state state, ufbxi_buf *buf, bool *p_end)
{
if (uc->from_ascii) {
ufbxi_check(ufbxi_ascii_parse_node(uc, 0, state, p_end, buf, true));
} else {
ufbxi_check(ufbxi_binary_parse_node(uc, 0, state, p_end, buf, true));
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_toplevel(ufbxi_context *uc, const char *name)
{
ufbxi_for(ufbxi_node, node, uc->top_nodes, uc->top_nodes_len) {
if (node->name == name) {
uc->top_node = node;
uc->top_child_index = 0;
return 1;
}
}
// Reached end and not found in cache
if (uc->parsed_to_end) {
uc->top_node = NULL;
uc->top_child_index = 0;
return 1;
}
for (;;) {
// Parse the next top-level node
bool end = false;
if (uc->from_ascii) {
ufbxi_check(ufbxi_ascii_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, false));
} else {
ufbxi_check(ufbxi_binary_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, false));
}
// Top-level node not found
if (end) {
uc->top_node = NULL;
uc->top_child_index = 0;
uc->parsed_to_end = true;
if (uc->opts.retain_dom) {
ufbxi_check(ufbxi_retain_toplevel(uc, NULL));
}
// Not needed anymore
ufbxi_buf_free(&uc->tmp_parse);
return 1;
}
uc->top_nodes_len++;
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->top_nodes, &uc->top_nodes_cap, uc->top_nodes_len));
ufbxi_node *node = &uc->top_nodes[uc->top_nodes_len - 1];
ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, node);
if (uc->opts.retain_dom) {
ufbxi_check(ufbxi_retain_toplevel(uc, node));
}
// Return if we parsed the right one
if (node->name == name) {
uc->top_node = node;
uc->top_child_index = SIZE_MAX;
return 1;
}
// If not we need to parse all the children of the node for later
uint32_t num_children = 0;
ufbxi_parse_state state = ufbxi_update_parse_state(UFBXI_PARSE_ROOT, node->name);
if (uc->has_next_child) {
for (;;) {
ufbxi_check(ufbxi_parse_toplevel_child_imp(uc, state, &uc->tmp, &end));
if (end) break;
num_children++;
}
}
node->num_children = num_children;
node->children = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_node, num_children);
ufbxi_check(node->children);
if (uc->opts.retain_dom) {
for (size_t i = 0; i < num_children; i++) {
ufbxi_check(ufbxi_retain_toplevel_child(uc, &node->children[i]));
}
}
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_toplevel_child(ufbxi_context *uc, ufbxi_node **p_node, ufbxi_buf *tmp_buf)
{
// Top-level node not found
if (!uc->top_node) {
*p_node = NULL;
return 1;
}
if (uc->top_child_index == SIZE_MAX) {
// Parse children on demand
if (!tmp_buf) {
ufbxi_buf_clear(&uc->tmp_parse);
}
bool end = false;
ufbxi_parse_state state = ufbxi_update_parse_state(UFBXI_PARSE_ROOT, uc->top_node->name);
ufbxi_check(ufbxi_parse_toplevel_child_imp(uc, state, tmp_buf ? tmp_buf : &uc->tmp_parse, &end));
if (end) {
*p_node = NULL;
} else {
// Parse to either reused `uc->top_child` or push if retaining to `tmp_buf`.
ufbxi_node *dst = &uc->top_child;
if (tmp_buf) {
dst = ufbxi_push_zero(tmp_buf, ufbxi_node, 1);
ufbxi_check(dst);
}
ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, dst);
*p_node = dst;
if (uc->opts.retain_dom) {
ufbxi_check(ufbxi_retain_toplevel_child(uc, dst));
}
}
} else {
// Iterate already parsed nodes
size_t child_index = uc->top_child_index;
if (child_index == uc->top_node->num_children) {
*p_node = NULL;
} else {
uc->top_child_index++;
*p_node = &uc->top_node->children[child_index];
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_parse_legacy_toplevel(ufbxi_context *uc)
{
ufbx_assert(uc->top_nodes_len == 0);
bool end = false;
if (uc->from_ascii) {
ufbxi_check(ufbxi_ascii_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, true));
} else {
ufbxi_check(ufbxi_binary_parse_node(uc, 0, UFBXI_PARSE_ROOT, &end, &uc->tmp, true));
}
// Top-level node not found
if (end) {
uc->top_node = NULL;
uc->top_child_index = 0;
uc->parsed_to_end = true;
return 1;
}
ufbxi_pop(&uc->tmp_stack, ufbxi_node, 1, &uc->legacy_node);
uc->top_child_index = 0;
uc->top_node = &uc->legacy_node;
if (uc->opts.retain_dom) {
ufbxi_check(ufbxi_retain_toplevel(uc, &uc->legacy_node));
}
return 1;
}
// -- Setup
ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_strings(ufbxi_context *uc)
{
#if defined(UFBX_REGRESSION)
ufbx_string reg_prev = ufbx_empty_string;
#endif
// Push all the global 'ufbxi_*' strings into the pool without copying them
// This allows us to compare name pointers to the global values
ufbxi_for(const ufbx_string, str, ufbxi_strings, ufbxi_arraycount(ufbxi_strings)) {
#if defined(UFBX_REGRESSION)
ufbx_assert(strlen(str->data) == str->length);
ufbx_assert(ufbxi_str_less(reg_prev, *str));
reg_prev = *str;
#endif
ufbxi_check(ufbxi_push_string_imp(&uc->string_pool, str->data, str->length, NULL, false, true));
}
return 1;
}
typedef struct {
const char *name;
ufbx_prop_type type;
} ufbxi_prop_type_name;
static const ufbxi_prop_type_name ufbxi_prop_type_names[] = {
{ "Boolean", UFBX_PROP_BOOLEAN },
{ "bool", UFBX_PROP_BOOLEAN },
{ "Bool", UFBX_PROP_BOOLEAN },
{ "Integer", UFBX_PROP_INTEGER },
{ "int", UFBX_PROP_INTEGER },
{ "enum", UFBX_PROP_INTEGER },
{ "Visibility", UFBX_PROP_INTEGER },
{ "Visibility Inheritance", UFBX_PROP_INTEGER },
{ "KTime", UFBX_PROP_INTEGER },
{ "Number", UFBX_PROP_NUMBER },
{ "double", UFBX_PROP_NUMBER },
{ "Real", UFBX_PROP_NUMBER },
{ "Float", UFBX_PROP_NUMBER },
{ "Intensity", UFBX_PROP_NUMBER },
{ "Vector", UFBX_PROP_VECTOR },
{ "Vector3D", UFBX_PROP_VECTOR },
{ "Color", UFBX_PROP_COLOR },
{ "ColorAndAlpha", UFBX_PROP_COLOR_WITH_ALPHA },
{ "ColorRGB", UFBX_PROP_COLOR },
{ "String", UFBX_PROP_STRING },
{ "KString", UFBX_PROP_STRING },
{ "object", UFBX_PROP_STRING },
{ "DateTime", UFBX_PROP_DATE_TIME },
{ "Lcl Translation", UFBX_PROP_TRANSLATION },
{ "Lcl Rotation", UFBX_PROP_ROTATION },
{ "Lcl Scaling", UFBX_PROP_SCALING },
{ "Distance", UFBX_PROP_DISTANCE },
{ "Compound", UFBX_PROP_COMPOUND },
{ "Blob", UFBX_PROP_BLOB },
{ "Reference", UFBX_PROP_REFERENCE },
};
static ufbx_prop_type ufbxi_get_prop_type(ufbxi_context *uc, const char *name)
{
uint32_t hash = ufbxi_hash_ptr(name);
ufbxi_prop_type_name *entry = ufbxi_map_find(&uc->prop_type_map, ufbxi_prop_type_name, hash, &name);
if (entry) {
return entry->type;
}
return UFBX_PROP_UNKNOWN;
}
static ufbxi_noinline ufbx_prop *ufbxi_find_prop_with_key(const ufbx_props *props, const char *name, uint32_t key)
{
do {
ufbx_prop *prop_data = props->props.data;
size_t begin = 0;
size_t end = props->props.count;
while (end - begin >= 16) {
size_t mid = (begin + end) >> 1;
const ufbx_prop *p = &prop_data[mid];
if (p->_internal_key < key) {
begin = mid + 1;
} else {
end = mid;
}
}
end = props->props.count;
for (; begin < end; begin++) {
const ufbx_prop *p = &prop_data[begin];
if (p->_internal_key > key) break;
if (p->name.data == name && (p->flags & UFBX_PROP_FLAG_NO_VALUE) == 0) {
return (ufbx_prop*)p;
}
}
props = props->defaults;
} while (props);
return NULL;
}
typedef struct {
const char *key;
ufbx_texture_file *file;
} ufbxi_texture_file_entry;
#define ufbxi_find_prop(props, name) ufbxi_find_prop_with_key((props), (name), \
((uint32_t)(uint8_t)name[0] << 24u) | ((uint32_t)(uint8_t)name[1] << 16u) | \
((uint32_t)(uint8_t)name[2] << 8u) | (uint32_t)(uint8_t)name[3])
static ufbxi_forceinline ufbx_real ufbxi_find_real(const ufbx_props *props, const char *name, ufbx_real def)
{
ufbx_prop *prop = ufbxi_find_prop(props, name);
if (prop) {
return prop->value_real;
} else {
return def;
}
}
static ufbxi_forceinline ufbx_vec3 ufbxi_find_vec3(const ufbx_props *props, const char *name, ufbx_real def_x, ufbx_real def_y, ufbx_real def_z)
{
ufbx_prop *prop = ufbxi_find_prop(props, name);
if (prop) {
return prop->value_vec3;
} else {
ufbx_vec3 def = { def_x, def_y, def_z };
return def;
}
}
static ufbxi_forceinline int64_t ufbxi_find_int(const ufbx_props *props, const char *name, int64_t def)
{
ufbx_prop *prop = ufbxi_find_prop(props, name);
if (prop) {
return prop->value_int;
} else {
return def;
}
}
static ufbxi_forceinline int64_t ufbxi_find_enum(const ufbx_props *props, const char *name, int64_t def, int64_t max_value)
{
ufbx_prop *prop = ufbxi_find_prop(props, name);
if (prop) {
int64_t value = prop->value_int;
if (value >= 0 && value <= max_value) {
return value;
} else {
return def;
}
} else {
return def;
}
}
ufbxi_noinline static bool ufbxi_matrix_all_zero(const ufbx_matrix *matrix)
{
for (size_t i = 0; i < 12; i++) {
if (matrix->v[i] != 0.0f) return false;
}
return true;
}
static ufbxi_forceinline bool ufbxi_is_vec3_zero(ufbx_vec3 v)
{
return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0);
}
static ufbxi_forceinline bool ufbxi_is_vec4_zero(ufbx_vec4 v)
{
return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0);
}
static ufbxi_forceinline bool ufbxi_is_vec3_one(ufbx_vec3 v)
{
return (v.x == 1.0) & (v.y == 1.0) & (v.z == 1.0);
}
static ufbxi_forceinline bool ufbxi_is_quat_identity(ufbx_quat v)
{
return (v.x == 0.0) & (v.y == 0.0) & (v.z == 0.0) & (v.w == 1.0);
}
static ufbxi_noinline bool ufbxi_is_transform_identity(const ufbx_transform *t)
{
return (bool)((int)ufbxi_is_vec3_zero(t->translation) & (int)ufbxi_is_quat_identity(t->rotation) & (int)ufbxi_is_vec3_one(t->scale));
}
static ufbxi_forceinline uint32_t ufbxi_get_name_key(const char *name, size_t len)
{
uint32_t key = 0;
if (len >= 4) {
key = (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16
| (uint32_t)(uint8_t)name[2]<<8 | (uint32_t)(uint8_t)name[3];
} else {
for (size_t i = 0; i < 4; i++) {
key <<= 8;
if (i < len) key |= (uint8_t)name[i];
}
}
return key;
}
static ufbxi_forceinline uint32_t ufbxi_get_name_key_c(const char *name)
{
if (name[0] == '\0') return 0;
if (name[1] == '\0') return (uint32_t)(uint8_t)name[0]<<24;
if (name[2] == '\0') return (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16;
return (uint32_t)(uint8_t)name[0]<<24 | (uint32_t)(uint8_t)name[1]<<16
| (uint32_t)(uint8_t)name[2]<<8 | (uint32_t)(uint8_t)name[3];
}
static ufbxi_forceinline bool ufbxi_name_key_less(ufbx_prop *prop, const char *data, size_t name_len, uint32_t key)
{
if (prop->_internal_key < key) return true;
if (prop->_internal_key > key) return false;
size_t prop_len = prop->name.length;
size_t len = ufbxi_min_sz(prop_len, name_len);
int cmp = memcmp(prop->name.data, data, len);
if (cmp != 0) return cmp < 0;
return prop_len < name_len;
}
static const char *const ufbxi_node_prop_names[] = {
"AxisLen",
"DefaultAttributeIndex",
"Freeze",
"GeometricRotation",
"GeometricScaling",
"GeometricTranslation",
"InheritType",
"LODBox",
"Lcl Rotation",
"Lcl Scaling",
"Lcl Translation",
"LookAtProperty",
"MaxDampRangeX",
"MaxDampRangeY",
"MaxDampRangeZ",
"MaxDampStrengthX",
"MaxDampStrengthY",
"MaxDampStrengthZ",
"MinDampRangeX",
"MinDampRangeY",
"MinDampRangeZ",
"MinDampStrengthX",
"MinDampStrengthY",
"MinDampStrengthZ",
"NegativePercentShapeSupport",
"PostRotation",
"PreRotation",
"PreferedAngleX",
"PreferedAngleY",
"PreferedAngleZ",
"QuaternionInterpolate",
"RotationActive",
"RotationMax",
"RotationMaxX",
"RotationMaxY",
"RotationMaxZ",
"RotationMin",
"RotationMinX",
"RotationMinY",
"RotationMinZ",
"RotationOffset",
"RotationOrder",
"RotationPivot",
"RotationSpaceForLimitOnly",
"RotationStiffnessX",
"RotationStiffnessY",
"RotationStiffnessZ",
"ScalingActive",
"ScalingMax",
"ScalingMaxX",
"ScalingMaxY",
"ScalingMaxZ",
"ScalingMin",
"ScalingMinX",
"ScalingMinY",
"ScalingMinZ",
"ScalingOffset",
"ScalingPivot",
"Show",
"TranslationActive",
"TranslationMax",
"TranslationMaxX",
"TranslationMaxY",
"TranslationMaxZ",
"TranslationMin",
"TranslationMinX",
"TranslationMinY",
"TranslationMinZ",
"UpVectorProperty",
"Visibility Inheritance",
"Visibility",
"notes",
};
ufbxi_nodiscard static ufbxi_noinline int ufbxi_init_node_prop_names(ufbxi_context *uc)
{
ufbxi_check(ufbxi_map_grow(&uc->node_prop_set, const char*, ufbxi_arraycount(ufbxi_node_prop_names)));
for (size_t i = 0; i < ufbxi_arraycount(ufbxi_node_prop_names); i++) {
const char *name = ufbxi_node_prop_names[i];
const char *pooled = ufbxi_push_string_imp(&uc->string_pool, name, strlen(name), NULL, false, true);
ufbxi_check(pooled);
uint32_t hash = ufbxi_hash_ptr(pooled);
const char **entry = ufbxi_map_insert(&uc->node_prop_set, const char*, hash, &pooled);
ufbxi_check(entry);
*entry = pooled;
}
return 1;
}
static bool ufbxi_is_node_property(ufbxi_context *uc, const char *name)
{
// You need to call `ufbxi_init_node_prop_names()` before calling this
ufbx_assert(uc->node_prop_set.size > 0);
uint32_t hash = ufbxi_hash_ptr(name);
const char **entry = ufbxi_map_find(&uc->node_prop_set, const char*, hash, &name);
return entry != NULL;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_maps(ufbxi_context *uc)
{
ufbxi_check(ufbxi_map_grow(&uc->prop_type_map, ufbxi_prop_type_name, ufbxi_arraycount(ufbxi_prop_type_names)));
ufbxi_for(const ufbxi_prop_type_name, name, ufbxi_prop_type_names, ufbxi_arraycount(ufbxi_prop_type_names)) {
const char *pooled = ufbxi_push_string_imp(&uc->string_pool, name->name, strlen(name->name), NULL, false, true);
ufbxi_check(pooled);
uint32_t hash = ufbxi_hash_ptr(pooled);
ufbxi_prop_type_name *entry = ufbxi_map_insert(&uc->prop_type_map, ufbxi_prop_type_name, hash, &pooled);
ufbxi_check(entry);
entry->type = name->type;
entry->name = pooled;
}
return 1;
}
// -- Reading the parsed data
ufbxi_noinline static void ufbxi_decode_base64(char *dst, const char *src, size_t src_length)
{
uint8_t table[256] = { 0 };
for (char c = 'A'; c <= 'Z'; c++) table[(size_t)c] = (uint8_t)(c - 'A');
for (char c = 'a'; c <= 'z'; c++) table[(size_t)c] = (uint8_t)(26 + (c - 'a'));
for (char c = '0'; c <= '9'; c++) table[(size_t)c] = (uint8_t)(52 + (c - '0'));
table[(size_t)'+'] = 62;
table[(size_t)'/'] = 63;
for (size_t i = 0; i + 4 <= src_length; i += 4) {
uint32_t a = table[(size_t)(uint8_t)src[i + 0]];
uint32_t b = table[(size_t)(uint8_t)src[i + 1]];
uint32_t c = table[(size_t)(uint8_t)src[i + 2]];
uint32_t d = table[(size_t)(uint8_t)src[i + 3]];
dst[0] = (char)(uint8_t)(a << 2 | b >> 4);
dst[1] = (char)(uint8_t)(b << 4 | c >> 2);
dst[2] = (char)(uint8_t)(c << 6 | d);
dst += 3;
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_embedded_blob(ufbxi_context *uc, ufbx_blob *dst_blob, ufbxi_node *node)
{
if (!node) return 1;
ufbxi_value_array *content_arr = ufbxi_get_array(node, 'C');
if (content_arr && content_arr->size > 0) {
ufbx_string content;
size_t num_parts = content_arr->size;
ufbx_string *parts = (ufbx_string*)content_arr->data;
if (num_parts == 1) {
content = parts[0];
} else {
size_t total_size = 0;
ufbxi_for(ufbx_string, part, parts, num_parts) {
total_size += part->length;
}
ufbxi_buf *dst_buf = uc->from_ascii ? &uc->tmp_parse : &uc->result;
char *dst = ufbxi_push(dst_buf, char, total_size);
ufbxi_check(dst);
content.data = dst;
content.length = total_size;
ufbxi_for(ufbx_string, part, parts, num_parts) {
memcpy(dst, part->data, part->length);
dst += part->length;
}
}
if (uc->from_ascii) {
if (content.length % 4 == 0) {
size_t padding = 0;
while (padding < 2 && padding < content.length && content.data[content.length - 1 - padding] == '=') {
padding++;
}
dst_blob->size = content.length / 4 * 3 - padding;
dst_blob->data = ufbxi_push(&uc->result, char, dst_blob->size + 3);
ufbxi_check(dst_blob->data);
ufbxi_decode_base64((char*)dst_blob->data, content.data, content.length);
}
} else {
dst_blob->data = content.data;
dst_blob->size = content.length;
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_property(ufbxi_context *uc, ufbxi_node *node, ufbx_prop *prop, int version)
{
const char *type_str = NULL, *subtype_str = NULL;
ufbxi_check(ufbxi_get_val2(node, "SC", &prop->name, (char**)&type_str));
uint32_t val_ix = 2;
if (version == 70) {
ufbxi_check(ufbxi_get_val_at(node, val_ix++, 'C', (char**)&subtype_str));
}
uint32_t flags = 0;
prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length);
ufbx_string flags_str;
if (ufbxi_get_val_at(node, val_ix++, 'S', &flags_str)) {
for (size_t i = 0; i < flags_str.length; i++) {
char next = i + 1 < flags_str.length ? flags_str.data[i + 1] : '0';
switch (flags_str.data[i]) {
case 'A': flags |= UFBX_PROP_FLAG_ANIMATABLE; break;
case 'U': flags |= UFBX_PROP_FLAG_USER_DEFINED; break;
case 'H': flags |= UFBX_PROP_FLAG_HIDDEN; break;
case 'L': flags |= ((uint32_t)(next - '0') & 0xf) << 4; break; // UFBX_PROP_FLAG_LOCK_*
case 'M': flags |= ((uint32_t)(next - '0') & 0xf) << 8; break; // UFBX_PROP_FLAG_MUTE_*
default: break; // Ignore unknown flags
}
}
}
prop->type = ufbxi_get_prop_type(uc, type_str);
if (prop->type == UFBX_PROP_UNKNOWN && subtype_str) {
prop->type = ufbxi_get_prop_type(uc, subtype_str);
}
if (ufbxi_get_val_at(node, val_ix, 'L', &prop->value_int)) {
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_INT;
}
size_t real_ix;
for (real_ix = 0; real_ix < 4; real_ix++) {
if (!ufbxi_get_val_at(node, val_ix + real_ix, 'R', &prop->value_real_arr[real_ix])) break;
}
if (real_ix > 0) {
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (real_ix - 1);
}
// Distance properties have a string unit _after_ the real value, eg. `10, "cm"`
if (prop->type == UFBX_PROP_DISTANCE) {
val_ix++;
}
if (ufbxi_get_val_at(node, val_ix, 'S', &prop->value_str)) {
if (prop->value_str.length > 0) {
ufbxi_ignore(ufbxi_get_val_at(node, val_ix, 'b', &prop->value_blob));
}
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_STR;
} else {
prop->value_str = ufbx_empty_string;
}
// Very unlikely, seems to only exist in some "non standard" FBX files
if (node->num_children > 0) {
ufbxi_node *binary = ufbxi_find_child(node, ufbxi_BinaryData);
ufbxi_check(ufbxi_read_embedded_blob(uc, &prop->value_blob, binary));
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_BLOB;
}
prop->flags = (ufbx_prop_flags)flags;
return 1;
}
static ufbxi_forceinline bool ufbxi_prop_less(ufbx_prop *a, ufbx_prop *b)
{
if (a->_internal_key < b->_internal_key) return true;
if (a->_internal_key > b->_internal_key) return false;
return strcmp(a->name.data, b->name.data) < 0;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_properties(ufbxi_context *uc, ufbx_prop *props, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_prop)));
ufbxi_macro_stable_sort(ufbx_prop, 32, props, uc->tmp_arr, count, ( ufbxi_prop_less(a, b) ));
return 1;
}
ufbxi_noinline static void ufbxi_deduplicate_properties(ufbx_prop_list *list)
{
if (list->count >= 2) {
ufbx_prop *ps = list->data;
size_t dst = 0, src = 0, end = list->count;
while (src < end) {
if (src + 1 < end && ps[src].name.data == ps[src + 1].name.data) {
src++;
} else if (dst != src) {
ps[dst++] = ps[src++];
} else {
dst++; src++;
}
}
list->count = dst;
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_properties(ufbxi_context *uc, ufbxi_node *parent, ufbx_props *props)
{
props->defaults = NULL;
int version = 70;
ufbxi_node *node = ufbxi_find_child(parent, ufbxi_Properties70);
if (!node) {
node = ufbxi_find_child(parent, ufbxi_Properties60);
if (!node) {
// No properties found, not an error
props->props.data = NULL;
props->props.count = 0;
return 1;
}
version = 60;
}
props->props.data = ufbxi_push_zero(&uc->result, ufbx_prop, node->num_children);
props->props.count = node->num_children;
ufbxi_check(props->props.data);
for (size_t i = 0; i < props->props.count; i++) {
ufbxi_check(ufbxi_read_property(uc, &node->children[i], &props->props.data[i], version));
}
ufbxi_check(ufbxi_sort_properties(uc, props->props.data, props->props.count));
ufbxi_deduplicate_properties(&props->props);
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_thumbnail(ufbxi_context *uc, ufbxi_node *node, ufbx_thumbnail *thumbnail)
{
ufbxi_check(ufbxi_read_properties(uc, node, &thumbnail->props));
int64_t custom_width = ufbx_find_int(&thumbnail->props, "CustomWidth", 0);
int64_t custom_height = ufbx_find_int(&thumbnail->props, "CustomHeight", 0);
int32_t format;
ufbxi_node *format_node = ufbxi_find_child_strcmp(node, "Format");
if (format_node && ufbxi_get_val1(format_node, "I", &format)) {
if (format >= 0 && format + 1 < UFBX_THUMBNAIL_FORMAT_COUNT) {
thumbnail->format = (ufbx_thumbnail_format)(format + 1);
}
}
int32_t size;
if (ufbxi_find_val1(node, ufbxi_Size, "I", &size)) {
if (size > 0) {
thumbnail->width = (uint32_t)size;
thumbnail->height = (uint32_t)size;
} else if (size < 0 && custom_width > 0 && custom_height > 0) {
thumbnail->width = (uint32_t)custom_width;
thumbnail->height = (uint32_t)custom_height;
}
}
ufbxi_value_array *data_arr = ufbxi_find_array(node, ufbxi_ImageData, 'c');
if (data_arr) {
thumbnail->data.data = data_arr->data;
thumbnail->data.size = data_arr->size;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_scene_info(ufbxi_context *uc, ufbxi_node *node)
{
ufbxi_check(ufbxi_read_properties(uc, node, &uc->scene.metadata.scene_props));
ufbxi_node *thumbnail = ufbxi_find_child(node, ufbxi_Thumbnail);
if (thumbnail) {
ufbxi_check(ufbxi_read_thumbnail(uc, thumbnail, &uc->scene.metadata.thumbnail));
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_header_extension(ufbxi_context *uc)
{
// TODO: Read TCDefinition and adjust timestamps
uc->ktime_sec = 46186158000;
uc->ktime_sec_double = (double)uc->ktime_sec;
for (;;) {
ufbxi_node *child;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &child, NULL));
if (!child) break;
if (child->name == ufbxi_Creator) {
ufbxi_ignore(ufbxi_get_val1(child, "S", &uc->scene.metadata.creator));
}
if (uc->version < 6000 && child->name == ufbxi_FBXVersion) {
int32_t version;
if (ufbxi_get_val1(child, "I", &version)) {
if (version > 0 && version < 6000 && (uint32_t)version > uc->version) {
uc->version = (uint32_t)version;
}
}
}
if (child->name == ufbxi_SceneInfo) {
ufbxi_check(ufbxi_read_scene_info(uc, child));
}
}
return 1;
}
static bool ufbxi_match_version_string(const char *fmt, ufbx_string str, uint32_t *p_version)
{
size_t num_ix = 0;
size_t pos = 0;
while (*fmt) {
char c = *fmt++;
if (c >= 'a' && c <= 'z') {
if (pos >= str.length) return false;
char s = str.data[pos];
if (s != c && (int)s + (int)('a' - 'A') != (int)c) return false;
pos++;
} else if (c == ' ') {
while (pos < str.length) {
char s = str.data[pos];
if (s != ' ' && s != '\t') break;
pos++;
}
} else if (c == '-') {
while (pos < str.length) {
char s = str.data[pos];
if (s == '-') break;
pos++;
}
if (pos >= str.length) return false;
pos++;
} else if (c == '/' || c == '.' || c == '(' || c == ')') {
if (pos >= str.length) return false;
if (str.data[pos] != c) return false;
pos++;
} else if (c == '?') {
uint32_t num = 0;
size_t len = 0;
while (pos < str.length) {
char s = str.data[pos];
if (!(s >= '0' && s <= '9')) break;
num = num*10 + (uint32_t)(s - '0');
pos++;
len++;
}
if (len == 0) return false;
p_version[num_ix++] = num;
} else {
ufbxi_unreachable("Unhandled match character");
}
}
return true;
}
ufbxi_nodiscard static int ufbxi_match_exporter(ufbxi_context *uc)
{
ufbx_string creator = uc->scene.metadata.creator;
uint32_t version[3] = { 0 };
if (ufbxi_match_version_string("blender-- ?.?.?", creator, version)) {
uc->exporter = UFBX_EXPORTER_BLENDER_BINARY;
uc->exporter_version = ufbx_pack_version(version[0], version[1], version[2]);
} else if (ufbxi_match_version_string("blender- ?.?", creator, version)) {
uc->exporter = UFBX_EXPORTER_BLENDER_BINARY;
uc->exporter_version = ufbx_pack_version(version[0], version[1], 0);
} else if (ufbxi_match_version_string("blender version ?.?", creator, version)) {
uc->exporter = UFBX_EXPORTER_BLENDER_ASCII;
uc->exporter_version = ufbx_pack_version(version[0], version[1], 0);
} else if (ufbxi_match_version_string("fbx sdk/fbx plugins version ?.?", creator, version)) {
uc->exporter = UFBX_EXPORTER_FBX_SDK;
uc->exporter_version = ufbx_pack_version(version[0], version[1], 0);
} else if (ufbxi_match_version_string("fbx sdk/fbx plugins build ?", creator, version)) {
uc->exporter = UFBX_EXPORTER_FBX_SDK;
uc->exporter_version = ufbx_pack_version(version[0]/10000u, version[0]/100u%100u, version[0]%100u);
} else if (ufbxi_match_version_string("motionbuilder version ?.?", creator, version)) {
uc->exporter = UFBX_EXPORTER_MOTION_BUILDER;
uc->exporter_version = ufbx_pack_version(version[0], version[1], 0);
} else if (ufbxi_match_version_string("motionbuilder/mocap/online version ?.?", creator, version)) {
uc->exporter = UFBX_EXPORTER_MOTION_BUILDER;
uc->exporter_version = ufbx_pack_version(version[0], version[1], 0);
}
uc->scene.metadata.exporter = uc->exporter;
uc->scene.metadata.exporter_version = uc->exporter_version;
// Un-detect the exporter in `ufbxi_context` to disable special cases
if (uc->opts.disable_quirks) {
uc->exporter = UFBX_EXPORTER_UNKNOWN;
uc->exporter_version = 0;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_document(ufbxi_context *uc)
{
bool found_root_id = 0;
for (;;) {
ufbxi_node *child;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &child, NULL));
if (!child) break;
if (child->name == ufbxi_Document && !found_root_id) {
// Post-7000: Try to find the first document node and root ID.
// TODO: Multiple documents / roots?
if (ufbxi_find_val1(child, ufbxi_RootNode, "L", &uc->root_id)) {
found_root_id = true;
}
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_definitions(ufbxi_context *uc)
{
for (;;) {
ufbxi_node *object;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &object, NULL));
if (!object) break;
if (object->name != ufbxi_ObjectType) continue;
ufbxi_template *tmpl = ufbxi_push_zero(&uc->tmp_stack, ufbxi_template, 1);
uc->num_templates++;
ufbxi_check(tmpl);
ufbxi_check(ufbxi_get_val1(object, "C", (char**)&tmpl->type));
// Pre-7000 FBX versions don't have property templates, they just have
// the object counts by themselves.
ufbxi_node *props = ufbxi_find_child(object, ufbxi_PropertyTemplate);
if (props) {
ufbxi_check(ufbxi_get_val1(props, "S", &tmpl->sub_type));
// Remove the "Fbx" prefix from sub-types, remember to re-intern!
if (tmpl->sub_type.length > 3 && !strncmp(tmpl->sub_type.data, "Fbx", 3)) {
tmpl->sub_type.data += 3;
tmpl->sub_type.length -= 3;
// HACK: LOD groups use LODGroup for Template, LodGroup for Object?
if (tmpl->sub_type.length == 8 && !memcmp(tmpl->sub_type.data, "LODGroup", 8)) {
tmpl->sub_type.data = "LodGroup";
}
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &tmpl->sub_type, false));
}
ufbxi_check(ufbxi_read_properties(uc, props, &tmpl->props));
}
}
// TODO: Preserve only the `props` part of the templates
uc->templates = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbxi_template, uc->num_templates);
ufbxi_check(uc->templates);
return 1;
}
ufbxi_nodiscard static ufbx_props *ufbxi_find_template(ufbxi_context *uc, const char *name, const char *sub_type)
{
// TODO: Binary search
ufbxi_for(ufbxi_template, tmpl, uc->templates, uc->num_templates) {
if (tmpl->type == name) {
// Check that sub_type matches unless the type is Material, Model, AnimationStack, AnimationLayer.
// Those match to all sub-types.
if (tmpl->type != ufbxi_Material && tmpl->type != ufbxi_Model
&& tmpl->type != ufbxi_AnimationStack && tmpl->type != ufbxi_AnimationLayer) {
if (tmpl->sub_type.data != sub_type) {
return NULL;
}
}
if (tmpl->props.props.count > 0) {
return &tmpl->props;
} else {
return NULL;
}
}
}
return NULL;
}
// Name ID categories
#define UFBXI_SYNTHETIC_ID_BIT UINT64_C(0x8000000000000000)
ufbx_static_assert(uptr_size, sizeof(uintptr_t) <= sizeof(uint64_t));
static ufbxi_forceinline uint64_t ufbxi_synthetic_id_from_pointer(const void *ptr)
{
uintptr_t uptr = (uintptr_t)ptr;
ufbx_assert((uptr & 0x1) == 0);
return (uptr >> 1u) | UFBXI_SYNTHETIC_ID_BIT;
}
static ufbxi_forceinline uint64_t ufbxi_synthetic_id_from_string(const char *str)
{
uintptr_t uptr = (uintptr_t)str;
uptr &= ~(uintptr_t)1;
return (uptr >> 1u) | UFBXI_SYNTHETIC_ID_BIT;
}
static ufbxi_noinline int ufbxi_push_synthetic_id(ufbxi_context *uc, uint64_t *p_dst)
{
void *ptr = ufbxi_push_size(&uc->tmp, 8, 1);
ufbxi_check(ptr);
*p_dst = ufbxi_synthetic_id_from_pointer(ptr);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_split_type_and_name(ufbxi_context *uc, ufbx_string type_and_name, ufbx_string *type, ufbx_string *name)
{
// Name and type are packed in a single property as Type::Name (in ASCII)
// or Name\x00\x01Type (in binary)
const char *sep = uc->from_ascii ? "::" : "\x00\x01";
size_t type_end = 2;
for (; type_end <= type_and_name.length; type_end++) {
const char *ch = type_and_name.data + type_end - 2;
if (ch[0] == sep[0] && ch[1] == sep[1]) break;
}
// ???: ASCII and binary store type and name in different order
if (type_end <= type_and_name.length) {
if (uc->from_ascii) {
name->data = type_and_name.data + type_end;
name->length = type_and_name.length - type_end;
type->data = type_and_name.data;
type->length = type_end - 2;
} else {
name->data = type_and_name.data;
name->length = type_end - 2;
type->data = type_and_name.data + type_end;
type->length = type_and_name.length - type_end;
}
} else {
*name = type_and_name;
type->data = ufbxi_empty_char;
type->length = 0;
}
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, type, false));
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, name, false));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_fbx_id(ufbxi_context *uc, uint64_t fbx_id, uint32_t element_id)
{
uint32_t hash = ufbxi_hash64(fbx_id);
ufbxi_fbx_id_entry *entry = ufbxi_map_find(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id);
if (!entry) {
entry = ufbxi_map_insert(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id);
ufbxi_check(entry);
entry->fbx_id = fbx_id;
entry->element_id = element_id;
entry->user_id = 0;
} else {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_DUPLICATE_OBJECT_ID, "Duplicate object ID"));
}
return 1;
}
static ufbxi_noinline ufbxi_fbx_id_entry *ufbxi_find_fbx_id(ufbxi_context *uc, uint64_t fbx_id)
{
uint32_t hash = ufbxi_hash64(fbx_id);
return ufbxi_map_find(&uc->fbx_id_map, ufbxi_fbx_id_entry, hash, &fbx_id);
}
static ufbxi_forceinline bool ufbxi_fbx_id_exists(ufbxi_context *uc, uint64_t fbx_id)
{
return ufbxi_find_fbx_id(uc, fbx_id) != NULL;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_fbx_attr(ufbxi_context *uc, uint64_t fbx_id, uint64_t attrib_fbx_id)
{
uint32_t hash = ufbxi_hash64(fbx_id);
ufbxi_fbx_attr_entry *entry = ufbxi_map_find(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &fbx_id);
// TODO: Strict / warn about duplicate objects
if (!entry) {
entry = ufbxi_map_insert(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &fbx_id);
ufbxi_check(entry);
entry->node_fbx_id = fbx_id;
entry->attr_fbx_id = attrib_fbx_id;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_element_size(ufbxi_context *uc, ufbxi_element_info *info, size_t size, ufbx_element_type type)
{
size_t aligned_size = (size + 7u) & ~0x7u;
uint32_t typed_id = (uint32_t)uc->tmp_typed_element_offsets[type].num_items;
uint32_t element_id = uc->num_elements++;
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL);
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL);
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_fbx_ids, uint64_t, 1, &info->fbx_id), NULL);
uc->tmp_element_byte_offset += aligned_size;
ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8);
ufbxi_check_return(elem, NULL);
elem->type = type;
elem->element_id = element_id;
elem->typed_id = typed_id;
elem->name = info->name;
elem->props = info->props;
elem->dom_node = info->dom_node;
if (uc->p_element_id) {
*uc->p_element_id = element_id;
}
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL);
ufbxi_check_return(ufbxi_insert_fbx_id(uc, info->fbx_id, element_id), NULL);
return elem;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_synthetic_element_size(ufbxi_context *uc, uint64_t *p_fbx_id, ufbxi_node *node, const char *name, size_t size, ufbx_element_type type)
{
size_t aligned_size = (size + 7u) & ~0x7u;
uint32_t typed_id = (uint32_t)uc->tmp_typed_element_offsets[type].num_items;
uint32_t element_id = uc->num_elements++;
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL);
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL);
uc->tmp_element_byte_offset += aligned_size;
ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8);
ufbxi_check_return(elem, NULL);
elem->type = type;
elem->element_id = element_id;
elem->typed_id = typed_id;
elem->dom_node = ufbxi_get_dom_node(uc, node);
if (name) {
elem->name.data = name;
elem->name.length = strlen(name);
}
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL);
uint64_t fbx_id = ufbxi_synthetic_id_from_pointer(elem);
*p_fbx_id = fbx_id;
ufbxi_check_return(ufbxi_push_copy_fast(&uc->tmp_element_fbx_ids, uint64_t, 1, &fbx_id), NULL);
ufbxi_check_return(ufbxi_insert_fbx_id(uc, fbx_id, element_id), NULL);
return elem;
}
#define ufbxi_push_element(uc, info, type_name, type_enum) ufbxi_maybe_null((type_name*)ufbxi_push_element_size((uc), (info), sizeof(type_name), (type_enum)))
#define ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, type_name, type_enum) ufbxi_maybe_null((type_name*)ufbxi_push_synthetic_element_size((uc), (p_fbx_id), (node), (name), sizeof(type_name), (type_enum)))
ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_oo(ufbxi_context *uc, uint64_t src, uint64_t dst)
{
ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1);
ufbxi_check(conn);
conn->src = src;
conn->dst = dst;
conn->src_prop = conn->dst_prop = ufbx_empty_string;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_op(ufbxi_context *uc, uint64_t src, uint64_t dst, ufbx_string prop)
{
ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1);
ufbxi_check(conn);
conn->src = src;
conn->dst = dst;
conn->src_prop = ufbx_empty_string;
conn->dst_prop = prop;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_connect_pp(ufbxi_context *uc, uint64_t src, uint64_t dst, ufbx_string src_prop, ufbx_string dst_prop)
{
ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1);
ufbxi_check(conn);
conn->src = src;
conn->dst = dst;
conn->src_prop = src_prop;
conn->dst_prop = dst_prop;
return 1;
}
ufbxi_noinline static void ufbxi_init_synthetic_int_prop(ufbx_prop *dst, const char *name, int64_t value, ufbx_prop_type type)
{
dst->type = type;
dst->name.data = name;
dst->name.length = strlen(name);
dst->value_real = (ufbx_real)value;
dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_INT);
dst->value_int = value;
dst->value_str.data = ufbxi_empty_char;
ufbxi_dev_assert(dst->name.length >= 4);
dst->_internal_key = ufbxi_get_name_key(name, 4);
}
ufbxi_noinline static void ufbxi_init_synthetic_real_prop(ufbx_prop *dst, const char *name, ufbx_real value, ufbx_prop_type type)
{
dst->type = type;
dst->name.data = name;
dst->name.length = strlen(name);
dst->value_real = value;
dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_REAL);
dst->value_int = (int64_t)value;
dst->value_str.data = ufbxi_empty_char;
ufbxi_dev_assert(dst->name.length >= 4);
dst->_internal_key = ufbxi_get_name_key(name, 4);
}
ufbxi_noinline static void ufbxi_init_synthetic_vec3_prop(ufbx_prop *dst, const char *name, const ufbx_vec3 *value, ufbx_prop_type type)
{
dst->type = type;
dst->name.data = name;
dst->name.length = strlen(name);
dst->value_vec3 = *value;
dst->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_SYNTHETIC|UFBX_PROP_FLAG_VALUE_VEC3);
dst->value_int = ufbxi_f64_to_i64(dst->value_real);
dst->value_str.data = ufbxi_empty_char;
ufbxi_dev_assert(dst->name.length >= 4);
dst->_internal_key = ufbxi_get_name_key(name, 4);
}
ufbxi_noinline static void ufbxi_set_own_prop_vec3_uniform(ufbx_props *props, const char *name, ufbx_real value)
{
ufbx_props local_props = *props;
local_props.defaults = NULL;
ufbx_prop *prop = ufbx_find_prop(&local_props, name);
if (prop) {
prop->value_vec4.x = value;
prop->value_vec4.y = value;
prop->value_vec4.z = value;
prop->value_vec4.w = 0.0f;
prop->value_int = (int64_t)value;
}
}
typedef struct {
uint32_t geometry_helper_id;
uint32_t scale_helper_id;
} ufbxi_node_extra;
ufbxi_nodiscard ufbxi_noinline static int ufbxi_setup_geometry_transform_helper(ufbxi_context *uc, ufbx_node *node, uint64_t node_fbx_id)
{
ufbx_vec3 geo_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 geo_rotation = ufbxi_find_vec3(&node->props, ufbxi_GeometricRotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 geo_scaling = ufbxi_find_vec3(&node->props, ufbxi_GeometricScaling, 1.0f, 1.0f, 1.0f);
if (!ufbxi_is_vec3_zero(geo_translation) || !ufbxi_is_vec3_zero(geo_rotation) || !ufbxi_is_vec3_one(geo_scaling)) {
uint64_t geo_fbx_id;
ufbx_node *geo_node = ufbxi_push_synthetic_element(uc, &geo_fbx_id, NULL, uc->opts.geometry_transform_helper_name.data, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(geo_node);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &geo_node->element.element_id));
geo_node->element.dom_node = node->element.dom_node;
ufbx_prop *props = ufbxi_push_zero(&uc->result, ufbx_prop, 3);
ufbxi_check(props);
ufbxi_init_synthetic_vec3_prop(&props[0], ufbxi_Lcl_Rotation, &geo_rotation, UFBX_PROP_ROTATION);
ufbxi_init_synthetic_vec3_prop(&props[1], ufbxi_Lcl_Scaling, &geo_scaling, UFBX_PROP_SCALING);
ufbxi_init_synthetic_vec3_prop(&props[2], ufbxi_Lcl_Translation, &geo_translation, UFBX_PROP_TRANSLATION);
geo_node->props.props.data = props;
geo_node->props.props.count = 3;
node->has_geometry_transform = true;
geo_node->is_geometry_transform_helper = true;
ufbxi_check(ufbxi_connect_oo(uc, geo_fbx_id, node_fbx_id));
uc->has_geometry_transform_nodes = true;
ufbxi_node_extra *extra = ufbxi_push_element_extra(uc, node->element_id, ufbxi_node_extra);
ufbxi_check(extra);
extra->geometry_helper_id = geo_node->element_id;
}
return 1;
}
typedef struct {
const char *name;
ufbx_vec3 default_value;
} ufbxi_scale_helper_prop;
static const ufbxi_scale_helper_prop ufbxi_scale_helper_props[] = {
{ ufbxi_GeometricRotation, { 0.0f, 0.0f, 0.0f } },
{ ufbxi_GeometricScaling, { 1.0f, 1.0f, 1.0f } },
{ ufbxi_GeometricTranslation, { 0.0f, 0.0f, 0.0f } },
{ ufbxi_Lcl_Scaling, { 1.0f, 1.0f, 1.0f } },
};
ufbxi_nodiscard ufbxi_noinline static int ufbxi_setup_scale_helper(ufbxi_context *uc, ufbx_node *node, uint64_t node_fbx_id)
{
uint64_t scale_fbx_id;
ufbx_node *scale_node = ufbxi_push_synthetic_element(uc, &scale_fbx_id, NULL, uc->opts.scale_helper_name.data, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(scale_node);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &scale_node->element.element_id));
scale_node->element.dom_node = node->element.dom_node;
node->scale_helper = scale_node;
scale_node->is_scale_helper = true;
ufbxi_check(ufbxi_connect_oo(uc, scale_fbx_id, node_fbx_id));
uc->has_scale_helper_nodes = true;
ufbxi_node_extra *extra = ufbxi_push_element_extra(uc, node->element.element_id, ufbxi_node_extra);
ufbxi_check(extra);
extra->scale_helper_id = scale_node->element_id;
size_t max_props = ufbxi_arraycount(ufbxi_scale_helper_props);
ufbx_prop *helper_props = ufbxi_push(&uc->result, ufbx_prop, max_props);
ufbxi_check(helper_props);
size_t num_props = 0;
ufbx_props props_copy = node->props;
props_copy.defaults = NULL;
for (size_t i = 0; i < max_props; i++) {
const ufbxi_scale_helper_prop *hp = &ufbxi_scale_helper_props[i];
ufbx_prop *src_prop = ufbxi_find_prop(&props_copy, hp->name);
if (!src_prop) continue;
helper_props[num_props++] = *src_prop;
src_prop->value_vec3 = hp->default_value;
src_prop->value_int = (int64_t)src_prop->value_vec3.x;
}
scale_node->props.props.data = helper_props;
scale_node->props.props.count = num_props;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_model(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
(void)node;
ufbx_node *elem_node = ufbxi_push_element(uc, info, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(elem_node);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &elem_node->element.element_id));
int64_t inherit_type = ufbxi_find_int(&elem_node->props, ufbxi_InheritType, -1);
switch (inherit_type) {
case 0: // RrSs
elem_node->original_inherit_mode = UFBX_INHERIT_MODE_COMPONENTWISE_SCALE;
break;
case 2: // Rrs
elem_node->original_inherit_mode = UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE;
break;
default: break;
}
if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_PRESERVE) {
elem_node->inherit_mode = elem_node->original_inherit_mode;
} else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_IGNORE) {
elem_node->original_inherit_mode = UFBX_INHERIT_MODE_NORMAL;
elem_node->inherit_mode = UFBX_INHERIT_MODE_NORMAL;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_element(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, size_t size, ufbx_element_type type)
{
(void)node;
ufbx_element *elem = ufbxi_push_element_size(uc, info, size, type);
ufbxi_check(elem);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_unknown(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *element, ufbx_string type, ufbx_string sub_type, const char *node_name)
{
(void)node;
ufbx_unknown *unknown = ufbxi_push_element(uc, element, ufbx_unknown, UFBX_ELEMENT_UNKNOWN);
ufbxi_check(unknown);
unknown->type = type;
unknown->sub_type = sub_type;
unknown->super_type.data = node_name;
unknown->super_type.length = strlen(node_name);
// `type`, `sub_type` and `node_name` are raw strings so they may need to be sanitized.
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->type, false));
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->sub_type, false));
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &unknown->super_type, false));
return 1;
}
typedef struct {
ufbx_vertex_vec3 elem;
uint32_t index;
} ufbxi_tangent_layer;
static ufbx_real ufbxi_zero_element[8] = { 0 };
// Sentinel pointers used for zero/sequential index buffers
static const uint32_t ufbxi_sentinel_index_zero[1] = { 100000000 };
static const uint32_t ufbxi_sentinel_index_consecutive[1] = { 123456789 };
ufbxi_noinline static int ufbxi_fix_index(ufbxi_context *uc, uint32_t *p_dst, uint32_t index, size_t one_past_max_val)
{
switch (uc->opts.index_error_handling) {
case UFBX_INDEX_ERROR_HANDLING_CLAMP:
ufbxi_check(one_past_max_val > 0);
ufbxi_check(one_past_max_val <= UINT32_MAX);
*p_dst = (uint32_t)one_past_max_val - 1;
ufbxi_check(ufbxi_warnf(UFBX_WARNING_INDEX_CLAMPED, "Clamped index"));
break;
case UFBX_INDEX_ERROR_HANDLING_NO_INDEX:
*p_dst = UFBX_NO_INDEX;
break;
case UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING:
ufbxi_fmt_err_info(&uc->error, "%u (max %u)", index, one_past_max_val ? (one_past_max_val - 1) : 0);
ufbxi_fail_msg("UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING", "Bad index");
case UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE:
*p_dst = index;
break;
default:
ufbxi_unreachable("Unhandled index_error_handling");
return 0;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_check_indices(ufbxi_context *uc, uint32_t **p_dst, uint32_t *indices, bool owns_indices, size_t num_indices, size_t num_indexers, size_t num_elems)
{
// If the indices are truncated extend them with `UFBX_NO_INDEX`, the following normalization pass
// will handle them the same way as other out-of-bounds indices.
if (num_indices < num_indexers) {
uint32_t *new_indices = ufbxi_push(&uc->result, uint32_t, num_indexers);
ufbxi_check(new_indices);
memcpy(new_indices, indices, sizeof(uint32_t) * num_indices);
for (size_t i = num_indices; i < num_indexers; i++) {
new_indices[i] = UFBX_NO_INDEX;
}
indices = new_indices;
num_indices = num_indexers;
owns_indices = true;
}
// Normalize out-of-bounds indices to `invalid_index`
for (size_t i = 0; i < num_indices; i++) {
uint32_t ix = indices[i];
if (ix >= num_elems) {
// If the indices refer to an external buffer we need to
// allocate a separate buffer for them
if (!owns_indices) {
indices = ufbxi_push_copy(&uc->result, uint32_t, num_indices, indices);
ufbxi_check(indices);
owns_indices = true;
}
ufbxi_check(ufbxi_fix_index(uc, &indices[i], ix, num_elems));
}
}
*p_dst = indices;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_vertex_element(ufbxi_context *uc, ufbx_mesh *mesh, ufbxi_node *node,
ufbx_vertex_attrib *attrib, const char *data_name, const char *index_name, const char *w_name, char data_type, size_t num_components)
{
ufbx_real **p_dst_data = (ufbx_real**)&attrib->values.data;
ufbxi_value_array *data = ufbxi_find_array(node, data_name, data_type);
ufbxi_value_array *indices = ufbxi_find_array(node, index_name, 'i');
if (!uc->opts.strict) {
if (!data) return 1;
}
ufbxi_check(data);
ufbxi_check(data->size % num_components == 0);
size_t num_elems = data->size / num_components;
// HACK: If there's no elements at all keep the attribute as NULL
// TODO: Strict mode for this?
if (num_elems == 0) {
return 1;
}
ufbxi_check(num_elems > 0 && num_elems < INT32_MAX);
attrib->exists = true;
attrib->indices.count = mesh->num_indices;
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(node, ufbxi_MappingInformationType, "C", (char**)&mapping));
attrib->values.count = num_elems ? num_elems : 1;
// Data array is always used as-is, if empty set the data to a global
// zero buffer so invalid zero index can point to some valid data.
// The zero data is offset by 4 elements to accommodate for invalid index (-1)
if (num_elems > 0) {
*p_dst_data = (ufbx_real*)data->data;
} else {
*p_dst_data = ufbxi_zero_element + 4;
}
// HACK: Some old exporters seem to use ByPolygon to mean ByPolygonVertex,
// it should be quite safe to remap this
if (mapping == ufbxi_ByPolygon) {
size_t num_indices = indices ? indices->size : num_elems;
if (num_indices == mesh->num_indices) {
mapping = ufbxi_ByPolygonVertex;
}
}
if (indices) {
size_t num_indices = indices->size;
uint32_t *index_data = (uint32_t*)indices->data;
if (mapping == ufbxi_ByPolygonVertex) {
// Indexed by polygon vertex: We can use the provided indices directly.
ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, index_data, true, num_indices, mesh->num_indices, num_elems));
} else if (mapping == ufbxi_ByVertex || mapping == ufbxi_ByVertice) {
// Indexed by vertex: Follow through the position index mapping to get the final indices.
uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices);
ufbxi_check(new_index_data);
uint32_t *vert_ix = mesh->vertex_indices.data;
for (size_t i = 0; i < mesh->num_indices; i++) {
uint32_t ix = vert_ix[i];
if (ix < num_indices) {
new_index_data[i] = index_data[ix];
} else {
ufbxi_check(ufbxi_fix_index(uc, &new_index_data[i], ix, num_elems));
}
}
ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, new_index_data, true, mesh->num_indices, mesh->num_indices, num_elems));
attrib->unique_per_vertex = true;
} else if (mapping == ufbxi_ByPolygon) {
// Indexed by polygon: Generate new indices based on polygons
uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices);
ufbxi_check(new_index_data);
size_t num_faces = mesh->num_faces;
for (size_t face_ix = 0; face_ix < num_faces; face_ix++) {
ufbx_face face = mesh->faces.data[face_ix];
uint32_t index = UFBX_NO_INDEX;
if (face_ix < num_indices) {
index = index_data[face_ix];
}
if (index >= num_elems) {
ufbxi_check(ufbxi_fix_index(uc, &index, index, num_elems));
}
for (size_t i = 0; i < face.num_indices; i++) {
new_index_data[face.index_begin + i] = index;
}
}
attrib->indices.data = new_index_data;
} else if (mapping == ufbxi_AllSame) {
// Indexed by all same: ??? This could be possibly used for making
// holes with invalid indices, but that seems really fringe.
// Just use the shared zero index buffer for this.
uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_indices);
attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_zero;
attrib->unique_per_vertex = true;
} else {
ufbxi_fail("Invalid mapping");
}
} else {
if (mapping == ufbxi_ByPolygonVertex) {
// Direct by polygon index: Use shared consecutive array if there's enough
// elements, otherwise use a unique truncated consecutive index array.
if (num_elems >= mesh->num_indices) {
uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_indices);
attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive;
} else {
uint32_t *index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices);
ufbxi_check(index_data);
for (size_t i = 0; i < mesh->num_indices; i++) {
index_data[i] = (uint32_t)i;
}
ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, index_data, true, mesh->num_indices, mesh->num_indices, num_elems));
}
} else if (mapping == ufbxi_ByVertex || mapping == ufbxi_ByVertice) {
// Direct by vertex: We can re-use the position indices..
ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, mesh->vertex_position.indices.data, false, mesh->num_indices, mesh->num_indices, num_elems));
attrib->unique_per_vertex = true;
} else if (mapping == ufbxi_ByPolygon) {
// Direct by polygon: Generate new indices based on polygons
uint32_t *new_index_data = ufbxi_push(&uc->result, uint32_t, mesh->num_indices);
ufbxi_check(new_index_data);
uint32_t num_faces = (uint32_t)mesh->num_faces;
for (uint32_t face_ix = 0; face_ix < num_faces; face_ix++) {
ufbx_face face = mesh->faces.data[face_ix];
for (size_t i = 0; i < face.num_indices; i++) {
new_index_data[face.index_begin + i] = face_ix;
}
}
ufbxi_check(ufbxi_check_indices(uc, &attrib->indices.data, new_index_data, true, mesh->num_indices, mesh->num_indices, num_elems));
} else if (mapping == ufbxi_AllSame) {
// Direct by all same: This cannot fail as the index list is just zero.
uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_indices);
attrib->indices.data = (uint32_t*)ufbxi_sentinel_index_zero;
attrib->unique_per_vertex = true;
} else {
ufbxi_fail("Invalid mapping");
}
}
if (uc->opts.retain_vertex_attrib_w && w_name) {
ufbxi_value_array *w_data = ufbxi_find_array(node, w_name, 'r');
if (w_data) {
if (w_data->size == num_elems) {
attrib->values_w.count = w_data->size;
attrib->values_w.data = (ufbx_real*)w_data->data;
} else {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_BAD_VERTEX_W_ATTRIBUTE, "Bad W array size %s=%zu, %s=%zu",
w_name, w_data->size, data_name, num_elems));
}
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_truncated_array(ufbxi_context *uc, void *p_data, size_t *p_count, ufbxi_node *node, const char *name, char fmt, size_t size)
{
ufbxi_value_array *arr = ufbxi_find_array(node, name, fmt);
if (!arr) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_GEOMETRY_DATA, "Missing geometry data: %s", name));
return 1;
}
*p_count = size;
void *data = arr->data;
if (arr->size < size) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_TRUNCATED_ARRAY, "Truncated array: %s", name));
size_t elem_size = ufbxi_array_type_size(fmt);
void *new_data = ufbxi_push_size(&uc->result, elem_size, size);
ufbxi_check(new_data);
memcpy(new_data, data, arr->size * elem_size);
// Extend the array with the last element if possible
if (arr->size > 0) {
char *first_elem = (char*)data + (arr->size - 1) * elem_size;
for (size_t i = arr->size; i < size; i++) {
memcpy((char*)new_data + i * elem_size, first_elem, elem_size);
}
} else {
memset(new_data, 0, size * elem_size);
}
data = new_data;
}
*(void**)p_data = data;
return 1;
}
ufbxi_noinline static bool ufbxi_uv_set_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_uv_set *a = (const ufbx_uv_set *)va, *b = (const ufbx_uv_set *)vb;
return a->index < b->index;
}
ufbxi_noinline static bool ufbxi_color_set_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_color_set *a = (const ufbx_color_set *)va, *b = (const ufbx_color_set *)vb;
return a->index < b->index;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_uv_sets(ufbxi_context *uc, ufbx_uv_set *sets, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_uv_set)));
ufbxi_stable_sort(sizeof(ufbx_uv_set), 32, sets, uc->tmp_arr, count, &ufbxi_uv_set_less, NULL);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_color_sets(ufbxi_context *uc, ufbx_color_set *sets, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_color_set)));
ufbxi_stable_sort(sizeof(ufbx_color_set), 32, sets, uc->tmp_arr, count, &ufbxi_color_set_less, NULL);
return 1;
}
typedef struct ufbxi_blend_offset {
uint32_t vertex;
ufbx_vec3 position_offset;
ufbx_vec3 normal_offset;
} ufbxi_blend_offset;
static ufbxi_noinline bool ufbxi_blend_offset_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbxi_blend_offset *a = (const ufbxi_blend_offset*)va, *b = (const ufbxi_blend_offset*)vb;
return a->vertex < b->vertex;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_blend_offsets(ufbxi_context *uc, ufbxi_blend_offset *offsets, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_blend_offset)));
ufbxi_stable_sort(sizeof(ufbxi_blend_offset), 16, offsets, uc->tmp_arr, count, &ufbxi_blend_offset_less, NULL);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_shape(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices);
ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_Indexes);
ufbxi_node *node_normals = ufbxi_find_child(node, ufbxi_Normals);
if (!node_vertices || !node_indices) return 1;
ufbx_blend_shape *shape = ufbxi_push_element(uc, info, ufbx_blend_shape, UFBX_ELEMENT_BLEND_SHAPE);
ufbxi_check(shape);
if (uc->opts.ignore_geometry) return 1;
ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r');
ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i');
ufbxi_check(vertices && indices);
ufbxi_check(vertices->size % 3 == 0);
ufbxi_check(indices->size == vertices->size / 3);
size_t num_offsets = indices->size;
uint32_t *vertex_indices = (uint32_t*)indices->data;
shape->num_offsets = num_offsets;
shape->position_offsets.data = (ufbx_vec3*)vertices->data;
shape->offset_vertices.data = vertex_indices;
shape->position_offsets.count = num_offsets;
shape->offset_vertices.count = num_offsets;
if (node_normals) {
ufbxi_value_array *normals = ufbxi_get_array(node_normals, 'r');
ufbxi_check(normals && normals->size == vertices->size);
shape->normal_offsets.data = (ufbx_vec3*)normals->data;
shape->normal_offsets.count = num_offsets;
}
// Sort the blend shape vertices only if absolutely necessary
bool sorted = true;
for (size_t i = 1; i < num_offsets; i++) {
if (vertex_indices[i - 1] > vertex_indices[i]) {
sorted = false;
break;
}
}
if (!sorted) {
ufbxi_blend_offset *offsets = ufbxi_push(&uc->tmp_stack, ufbxi_blend_offset, num_offsets);
ufbxi_check(offsets);
for (size_t i = 0; i < num_offsets; i++) {
offsets[i].vertex = shape->offset_vertices.data[i];
offsets[i].position_offset = shape->position_offsets.data[i];
if (node_normals) offsets[i].normal_offset = shape->normal_offsets.data[i];
}
ufbxi_check(ufbxi_sort_blend_offsets(uc, offsets, num_offsets));
for (size_t i = 0; i < num_offsets; i++) {
shape->offset_vertices.data[i] = offsets[i].vertex;
shape->position_offsets.data[i] = offsets[i].position_offset;
if (node_normals) shape->normal_offsets.data[i] = offsets[i].normal_offset;
}
ufbxi_pop(&uc->tmp_stack, ufbxi_blend_offset, num_offsets, NULL);
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_blend_shapes(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_blend_deformer *deformer = NULL;
uint64_t deformer_fbx_id = 0;
ufbxi_for (ufbxi_node, n, node->children, node->num_children) {
if (n->name != ufbxi_Shape) continue;
ufbx_string name;
ufbxi_check(ufbxi_get_val1(n, "S", &name));
if (deformer == NULL) {
deformer = ufbxi_push_synthetic_element(uc, &deformer_fbx_id, n, name.data, ufbx_blend_deformer, UFBX_ELEMENT_BLEND_DEFORMER);
ufbxi_check(deformer);
ufbxi_check(ufbxi_connect_oo(uc, deformer_fbx_id, info->fbx_id));
}
uint64_t channel_fbx_id = 0;
ufbx_blend_channel *channel = ufbxi_push_synthetic_element(uc, &channel_fbx_id, n, name.data, ufbx_blend_channel, UFBX_ELEMENT_BLEND_CHANNEL);
ufbxi_check(channel);
ufbx_real_list weight_list = { NULL, 0 };
ufbxi_check(ufbxi_push_copy(&uc->tmp_full_weights, ufbx_real_list, 1, &weight_list));
size_t num_shape_props = 1;
ufbx_prop *shape_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_shape_props);
ufbxi_check(shape_props);
shape_props[0].name.data = ufbxi_DeformPercent;
shape_props[0].name.length = sizeof(ufbxi_DeformPercent) - 1;
shape_props[0]._internal_key = ufbxi_get_name_key_c(ufbxi_DeformPercent);
shape_props[0].type = UFBX_PROP_NUMBER;
shape_props[0].value_real = (ufbx_real)0.0;
shape_props[0].value_str = ufbx_empty_string;
shape_props[0].value_blob = ufbx_empty_blob;
ufbx_prop *self_prop = ufbx_find_prop_len(&info->props, name.data, name.length);
if (self_prop && (self_prop->type == UFBX_PROP_NUMBER || self_prop->type == UFBX_PROP_INTEGER)) {
shape_props[0].value_real = self_prop->value_real;
ufbxi_check(ufbxi_connect_pp(uc, info->fbx_id, channel_fbx_id, name, shape_props[0].name));
} else if (uc->version < 6000) {
ufbxi_check(ufbxi_connect_pp(uc, info->fbx_id, channel_fbx_id, name, shape_props[0].name));
}
channel->name = name;
channel->props.props.data = shape_props;
channel->props.props.count = num_shape_props;
ufbxi_element_info shape_info = { 0 };
ufbxi_check(ufbxi_push_synthetic_id(uc, &shape_info.fbx_id));
shape_info.name = name;
shape_info.dom_node = ufbxi_get_dom_node(uc, n);
ufbxi_check(ufbxi_read_shape(uc, n, &shape_info));
ufbxi_check(ufbxi_connect_oo(uc, channel_fbx_id, deformer_fbx_id));
ufbxi_check(ufbxi_connect_oo(uc, shape_info.fbx_id, channel_fbx_id));
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_process_indices(ufbxi_context *uc, ufbx_mesh *mesh, uint32_t *index_data)
{
// Count the number of faces and allocate the index list
// Indices less than zero (~actual_index) ends a polygon
size_t num_total_faces = 0;
ufbxi_for (uint32_t, p_ix, index_data, mesh->num_indices) {
num_total_faces += ((int32_t)*p_ix < 0) ? 1u : 0u;
}
mesh->faces.data = ufbxi_push(&uc->result, ufbx_face, num_total_faces);
ufbxi_check(mesh->faces.data);
size_t num_triangles = 0;
size_t max_face_triangles = 0;
size_t num_bad_faces[3] = { 0 };
ufbx_face *dst_face = mesh->faces.data;
uint32_t *p_face_begin = index_data;
ufbxi_for (uint32_t, p_ix, index_data, mesh->num_indices) {
uint32_t ix = *p_ix;
// Un-negate final indices of polygons
if ((int32_t)ix < 0) {
ix = ~ix;
*p_ix = ix;
uint32_t num_indices = (uint32_t)((p_ix - p_face_begin) + 1);
dst_face->index_begin = (uint32_t)(p_face_begin - index_data);
dst_face->num_indices = num_indices;
if (num_indices >= 3) {
num_triangles += num_indices - 2;
max_face_triangles = ufbxi_max_sz(max_face_triangles, num_indices - 2);
} else {
num_bad_faces[num_indices]++;
}
dst_face++;
p_face_begin = p_ix + 1;
}
ufbxi_check((size_t)ix < mesh->num_vertices);
}
mesh->vertex_position.indices.data = index_data;
mesh->num_faces = ufbxi_to_size(dst_face - mesh->faces.data);
mesh->faces.count = mesh->num_faces;
mesh->num_triangles = num_triangles;
mesh->max_face_triangles = max_face_triangles;
mesh->num_empty_faces = num_bad_faces[0];
mesh->num_point_faces = num_bad_faces[1];
mesh->num_line_faces = num_bad_faces[2];
mesh->vertex_first_index.count = mesh->num_vertices;
mesh->vertex_first_index.data = ufbxi_push(&uc->result, uint32_t, mesh->num_vertices);
ufbxi_check(mesh->vertex_first_index.data);
ufbxi_for_list(uint32_t, p_vx_ix, mesh->vertex_first_index) {
*p_vx_ix = UFBX_NO_INDEX;
}
{
size_t num_indices = mesh->num_indices;
size_t num_vertices = mesh->num_vertices;
uint32_t *vertex_indices = mesh->vertex_indices.data;
uint32_t *vertex_first_index = mesh->vertex_first_index.data;
for (size_t ix = 0; ix < num_indices; ix++) {
uint32_t vx = vertex_indices[ix];
if (vx < num_vertices) {
if (vertex_first_index[vx] == UFBX_NO_INDEX) {
vertex_first_index[vx] = (uint32_t)ix;
}
} else {
ufbxi_check(ufbxi_fix_index(uc, &vertex_indices[ix], vx, mesh->num_vertices));
}
}
}
// HACK(consecutive-faces): Prepare for finalize to re-use a consecutive/zero
// index buffer for face materials..
uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_faces);
uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_faces);
return 1;
}
ufbxi_noinline static void ufbxi_patch_mesh_reals(ufbx_mesh *mesh)
{
mesh->vertex_position.value_reals = 3;
mesh->vertex_normal.value_reals = 3;
mesh->vertex_uv.value_reals = 2;
mesh->vertex_tangent.value_reals = 3;
mesh->vertex_bitangent.value_reals = 3;
mesh->vertex_color.value_reals = 4;
mesh->vertex_crease.value_reals = 1;
mesh->skinned_position.value_reals = 3;
mesh->skinned_normal.value_reals = 3;
ufbxi_nounroll ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) {
set->vertex_uv.value_reals = 2;
set->vertex_tangent.value_reals = 3;
set->vertex_bitangent.value_reals = 3;
}
ufbxi_nounroll ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) {
set->vertex_color.value_reals = 4;
}
}
typedef struct {
uint32_t id, index;
} ufbxi_id_group;
static bool ufbxi_less_int32(void *user, const void *va, const void *vb)
{
(void)user;
const int32_t a = *(const int32_t*)va, b = *(const int32_t*)vb;
return a < b;
}
ufbx_static_assert(mesh_mat_point_faces, offsetof(ufbx_mesh_part, num_point_faces) - offsetof(ufbx_mesh_part, num_empty_faces) == 1 * sizeof(size_t));
ufbx_static_assert(mesh_mat_line_faces, offsetof(ufbx_mesh_part, num_line_faces) - offsetof(ufbx_mesh_part, num_empty_faces) == 2 * sizeof(size_t));
static ufbxi_forceinline void ufbxi_mesh_part_add_face(ufbx_mesh_part *part, uint32_t num_indices)
{
part->num_faces++;
if (num_indices >= 3) {
part->num_triangles += num_indices - 2;
} else {
// `num_empty/point/line_faces` are consecutive, see static asserts above.
// cppcheck-suppress objectIndex
(&part->num_empty_faces)[num_indices]++;
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_assign_face_groups(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh, size_t *p_consecutive_indices, bool retain_parts)
{
size_t num_faces = mesh->num_faces;
ufbxi_check_err(error, num_faces > 0);
ufbxi_check_err(error, num_faces < UINT32_MAX);
ufbxi_check_err(error, mesh->face_group.count == num_faces);
uint32_t *ids = ufbxi_push(buf, uint32_t, num_faces);
ufbxi_check_err(error, ids);
uint32_t num_ids = 0;
ufbxi_id_group seen_ids[1 << UFBXI_FACE_GROUP_HASH_BITS];
memset(seen_ids, 0, sizeof(seen_ids));
uint32_t seed = 2654435769u;
uint32_t rehash_threshold = 256;
// Loosely deduplicate group IDs
ufbxi_for_list(uint32_t, p_id, mesh->face_group) {
uint32_t id = *p_id;
uint32_t id_hash = (id * seed) >> (32u - UFBXI_FACE_GROUP_HASH_BITS);
if (seen_ids[id_hash].id != id || seen_ids[id_hash].index == 0) {
seen_ids[id_hash].id = id;
if (++seen_ids[id_hash].index > rehash_threshold) {
seed *= seed;
rehash_threshold *= 2;
}
ids[num_ids++] = id;
}
}
// Sort and deduplicate remaining IDs
ufbxi_unstable_sort(ids, num_ids, sizeof(uint32_t), &ufbxi_less_int32, NULL);
size_t num_groups = 0;
for (size_t i = 0; i < num_ids; ) {
uint32_t id = ids[i];
ids[num_groups++] = id;
do { i++; } while (i < num_ids && ids[i] == id);
}
// Allocate group info structs
ufbx_face_group *groups = ufbxi_push_zero(buf, ufbx_face_group, num_groups);
ufbxi_check_err(error, groups);
for (size_t i = 0; i < num_groups; i++) {
groups[i].id = (int32_t)ids[i];
groups[i].name.data = ufbxi_empty_char;
}
mesh->face_groups.data = groups;
mesh->face_groups.count = num_groups;
ufbx_mesh_part *parts = NULL;
if (retain_parts) {
parts = ufbxi_push_zero(buf, ufbx_mesh_part, num_groups);
ufbxi_check_err(error, parts);
mesh->face_group_parts.data = parts;
mesh->face_group_parts.count = num_groups;
}
// Optimization: Use `consecutive_indices` for a single group
if (p_consecutive_indices && num_groups == 1) {
memset(mesh->face_group.data, 0, sizeof(uint32_t) * num_faces);
if (parts) {
parts[0].face_indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive;
parts[0].face_indices.count = num_faces;
parts[0].num_empty_faces = mesh->num_empty_faces;
parts[0].num_point_faces = mesh->num_point_faces;
parts[0].num_line_faces = mesh->num_line_faces;
parts[0].num_faces = num_faces;
parts[0].num_triangles = mesh->num_triangles;
}
*p_consecutive_indices = ufbxi_max_sz(*p_consecutive_indices, num_faces);
return 1;
}
memset(seen_ids, 0, sizeof(seen_ids));
// Count faces and triangles per group and reassign IDs
const ufbx_face *p_face = mesh->faces.data;
ufbxi_for_list(uint32_t, p_id, mesh->face_group) {
uint32_t id = *p_id;
uint32_t id_hash = (id * seed) >> (32u - UFBXI_FACE_GROUP_HASH_BITS);
uint32_t num_indices = p_face->num_indices;
size_t index;
if (seen_ids[id_hash].id == id && seen_ids[id_hash].index > 0) {
index = seen_ids[id_hash].index - 1;
*p_id = (uint32_t)index;
} else {
int32_t signed_id = (int32_t)id;
index = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_face_group, 8, &index, groups, 0, num_groups, ( a->id < signed_id ), ( a->id == signed_id ));
ufbx_assert(index < num_groups);
seen_ids[id_hash].id = id;
seen_ids[id_hash].index = (uint32_t)index + 1;
}
if (parts) {
ufbxi_mesh_part_add_face(&parts[index], num_indices);
}
*p_id = (uint32_t)index;
p_face++;
}
if (!parts) return 1;
// Subdivide `ids` for per-group `face_indices`
uint32_t *face_indices = ids;
uint32_t part_index = 0;
ufbxi_for(ufbx_mesh_part, part, parts, num_groups) {
part->index = part_index++;
part->face_indices.data = face_indices;
face_indices += part->num_faces;
}
ufbx_assert(face_indices == ids + num_faces);
// Collect per-group faces
uint32_t face_index = 0;
ufbxi_for_list(uint32_t, p_id, mesh->face_group) {
ufbx_mesh_part *part = &parts[*p_id];
part->face_indices.data[part->face_indices.count++] = face_index++;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_update_face_groups(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh, bool need_copy)
{
size_t num_faces = mesh->faces.count;
size_t num_groups = mesh->face_group_parts.count;
if (num_groups == 0) return 1;
if (need_copy) {
mesh->face_group_parts.data = ufbxi_push_zero(buf, ufbx_mesh_part, num_groups);
ufbxi_check_err(error, mesh->face_group_parts.data);
}
uint32_t *face_indices = ufbxi_push(buf, uint32_t, num_faces);
ufbxi_check_err(error, face_indices);
ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) {
ufbx_mesh_part *part = &mesh->face_group_parts.data[mesh->face_group.data[i]];
ufbxi_mesh_part_add_face(part, mesh->faces.data[i].num_indices);
}
uint32_t part_index = 0;
ufbxi_for_list(ufbx_mesh_part, part, mesh->face_group_parts) {
part->index = part_index++;
part->face_indices.data = face_indices;
part->face_indices.count = 0;
face_indices += part->num_faces;
}
ufbxi_nounroll for (uint32_t i = 0; i < num_faces; i++) {
ufbx_mesh_part *part = &mesh->face_group_parts.data[mesh->face_group.data[i]];
part->face_indices.data[part->face_indices.count++] = i;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_mesh *ufbxi_restrict mesh = ufbxi_push_element(uc, info, ufbx_mesh, UFBX_ELEMENT_MESH);
ufbxi_check(mesh);
// In up to version 7100 FBX files blend shapes are contained within the same geometry node
if (uc->version <= 7100) {
ufbxi_check(ufbxi_read_synthetic_blend_shapes(uc, node, info));
}
ufbxi_patch_mesh_reals(mesh);
// Sometimes there are empty meshes in FBX files?
// TODO: Should these be included in output? option? strict mode?
ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices);
ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex);
if (!node_vertices) return 1;
if (uc->opts.ignore_geometry) return 1;
ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r');
ufbxi_value_array *indices = node_indices ? ufbxi_get_array(node_indices, 'i') : NULL;
ufbxi_value_array *edge_indices = ufbxi_find_array(node, ufbxi_Edges, 'i');
ufbxi_check(vertices);
ufbxi_check(!node_indices || indices); // If node_indices exists, it must be an array
ufbxi_check(vertices->size % 3 == 0);
mesh->num_vertices = vertices->size / 3;
mesh->num_indices = indices ? indices->size : 0;
uint32_t *index_data = indices ? (uint32_t*)indices->data : NULL;
// Duplicate `index_data` for modification if we retain DOM
if (uc->opts.retain_dom) {
index_data = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, index_data);
ufbxi_check(index_data);
}
mesh->vertices.data = (ufbx_vec3*)vertices->data;
mesh->vertices.count = mesh->num_vertices;
mesh->vertex_indices.data = index_data;
mesh->vertex_indices.count = mesh->num_indices;
mesh->vertex_position.exists = true;
mesh->vertex_position.values.data = (ufbx_vec3*)vertices->data;
mesh->vertex_position.values.count = mesh->num_vertices;
mesh->vertex_position.indices.data = index_data;
mesh->vertex_position.indices.count = mesh->num_indices;
mesh->vertex_position.unique_per_vertex = true;
// Check/make sure that the last index is negated (last of polygon)
if (mesh->num_indices > 0) {
if ((int32_t)index_data[mesh->num_indices - 1] >= 0) {
if (uc->opts.strict) ufbxi_fail("Non-negated last index");
index_data[mesh->num_indices - 1] = ~index_data[mesh->num_indices - 1];
}
}
// Read edges before un-negating the indices
if (edge_indices) {
size_t num_edges = edge_indices->size;
ufbx_edge *edges = ufbxi_push(&uc->result, ufbx_edge, num_edges);
ufbxi_check(edges);
size_t dst_ix = 0;
// Edges are represented using a single index into PolygonVertexIndex.
// The edge is between two consecutive vertices in the polygon.
uint32_t *edge_data = (uint32_t*)edge_indices->data;
for (size_t i = 0; i < num_edges; i++) {
uint32_t index_ix = edge_data[i];
if (index_ix >= mesh->num_indices) {
if (uc->opts.strict) ufbxi_fail("Edge index out of bounds");
continue;
}
edges[dst_ix].a = index_ix;
if ((int32_t)index_data[index_ix] < 0) {
// Previous index is the last one of this polygon, rewind to first index.
while (index_ix > 0 && (int32_t)index_data[index_ix - 1] >= 0) {
index_ix--;
}
} else {
// Connect to the next index in the same polygon
index_ix++;
}
ufbxi_check(index_ix < mesh->num_indices);
edges[dst_ix].b = index_ix;
dst_ix++;
}
mesh->edges.data = edges;
mesh->edges.count = dst_ix;
mesh->num_edges = mesh->edges.count;
}
ufbxi_check(ufbxi_process_indices(uc, mesh, index_data));
// Count the number of UV/color sets
size_t num_uv = 0, num_color = 0, num_bitangents = 0, num_tangents = 0;
ufbxi_for (ufbxi_node, n, node->children, node->num_children) {
if (n->name == ufbxi_LayerElementUV) num_uv++;
if (n->name == ufbxi_LayerElementColor) num_color++;
if (n->name == ufbxi_LayerElementBinormal) num_bitangents++;
if (n->name == ufbxi_LayerElementTangent) num_tangents++;
}
size_t num_textures = 0;
ufbxi_tangent_layer *bitangents = ufbxi_push_zero(&uc->tmp_stack, ufbxi_tangent_layer, num_bitangents);
ufbxi_tangent_layer *tangents = ufbxi_push_zero(&uc->tmp_stack, ufbxi_tangent_layer, num_tangents);
ufbxi_check(bitangents);
ufbxi_check(tangents);
mesh->uv_sets.data = ufbxi_push_zero(&uc->result, ufbx_uv_set, num_uv);
mesh->color_sets.data = ufbxi_push_zero(&uc->result, ufbx_color_set, num_color);
ufbxi_check(mesh->uv_sets.data);
ufbxi_check(mesh->color_sets.data);
size_t num_bitangents_read = 0, num_tangents_read = 0;
ufbxi_for (ufbxi_node, n, node->children, node->num_children) {
if (n->name[0] != 'L') continue; // All names start with 'LayerElement*'
if (n->name == ufbxi_LayerElementNormal) {
if (mesh->vertex_normal.exists) continue;
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&mesh->vertex_normal,
ufbxi_Normals, ufbxi_NormalsIndex, ufbxi_NormalsW, 'r', 3));
} else if (n->name == ufbxi_LayerElementBinormal) {
ufbxi_tangent_layer *layer = &bitangents[num_bitangents_read++];
ufbxi_ignore(ufbxi_get_val1(n, "I", &layer->index));
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&layer->elem,
ufbxi_Binormals, ufbxi_BinormalsIndex, ufbxi_BinormalsW, 'r', 3));
if (!layer->elem.exists) num_bitangents_read--;
} else if (n->name == ufbxi_LayerElementTangent) {
ufbxi_tangent_layer *layer = &tangents[num_tangents_read++];
ufbxi_ignore(ufbxi_get_val1(n, "I", &layer->index));
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&layer->elem,
ufbxi_Tangents, ufbxi_TangentsIndex, ufbxi_TangentsW, 'r', 3));
if (!layer->elem.exists) num_tangents_read--;
} else if (n->name == ufbxi_LayerElementUV) {
ufbx_uv_set *set = &mesh->uv_sets.data[mesh->uv_sets.count++];
ufbxi_ignore(ufbxi_get_val1(n, "I", &set->index));
if (!ufbxi_find_val1(n, ufbxi_Name, "S", &set->name)) {
set->name = ufbx_empty_string;
}
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_uv,
ufbxi_UV, ufbxi_UVIndex, NULL, 'r', 2));
if (!set->vertex_uv.exists) mesh->uv_sets.count--;
} else if (n->name == ufbxi_LayerElementColor) {
ufbx_color_set *set = &mesh->color_sets.data[mesh->color_sets.count++];
ufbxi_ignore(ufbxi_get_val1(n, "I", &set->index));
if (!ufbxi_find_val1(n, ufbxi_Name, "S", &set->name)) {
set->name = ufbx_empty_string;
}
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_color,
ufbxi_Colors, ufbxi_ColorIndex, NULL, 'r', 4));
if (!set->vertex_color.exists) mesh->color_sets.count--;
} else if (n->name == ufbxi_LayerElementVertexCrease) {
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&mesh->vertex_crease,
ufbxi_VertexCrease, ufbxi_VertexCreaseIndex, NULL, 'r', 1));
} else if (n->name == ufbxi_LayerElementEdgeCrease) {
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping));
if (mapping == ufbxi_ByEdge) {
if (mesh->edge_crease.count) continue;
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_crease.data, &mesh->edge_crease.count, n, ufbxi_EdgeCrease, 'r', mesh->num_edges));
}
} else if (n->name == ufbxi_LayerElementSmoothing) {
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping));
if (mapping == ufbxi_ByEdge) {
if (mesh->edge_smoothing.count) continue;
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_smoothing.data, &mesh->edge_smoothing.count, n, ufbxi_Smoothing, 'b', mesh->num_edges));
} else if (mapping == ufbxi_ByPolygon) {
if (mesh->face_smoothing.count) continue;
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_smoothing.data, &mesh->face_smoothing.count, n, ufbxi_Smoothing, 'b', mesh->num_faces));
}
} else if (n->name == ufbxi_LayerElementVisibility) {
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping));
if (mapping == ufbxi_ByEdge) {
if (mesh->edge_visibility.count) continue;
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_visibility.data, &mesh->edge_visibility.count, n, ufbxi_Visibility, 'b', mesh->num_edges));
}
} else if (n->name == ufbxi_LayerElementMaterial) {
if (mesh->face_material.count) continue;
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping));
if (mapping == ufbxi_ByPolygon) {
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_material.data, &mesh->face_material.count, n, ufbxi_Materials, 'i', mesh->num_faces));
} else if (mapping == ufbxi_AllSame) {
ufbxi_value_array *arr = ufbxi_find_array(n, ufbxi_Materials, 'i');
ufbxi_check(arr && arr->size >= 1);
uint32_t material = *(uint32_t*)arr->data;
mesh->face_material.count = mesh->num_faces;
if (material == 0) {
mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero;
} else {
mesh->face_material.data = ufbxi_push(&uc->result, uint32_t, mesh->num_faces);
ufbxi_check(mesh->face_material.data);
ufbxi_for_list(uint32_t, p_mat, mesh->face_material) {
*p_mat = material;
}
}
}
} else if (n->name == ufbxi_LayerElementPolygonGroup) {
if (mesh->face_group.count) continue;
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping));
if (mapping == ufbxi_ByPolygon) {
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_group.data, &mesh->face_group.count, n, ufbxi_PolygonGroup, 'i', mesh->num_faces));
}
} else if (n->name == ufbxi_LayerElementHole) {
if (mesh->face_group.count) continue;
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping));
if (mapping == ufbxi_ByPolygon) {
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_hole.data, &mesh->face_hole.count, n, ufbxi_Hole, 'b', mesh->num_faces));
}
} else if (!strncmp(n->name, "LayerElement", 12)) {
// Make sure the name has no internal zero bytes
ufbxi_check(!memchr(n->name, '\0', n->name_len));
// What?! 6x00 stores textures in mesh geometry, eg. "LayerElementTexture",
// "LayerElementDiffuseFactorTextures", "LayerElementEmissive_Textures"...
ufbx_string prop_name = ufbx_empty_string;
if (n->name_len > 20 && !strcmp(n->name + n->name_len - 8, "Textures")) {
prop_name.data = n->name + 12;
prop_name.length = (size_t)n->name_len - 20;
if (prop_name.data[prop_name.length - 1] == '_') {
prop_name.length -= 1;
}
} else if (!strcmp(n->name, "LayerElementTexture")) {
prop_name.data = "Diffuse";
prop_name.length = 7;
}
if (prop_name.length > 0) {
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop_name, false));
const char *mapping = NULL;
if (ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)) {
ufbxi_value_array *arr = ufbxi_find_array(n, ufbxi_TextureId, 'i');
ufbxi_tmp_mesh_texture *tex = ufbxi_push_zero(&uc->tmp_mesh_textures, ufbxi_tmp_mesh_texture, 1);
ufbxi_check(tex);
if (arr) {
tex->face_texture = (uint32_t*)arr->data;
tex->num_faces = arr->size;
}
tex->prop_name = prop_name;
tex->all_same = (mapping == ufbxi_AllSame);
num_textures++;
}
}
}
}
// Always use a default zero material, this will be removed if no materials are found
if (!mesh->face_material.count) {
uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, mesh->num_faces);
mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero;
mesh->face_material.count = mesh->num_faces;
}
if (uc->opts.strict) {
ufbxi_check(mesh->uv_sets.count == num_uv);
ufbxi_check(mesh->color_sets.count == num_color);
ufbxi_check(num_bitangents_read == num_bitangents);
ufbxi_check(num_tangents_read == num_tangents);
}
// Connect bitangents/tangents to UV sets
ufbxi_for (ufbxi_node, n, node->children, node->num_children) {
if (n->name != ufbxi_Layer) continue;
ufbx_uv_set *uv_set = NULL;
ufbxi_tangent_layer *bitangent_layer = NULL;
ufbxi_tangent_layer *tangent_layer = NULL;
ufbxi_for (ufbxi_node, c, n->children, n->num_children) {
uint32_t index;
const char *type;
if (c->name != ufbxi_LayerElement) continue;
if (!ufbxi_find_val1(c, ufbxi_TypedIndex, "I", &index)) continue;
if (!ufbxi_find_val1(c, ufbxi_Type, "C", (char**)&type)) continue;
if (type == ufbxi_LayerElementUV) {
ufbxi_for(ufbx_uv_set, set, mesh->uv_sets.data, mesh->uv_sets.count) {
if (set->index == index) {
uv_set = set;
break;
}
}
} else if (type == ufbxi_LayerElementBinormal) {
ufbxi_for(ufbxi_tangent_layer, layer, bitangents, num_bitangents_read) {
if (layer->index == index) {
bitangent_layer = layer;
break;
}
}
} else if (type == ufbxi_LayerElementTangent) {
ufbxi_for(ufbxi_tangent_layer, layer, tangents, num_tangents_read) {
if (layer->index == index) {
tangent_layer = layer;
break;
}
}
}
}
if (uv_set) {
if (bitangent_layer) {
uv_set->vertex_bitangent = bitangent_layer->elem;
}
if (tangent_layer) {
uv_set->vertex_tangent = tangent_layer->elem;
}
}
}
mesh->skinned_is_local = true;
mesh->skinned_position = mesh->vertex_position;
mesh->skinned_normal = mesh->vertex_normal;
ufbxi_patch_mesh_reals(mesh);
if (mesh->face_group.count > 0 && mesh->face_groups.count == 0) {
ufbxi_check(ufbxi_assign_face_groups(&uc->result, &uc->error, mesh, &uc->max_consecutive_indices, uc->retain_mesh_parts));
}
// Sort UV and color sets by set index
ufbxi_check(ufbxi_sort_uv_sets(uc, mesh->uv_sets.data, mesh->uv_sets.count));
ufbxi_check(ufbxi_sort_color_sets(uc, mesh->color_sets.data, mesh->color_sets.count));
if (num_textures > 0) {
ufbxi_mesh_extra *extra = ufbxi_push_element_extra(uc, mesh->element.element_id, ufbxi_mesh_extra);
ufbxi_check(extra);
extra->texture_count = num_textures;
extra->texture_arr = ufbxi_push_pop(&uc->tmp, &uc->tmp_mesh_textures, ufbxi_tmp_mesh_texture, num_textures);
ufbxi_check(extra->texture_arr);
}
// Subdivision
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_PreviewDivisionLevels, "I", &mesh->subdivision_preview_levels));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RenderDivisionLevels, "I", &mesh->subdivision_render_levels));
int32_t smoothness, boundary;
if (ufbxi_find_val1(node, ufbxi_Smoothness, "I", &smoothness)) {
if (smoothness >= 0 && smoothness <= UFBX_SUBDIVISION_DISPLAY_SMOOTH) {
mesh->subdivision_display_mode = (ufbx_subdivision_display_mode)smoothness;
}
}
if (ufbxi_find_val1(node, ufbxi_BoundaryRule, "I", &boundary)) {
if (boundary >= 0 && boundary <= UFBX_SUBDIVISION_BOUNDARY_SHARP_CORNERS - 1) {
mesh->subdivision_boundary = (ufbx_subdivision_boundary)(boundary + 1);
}
}
return 1;
}
ufbxi_noinline static ufbx_nurbs_topology ufbxi_read_nurbs_topology(const char *form)
{
if (!strcmp(form, "Open")) {
return UFBX_NURBS_TOPOLOGY_OPEN;
} else if (!strcmp(form, "Closed")) {
return UFBX_NURBS_TOPOLOGY_CLOSED;
} else if (!strcmp(form, "Periodic")) {
return UFBX_NURBS_TOPOLOGY_PERIODIC;
}
return UFBX_NURBS_TOPOLOGY_OPEN;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_nurbs_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_nurbs_curve *nurbs = ufbxi_push_element(uc, info, ufbx_nurbs_curve, UFBX_ELEMENT_NURBS_CURVE);
ufbxi_check(nurbs);
int32_t dimension = 3;
const char *form = NULL;
ufbxi_check(ufbxi_find_val1(node, ufbxi_Order, "I", &nurbs->basis.order));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Dimension, "I", &dimension));
ufbxi_check(ufbxi_find_val1(node, ufbxi_Form, "C", (char**)&form));
nurbs->basis.topology = ufbxi_read_nurbs_topology(form);
nurbs->basis.is_2d = dimension == 2;
if (!uc->opts.ignore_geometry) {
ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r');
ufbxi_value_array *knot = ufbxi_find_array(node, ufbxi_KnotVector, 'r');
ufbxi_check(points);
ufbxi_check(knot);
ufbxi_check(points->size % 4 == 0);
nurbs->control_points.count = points->size / 4;
nurbs->control_points.data = (ufbx_vec4*)points->data;
nurbs->basis.knot_vector.data = (ufbx_real*)knot->data;
nurbs->basis.knot_vector.count = knot->size;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_nurbs_surface(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_nurbs_surface *nurbs = ufbxi_push_element(uc, info, ufbx_nurbs_surface, UFBX_ELEMENT_NURBS_SURFACE);
ufbxi_check(nurbs);
const char *form_u = NULL, *form_v = NULL;
size_t dimension_u = 0, dimension_v = 0;
int32_t step_u = 0, step_v = 0;
ufbxi_check(ufbxi_find_val2(node, ufbxi_NurbsSurfaceOrder, "II", &nurbs->basis_u.order, &nurbs->basis_v.order));
ufbxi_check(ufbxi_find_val2(node, ufbxi_Dimensions, "ZZ", &dimension_u, &dimension_v));
ufbxi_check(ufbxi_find_val2(node, ufbxi_Step, "II", &step_u, &step_v));
ufbxi_check(ufbxi_find_val2(node, ufbxi_Form, "CC", (char**)&form_u, (char**)&form_v));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FlipNormals, "B", &nurbs->flip_normals));
nurbs->basis_u.topology = ufbxi_read_nurbs_topology(form_u);
nurbs->basis_v.topology = ufbxi_read_nurbs_topology(form_v);
nurbs->num_control_points_u = dimension_u;
nurbs->num_control_points_v = dimension_v;
nurbs->span_subdivision_u = step_u > 0 ? (uint32_t)step_u : 4u;
nurbs->span_subdivision_v = step_v > 0 ? (uint32_t)step_v : 4u;
if (!uc->opts.ignore_geometry) {
ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r');
ufbxi_value_array *knot_u = ufbxi_find_array(node, ufbxi_KnotVectorU, 'r');
ufbxi_value_array *knot_v = ufbxi_find_array(node, ufbxi_KnotVectorV, 'r');
ufbxi_check(points);
ufbxi_check(knot_u);
ufbxi_check(knot_v);
ufbxi_check(points->size % 4 == 0);
ufbxi_check(points->size / 4 == (size_t)dimension_u * (size_t)dimension_v);
nurbs->control_points.count = points->size / 4;
nurbs->control_points.data = (ufbx_vec4*)points->data;
nurbs->basis_u.knot_vector.data = (ufbx_real*)knot_u->data;
nurbs->basis_u.knot_vector.count = knot_u->size;
nurbs->basis_v.knot_vector.data = (ufbx_real*)knot_v->data;
nurbs->basis_v.knot_vector.count = knot_v->size;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_line(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_line_curve *line = ufbxi_push_element(uc, info, ufbx_line_curve, UFBX_ELEMENT_LINE_CURVE);
ufbxi_check(line);
if (!uc->opts.ignore_geometry) {
ufbxi_value_array *points = ufbxi_find_array(node, ufbxi_Points, 'r');
ufbxi_value_array *points_index = ufbxi_find_array(node, ufbxi_PointsIndex, 'i');
ufbxi_check(points);
ufbxi_check(points_index);
ufbxi_check(points->size % 3 == 0);
if (points->size > 0) {
line->control_points.count = points->size / 3;
line->control_points.data = (ufbx_vec3*)points->data;
line->point_indices.count = points_index->size;
line->point_indices.data = (uint32_t*)points_index->data;
ufbxi_check(line->control_points.count < INT32_MAX);
// Count end points
size_t num_segments = 1;
if (line->point_indices.count > 0) {
for (size_t i = 0; i < line->point_indices.count - 1; i++) {
uint32_t ix = line->point_indices.data[i];
num_segments += (int32_t)ix < 0 ? 1u : 0u;
}
}
size_t prev_end = 0;
line->segments.data = ufbxi_push(&uc->result, ufbx_line_segment, num_segments);
ufbxi_check(line->segments.data);
for (size_t i = 0; i < line->point_indices.count; i++) {
uint32_t ix = line->point_indices.data[i];
if ((int32_t)ix < 0) {
ix = ~ix;
if (i + 1 < line->point_indices.count) {
ufbx_line_segment *segment = &line->segments.data[line->segments.count++];
segment->index_begin = (uint32_t)prev_end;
segment->num_indices = (uint32_t)(i - prev_end);
prev_end = i;
}
}
if (ix < line->control_points.count) {
line->point_indices.data[i] = ix;
} else {
ufbxi_check(ufbxi_fix_index(uc, &line->point_indices.data[i], ix, line->control_points.count));
}
}
ufbx_line_segment *segment = &line->segments.data[line->segments.count++];
segment->index_begin = (uint32_t)prev_end;
segment->num_indices = (uint32_t)ufbxi_to_size(line->point_indices.count - prev_end);
ufbx_assert(line->segments.count == num_segments);
}
}
return 1;
}
ufbxi_noinline static void ufbxi_read_transform_matrix(ufbx_matrix *m, ufbx_real *data)
{
m->m00 = data[ 0]; m->m10 = data[ 1]; m->m20 = data[ 2];
m->m01 = data[ 4]; m->m11 = data[ 5]; m->m21 = data[ 6];
m->m02 = data[ 8]; m->m12 = data[ 9]; m->m22 = data[10];
m->m03 = data[12]; m->m13 = data[13]; m->m23 = data[14];
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_bone(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type)
{
(void)node;
ufbx_bone *bone = ufbxi_push_element(uc, info, ufbx_bone, UFBX_ELEMENT_BONE);
ufbxi_check(bone);
if (sub_type == ufbxi_Root) {
bone->is_root = true;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_marker(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type, ufbx_marker_type type)
{
(void)node;
(void)sub_type;
ufbx_marker *marker = ufbxi_push_element(uc, info, ufbx_marker, UFBX_ELEMENT_MARKER);
ufbxi_check(marker);
marker->type = type;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_skin(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_skin_deformer *skin = ufbxi_push_element(uc, info, ufbx_skin_deformer, UFBX_ELEMENT_SKIN_DEFORMER);
ufbxi_check(skin);
const char *skinning_type = NULL;
if (ufbxi_find_val1(node, ufbxi_SkinningType, "C", (char**)&skinning_type)) {
if (!strcmp(skinning_type, "Rigid")) {
skin->skinning_method = UFBX_SKINNING_METHOD_RIGID;
} else if (!strcmp(skinning_type, "Linear")) {
skin->skinning_method = UFBX_SKINNING_METHOD_LINEAR;
} else if (!strcmp(skinning_type, "DualQuaternion")) {
skin->skinning_method = UFBX_SKINNING_METHOD_DUAL_QUATERNION;
} else if (!strcmp(skinning_type, "Blend")) {
skin->skinning_method = UFBX_SKINNING_METHOD_BLENDED_DQ_LINEAR;
}
}
ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i');
ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_BlendWeights, 'r');
if (indices && weights) {
// TODO strict: ufbxi_check(indices->size == weights->size);
skin->num_dq_weights = ufbxi_min_sz(indices->size, weights->size);
skin->dq_vertices.data = (uint32_t*)indices->data;
skin->dq_weights.data = (ufbx_real*)weights->data;
skin->dq_vertices.count = skin->num_dq_weights;
skin->dq_weights.count = skin->num_dq_weights;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_skin_cluster(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_skin_cluster *cluster = ufbxi_push_element(uc, info, ufbx_skin_cluster, UFBX_ELEMENT_SKIN_CLUSTER);
ufbxi_check(cluster);
ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i');
ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_Weights, 'r');
if (indices && weights) {
ufbxi_check(indices->size == weights->size);
cluster->num_weights = indices->size;
cluster->vertices.data = (uint32_t*)indices->data;
cluster->weights.data = (ufbx_real*)weights->data;
cluster->vertices.count = cluster->num_weights;
cluster->weights.count = cluster->num_weights;
}
ufbxi_value_array *transform = ufbxi_find_array(node, ufbxi_Transform, 'r');
ufbxi_value_array *transform_link = ufbxi_find_array(node, ufbxi_TransformLink, 'r');
if (transform && transform_link) {
ufbxi_check(transform->size >= 16);
ufbxi_check(transform_link->size >= 16);
ufbxi_read_transform_matrix(&cluster->mesh_node_to_bone, (ufbx_real*)transform->data);
ufbxi_read_transform_matrix(&cluster->bind_to_world, (ufbx_real*)transform_link->data);
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_blend_channel(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_blend_channel *channel = ufbxi_push_element(uc, info, ufbx_blend_channel, UFBX_ELEMENT_BLEND_CHANNEL);
ufbxi_check(channel);
ufbx_real_list list = { NULL, 0 };
ufbxi_value_array *full_weights = ufbxi_find_array(node, ufbxi_FullWeights, 'r');
if (full_weights) {
list.data = (ufbx_real*)full_weights->data;
list.count = full_weights->size;
}
ufbxi_check(ufbxi_push_copy(&uc->tmp_full_weights, ufbx_real_list, 1, &list));
// Blender saves blend shapes with DeformPercent as a field, not a property.
// However, the animations are mapped to the DeformPercent property.
ufbxi_node *deform_percent = ufbxi_find_child(node, ufbxi_DeformPercent);
if (channel->props.props.count == 0 && deform_percent) {
size_t num_shape_props = 1;
ufbx_prop *shape_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_shape_props);
ufbxi_check(shape_props);
shape_props[0].name.data = ufbxi_DeformPercent;
shape_props[0].name.length = sizeof(ufbxi_DeformPercent) - 1;
shape_props[0]._internal_key = ufbxi_get_name_key_c(ufbxi_DeformPercent);
shape_props[0].type = UFBX_PROP_NUMBER;
shape_props[0].value_str = ufbx_empty_string;
shape_props[0].value_real = 100.0f;
ufbxi_ignore(ufbxi_get_val1(deform_percent, "R", &shape_props[0].value_real));
channel->props.props.data = shape_props;
channel->props.props.count = num_shape_props;
}
return 1;
}
typedef enum {
UFBXI_KEY_INTERPOLATION_CONSTANT = 0x2,
UFBXI_KEY_INTERPOLATION_LINEAR = 0x4,
UFBXI_KEY_INTERPOLATION_CUBIC = 0x8,
UFBXI_KEY_TANGENT_AUTO = 0x100,
UFBXI_KEY_TANGENT_TCB = 0x200,
UFBXI_KEY_TANGENT_USER = 0x400,
UFBXI_KEY_TANGENT_BROKEN = 0x800,
UFBXI_KEY_CONSTANT_NEXT = 0x100,
UFBXI_KEY_CLAMP = 0x1000,
UFBXI_KEY_TIME_INDEPENDENT = 0x2000,
UFBXI_KEY_CLAMP_PROGRESSIVE = 0x4000,
UFBXI_KEY_WEIGHTED_RIGHT = 0x1000000,
UFBXI_KEY_WEIGHTED_NEXT_LEFT = 0x2000000,
UFBXI_KEY_VELOCITY_RIGHT = 0x10000000,
UFBXI_KEY_VELOCITY_NEXT_LEFT = 0x20000000,
} ufbxi_key_flags;
static ufbxi_noinline float ufbxi_solve_auto_tangent(ufbxi_context *uc, double prev_time, double time, double next_time, ufbx_real prev_value, ufbx_real value, ufbx_real next_value, float weight_left, float weight_right, float auto_bias, uint32_t flags)
{
// Clamp tangent to zero if near either left or right key
if (flags & UFBXI_KEY_CLAMP) {
if (ufbx_fmin(ufbx_fabs(prev_value - value), ufbx_fabs(next_value - value)) <= uc->opts.key_clamp_threshold) {
return 0.0f;
}
}
// Time-independent: Set the initial slope to be the difference between the two keyframes.
double slope = (next_value - prev_value) / (next_time - prev_time);
// Non-time-independent tangents seem to blend between left/right tangent and the total difference.
if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) {
double slope_left = (value - prev_value) / (time - prev_time);
double slope_right = (next_value - value) / (next_time - time);
double delta = (time - prev_time) / (next_time - prev_time);
slope = slope * 0.5 + (slope_left * (1.0 - delta) + slope_right * delta) * 0.5;
double bias_weight = ufbx_fabs(auto_bias) / 100.0;
if (bias_weight > 0.0001) {
double bias_target = auto_bias > 0.0 ? slope_right : slope_left;
double bias_delta = bias_target - slope;
slope = slope * (1.0 - bias_weight) + bias_target * bias_weight;
// Auto bias larger than 500 (positive or negative) adds an absolute
// value to the slope, determined by `((bias-500) / 100)^2 * 40`.
double abs_bias_weight = bias_weight - 5.0;
if (abs_bias_weight > 0.0) {
double bias_sign = ufbx_fabs(bias_delta) > 0.00001 ? bias_delta : auto_bias;
bias_sign = bias_sign > 0.0 ? 1.0 : -1.0;
slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0;
}
}
}
// Prevent overshooting by clamping the slope in case either
// tangent goes above/below the endpoints.
if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) {
// Split the slope to sign and a non-negative absolute value
double slope_sign = slope >= 0.0 ? 1.0 : -1.0;
double abs_slope = slope_sign * slope;
// Find limits for the absolute value of the slope
double range_left = weight_left * (time - prev_time);
double range_right = weight_right * (next_time - time);
double max_left = range_left > 0.0 ? slope_sign * (value - prev_value) / range_left : 0.0;
double max_right = range_right > 0.0 ? slope_sign * (next_value - value) / range_right : 0.0;
// Clamp negative values and NaNs to zero
if (!(max_left > 0.0)) max_left = 0.0;
if (!(max_right > 0.0)) max_right = 0.0;
// Clamp the absolute slope from both sides
if (abs_slope > max_left) abs_slope = max_left;
if (abs_slope > max_right) abs_slope = max_right;
slope = (slope_sign * abs_slope);
}
return (float)slope;
}
static float ufbxi_solve_auto_tangent_left(ufbxi_context *uc, double prev_time, double time, ufbx_real prev_value, ufbx_real value, float weight_left, float auto_bias, uint32_t flags)
{
(void)weight_left;
if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) return 0.0f;
if (flags & UFBXI_KEY_CLAMP) {
if (ufbx_fabs(prev_value - value) <= uc->opts.key_clamp_threshold) {
return 0.0f;
}
}
double slope = (value - prev_value) / (time - prev_time);
if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) {
double abs_bias_weight = ufbx_fabs(auto_bias) / 100.0 - 5.0;
if (abs_bias_weight > 0.0) {
double bias_sign = auto_bias > 0.0 ? 1.0 : -1.0;
slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0;
}
}
return (float)slope;
}
static float ufbxi_solve_auto_tangent_right(ufbxi_context *uc, double time, double next_time, ufbx_real value, ufbx_real next_value, float weight_right, float auto_bias, uint32_t flags)
{
(void)weight_right;
if (flags & UFBXI_KEY_CLAMP_PROGRESSIVE) return 0.0f;
if (flags & UFBXI_KEY_CLAMP) {
if (ufbx_fabs(next_value - value) <= uc->opts.key_clamp_threshold) {
return 0.0f;
}
}
double slope = (next_value - value) / (next_time - time);
if ((flags & UFBXI_KEY_TIME_INDEPENDENT) == 0) {
double abs_bias_weight = ufbx_fabs(auto_bias) / 100.0 - 5.0;
if (abs_bias_weight > 0.0) {
double bias_sign = auto_bias > 0.0 ? 1.0 : -1.0;
slope += abs_bias_weight * abs_bias_weight * bias_sign * 40.0;
}
}
return (float)slope;
}
static void ufbxi_solve_tcb(float *p_slope_left, float *p_slope_right, double tension, double continuity, double bias, double slope_left, double slope_right, bool edge)
{
double factor = edge ? 1.0 : 0.5;
double d00 = factor * (1.0 - tension) * (1.0 + bias) * (1.0 - continuity);
double d01 = factor * (1.0 - tension) * (1.0 - bias) * (1.0 + continuity);
double d10 = factor * (1.0 - tension) * (1.0 + bias) * (1.0 + continuity);
double d11 = factor * (1.0 - tension) * (1.0 - bias) * (1.0 - continuity);
*p_slope_left = (float)(d00 * slope_left + d01 * slope_right);
*p_slope_right = (float)(d10 * slope_left + d11 * slope_right);
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_animation_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_anim_curve *curve = ufbxi_push_element(uc, info, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE);
ufbxi_check(curve);
if (uc->opts.ignore_animation) return 1;
ufbxi_value_array *times, *values, *attr_flags, *attrs, *refs;
ufbxi_check(times = ufbxi_find_array(node, ufbxi_KeyTime, 'l'));
ufbxi_check(values = ufbxi_find_array(node, ufbxi_KeyValueFloat, 'r'));
ufbxi_check(attr_flags = ufbxi_find_array(node, ufbxi_KeyAttrFlags, 'i'));
ufbxi_check(attrs = ufbxi_find_array(node, ufbxi_KeyAttrDataFloat, '?'));
ufbxi_check(refs = ufbxi_find_array(node, ufbxi_KeyAttrRefCount, 'i'));
// Time and value arrays that define the keyframes should be parallel
ufbxi_check(times->size == values->size);
// Flags and attributes are run-length encoded where KeyAttrRefCount (refs)
// is an array that describes how many times to repeat a given flag/attribute.
// Attributes consist of 4 32-bit floating point values per key.
ufbxi_check(attr_flags->size == refs->size);
ufbxi_check(attrs->size == refs->size * 4u);
size_t num_keys = times->size;
ufbx_keyframe *keys = ufbxi_push(&uc->result, ufbx_keyframe, num_keys);
ufbxi_check(keys);
curve->keyframes.data = keys;
curve->keyframes.count = num_keys;
int64_t *p_time = (int64_t*)times->data;
ufbx_real *p_value = (ufbx_real*)values->data;
int32_t *p_flag = (int32_t*)attr_flags->data;
float *p_attr = (float*)attrs->data;
int32_t *p_ref = (int32_t*)refs->data, *p_ref_end = p_ref + refs->size;
// The previous key defines the weight/slope of the left tangent
float slope_left = 0.0f;
float weight_left = 0.333333f;
// float velocity_left = 0.0f;
double prev_time = 0.0;
double next_time = 0.0;
int32_t refs_left = 0;
if (num_keys > 0) {
next_time = (double)p_time[0] / uc->ktime_sec_double;
if (p_ref < p_ref_end) refs_left = *p_ref;
}
for (size_t i = 0; i < num_keys; i++) {
ufbx_keyframe *key = &keys[i];
ufbxi_check(refs_left > 0);
ufbx_real value = *p_value;
if (i == 0) {
curve->min_value = value;
curve->max_value = value;
} else {
curve->min_value = ufbxi_min_real(curve->min_value, value);
curve->max_value = ufbxi_max_real(curve->max_value, value);
}
key->time = next_time;
key->value = value;
if (i + 1 < num_keys) {
next_time = (double)p_time[1] / uc->ktime_sec_double;
}
uint32_t flags = (uint32_t)*p_flag;
float slope_right = p_attr[0];
float weight_right = 0.333333f;
//float velocity_right = 0.0f;
float next_slope_left = p_attr[1];
float next_weight_left = 0.333333f;
// float next_velocity_left = 0.0f;
if ((flags & (UFBXI_KEY_WEIGHTED_RIGHT|UFBXI_KEY_WEIGHTED_NEXT_LEFT)) != 0) {
// At least one of the tangents is weighted. The weights are encoded as
// two 0.4 _decimal_ fixed point values that are packed into 32 bits and
// interpreted as a 32-bit float.
uint32_t packed_weights;
memcpy(&packed_weights, &p_attr[2], sizeof(uint32_t));
if (flags & UFBXI_KEY_WEIGHTED_RIGHT) {
// Right tangent is weighted
weight_right = (float)(packed_weights & 0xffff) * 0.0001f;
}
if (flags & UFBXI_KEY_WEIGHTED_NEXT_LEFT) {
// Next left tangent is weighted
next_weight_left = (float)(packed_weights >> 16) * 0.0001f;
}
}
#if 0
if ((flags & (UFBXI_KEY_VELOCITY_RIGHT|UFBXI_KEY_VELOCITY_NEXT_LEFT)) != 0) {
// Velocities are encoded in the same way as weights, see above.
uint32_t packed_velocities;
memcpy(&packed_velocities, &p_attr[3], sizeof(uint32_t));
if (flags & UFBXI_KEY_VELOCITY_RIGHT) {
// Right tangent has velocity
velocity_right = (float)(int16_t)(packed_velocities & 0xffff) * 0.0001f;
}
if (flags & UFBXI_KEY_VELOCITY_NEXT_LEFT) {
// Next left tangent has velocity
next_velocity_left = (float)(int16_t)(packed_velocities >> 16) * 0.0001f;
}
}
#endif
if (flags & UFBXI_KEY_INTERPOLATION_CONSTANT) {
// Constant interpolation: Set cubic tangents to flat.
if (flags & UFBXI_KEY_CONSTANT_NEXT) {
// Take constant value from next key
key->interpolation = UFBX_INTERPOLATION_CONSTANT_NEXT;
} else {
// Take constant value from the previous key
key->interpolation = UFBX_INTERPOLATION_CONSTANT_PREV;
}
weight_right = next_weight_left = 0.333333f;
slope_right = next_slope_left = 0.0f;
} else if (flags & UFBXI_KEY_INTERPOLATION_CUBIC) {
// Cubic interpolation
key->interpolation = UFBX_INTERPOLATION_CUBIC;
if (flags & UFBXI_KEY_TANGENT_TCB) {
double tcb_slope_left = 0.0;
double tcb_slope_right = 0.0;
bool tcb_edge = false;
if (i > 0 && key->time > prev_time) {
tcb_slope_left = (key->value - p_value[-1]) / (key->time - prev_time);
} else {
tcb_edge = true;
}
if (i + 1 < num_keys && next_time > key->time) {
tcb_slope_right = (p_value[1] - key->value) / (next_time - key->time);
} else {
tcb_edge = true;
}
ufbxi_solve_tcb(&slope_left, &slope_right, p_attr[0], p_attr[1], p_attr[2], tcb_slope_left, tcb_slope_right, tcb_edge);
// TODO: How to handle these?
next_slope_left = 0.0f;
next_weight_left = 0.333333f;
// next_velocity_left = 0.0f;
} else if (flags & UFBXI_KEY_TANGENT_USER) {
// User tangents
if (flags & UFBXI_KEY_TANGENT_BROKEN) {
// Broken tangents: No need to modify slopes
} else {
// Unified tangents: Use right slope for both sides
// TODO: ??? slope_left = slope_right;
}
} else {
// TODO: Auto break (0x800)
if (i > 0 && i + 1 < num_keys && key->time > prev_time && next_time > key->time) {
if (ufbx_fabs(slope_left + slope_right) <= 0.0001f) {
slope_left = slope_right = ufbxi_solve_auto_tangent(uc,
prev_time, key->time, next_time,
p_value[-1], key->value, p_value[1],
weight_left, weight_right, slope_right, flags);
} else {
slope_left = ufbxi_solve_auto_tangent(uc,
prev_time, key->time, next_time,
p_value[-1], key->value, p_value[1],
weight_left, weight_right, -slope_left, flags);
slope_right = ufbxi_solve_auto_tangent(uc,
prev_time, key->time, next_time,
p_value[-1], key->value, p_value[1],
weight_left, weight_right, slope_right, flags);
}
} else if (i > 0 && key->time > prev_time) {
slope_left = slope_right = ufbxi_solve_auto_tangent_left(uc,
prev_time, key->time,
p_value[-1], key->value,
weight_left, -slope_left, flags);
} else if (i + 1 < num_keys && next_time > key->time) {
slope_left = slope_right = ufbxi_solve_auto_tangent_right(uc,
key->time, next_time,
key->value, p_value[1],
weight_right, slope_right, flags);
} else {
// Only / invalid keyframe: Set both slopes to zero
slope_left = slope_right = 0.0f;
}
// ??? Looks like at least MotionBuilder adjusts weight and auto bias to
// implement velocity and the velocity information in the file is purely
// for UI (?) If auto bias is not accounted for the velocity computation
// below results in the correct tangents, but with auto bias the velocity
// seems to be accounted for twice resulting in incorrect values...
#if 0
if (weight_left >= UFBX_EPSILON) {
slope_left *= (float)(1.0 - ufbx_fmin(velocity_left / weight_left, 1.0));
}
if (weight_right >= UFBX_EPSILON) {
slope_right *= (float)(1.0 - ufbx_fmin(velocity_right / weight_right, 1.0));
}
#endif
}
} else {
// Linear or unknown interpolation: Set cubic tangents to match
// the linear interpolation with weights of 1/3.
key->interpolation = UFBX_INTERPOLATION_LINEAR;
weight_right = 0.333333f;
next_weight_left = 0.333333f;
if (next_time > key->time) {
double delta_time = next_time - key->time;
if (delta_time > 0.0) {
double slope = (p_value[1] - key->value) / delta_time;
slope_right = next_slope_left = (float)slope;
} else {
slope_right = next_slope_left = 0.0f;
}
} else {
slope_right = next_slope_left = 0.0f;
}
}
// Set the tangents based on weights (dx relative to the time difference
// between the previous/next key) and slope (simply d = slope * dx)
if (key->time > prev_time) {
double delta = key->time - prev_time;
key->left.dx = (float)(weight_left * delta);
key->left.dy = key->left.dx * slope_left;
} else {
key->left.dx = 0.0f;
key->left.dy = 0.0f;
}
if (next_time > key->time) {
double delta = next_time - key->time;
key->right.dx = (float)(weight_right * delta);
key->right.dy = key->right.dx * slope_right;
} else {
key->right.dx = 0.0f;
key->right.dy = 0.0f;
}
slope_left = next_slope_left;
weight_left = next_weight_left;
// velocity_left = next_velocity_left;
prev_time = key->time;
// Decrement attribute refcount and potentially move to the next one.
if (--refs_left == 0) {
p_flag++;
p_attr += 4;
p_ref++;
if (p_ref < p_ref_end) refs_left = *p_ref;
}
p_time++;
p_value++;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_material(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_material *material = ufbxi_push_element(uc, info, ufbx_material, UFBX_ELEMENT_MATERIAL);
ufbxi_check(material);
if (!ufbxi_find_val1(node, ufbxi_ShadingModel, "S", &material->shading_model_name)) {
material->shading_model_name = ufbx_empty_string;
}
material->shader_prop_prefix = ufbx_empty_string;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_texture(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_texture *texture = ufbxi_push_element(uc, info, ufbx_texture, UFBX_ELEMENT_TEXTURE);
ufbxi_check(texture);
texture->type = UFBX_TEXTURE_FILE;
texture->filename = ufbx_empty_string;
texture->absolute_filename = ufbx_empty_string;
texture->relative_filename = ufbx_empty_string;
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "S", &texture->absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "S", &texture->absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "S", &texture->relative_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "S", &texture->relative_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "b", &texture->raw_absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "b", &texture->raw_absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "b", &texture->raw_relative_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "b", &texture->raw_relative_filename));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_layered_texture(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_texture *texture = ufbxi_push_element(uc, info, ufbx_texture, UFBX_ELEMENT_TEXTURE);
ufbxi_check(texture);
texture->type = UFBX_TEXTURE_LAYERED;
texture->filename = ufbx_empty_string;
texture->absolute_filename = ufbx_empty_string;
texture->relative_filename = ufbx_empty_string;
ufbxi_texture_extra *extra = ufbxi_push_element_extra(uc, texture->element.element_id, ufbxi_texture_extra);
ufbxi_check(extra);
ufbxi_value_array *alphas = ufbxi_find_array(node, ufbxi_Alphas, 'r');
if (alphas) {
extra->alphas = (ufbx_real*)alphas->data;
extra->num_alphas = alphas->size;
}
ufbxi_value_array *blend_modes = ufbxi_find_array(node, ufbxi_BlendModes, 'i');
if (blend_modes) {
extra->blend_modes = (int32_t*)blend_modes->data;
extra->num_blend_modes = blend_modes->size;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_video(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_video *video = ufbxi_push_element(uc, info, ufbx_video, UFBX_ELEMENT_VIDEO);
ufbxi_check(video);
video->filename = ufbx_empty_string;
video->absolute_filename = ufbx_empty_string;
video->relative_filename = ufbx_empty_string;
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "S", &video->absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "S", &video->absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "S", &video->relative_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "S", &video->relative_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_FileName, "b", &video->raw_absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Filename, "b", &video->raw_absolute_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFileName, "b", &video->raw_relative_filename));
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_RelativeFilename, "b", &video->raw_relative_filename));
ufbxi_node *content_node = ufbxi_find_child(node, ufbxi_Content);
ufbxi_check(ufbxi_read_embedded_blob(uc, &video->content, content_node));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_anim_stack(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
(void)node;
ufbx_anim_stack *stack = ufbxi_push_element(uc, info, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK);
ufbxi_check(stack);
uint32_t hash = ufbxi_hash_ptr(info->name.data);
ufbxi_tmp_anim_stack *entry = ufbxi_map_find(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &info->name.data);
if (!entry) {
entry = ufbxi_map_insert(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &info->name.data);
ufbxi_check(entry);
entry->name = info->name.data;
entry->stack = stack;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_pose(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, const char *sub_type)
{
ufbx_pose *pose = ufbxi_push_element(uc, info, ufbx_pose, UFBX_ELEMENT_POSE);
ufbxi_check(pose);
// TODO: What are the actual other types?
pose->is_bind_pose = sub_type == ufbxi_BindPose;
size_t num_bones = 0;
ufbxi_for(ufbxi_node, n, node->children, node->num_children) {
if (n->name != ufbxi_PoseNode) continue;
// Bones are linked with FBX names/IDs bypassing the connection system (!?)
uint64_t fbx_id = 0;
if (uc->version < 7000) {
char *name = NULL;
if (!ufbxi_find_val1(n, ufbxi_Node, "c", &name)) continue;
fbx_id = ufbxi_synthetic_id_from_string(name);
} else {
if (!ufbxi_find_val1(n, ufbxi_Node, "L", &fbx_id)) continue;
}
ufbxi_value_array *matrix = ufbxi_find_array(n, ufbxi_Matrix, 'r');
if (!matrix) continue;
ufbxi_check(matrix->size >= 16);
ufbxi_tmp_bone_pose *tmp_pose = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_bone_pose, 1);
ufbxi_check(tmp_pose);
num_bones++;
tmp_pose->bone_fbx_id = fbx_id;
ufbxi_read_transform_matrix(&tmp_pose->bone_to_world, (ufbx_real*)matrix->data);
}
// HACK: Transport `ufbxi_tmp_bone_pose` array through the `ufbx_bone_pose` pointer
pose->bone_poses.count = num_bones;
pose->bone_poses.data = (ufbx_bone_pose*)ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_tmp_bone_pose, num_bones);
ufbxi_check(pose->bone_poses.data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_shader_prop_bindings(ufbxi_context *uc, ufbx_shader_prop_binding *bindings, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_shader_prop_binding)));
ufbxi_macro_stable_sort(ufbx_shader_prop_binding, 32, bindings, uc->tmp_arr, count,
( ufbxi_str_less(a->shader_prop, b->shader_prop) ) );
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_binding_table(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_shader_binding *bindings = ufbxi_push_element(uc, info, ufbx_shader_binding, UFBX_ELEMENT_SHADER_BINDING);
ufbxi_check(bindings);
size_t num_entries = 0;
ufbxi_for (ufbxi_node, n, node->children, node->num_children) {
if (n->name != ufbxi_Entry) continue;
ufbx_string src, dst;
const char *src_type = NULL, *dst_type = NULL;
if (!ufbxi_get_val4(n, "SCSC", &src, (char**)&src_type, &dst, (char**)&dst_type)) {
continue;
}
if (src_type == ufbxi_FbxPropertyEntry && dst_type == ufbxi_FbxSemanticEntry) {
ufbx_shader_prop_binding *bind = ufbxi_push(&uc->tmp_stack, ufbx_shader_prop_binding, 1);
ufbxi_check(bind);
bind->material_prop = src;
bind->shader_prop = dst;
num_entries++;
} else if (src_type == ufbxi_FbxSemanticEntry && dst_type == ufbxi_FbxPropertyEntry) {
ufbx_shader_prop_binding *bind = ufbxi_push(&uc->tmp_stack, ufbx_shader_prop_binding, 1);
ufbxi_check(bind);
bind->material_prop = dst;
bind->shader_prop = src;
num_entries++;
}
}
bindings->prop_bindings.count = num_entries;
bindings->prop_bindings.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_shader_prop_binding, num_entries);
ufbxi_check(bindings->prop_bindings.data);
ufbxi_check(ufbxi_sort_shader_prop_bindings(uc, bindings->prop_bindings.data, bindings->prop_bindings.count));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_selection_set(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
(void)node;
ufbx_selection_set *set = ufbxi_push_element(uc, info, ufbx_selection_set, UFBX_ELEMENT_SELECTION_SET);
ufbxi_check(set);
return 1;
}
ufbxi_noinline static void ufbxi_find_uint32_list(ufbx_uint32_list *dst, ufbxi_node *node, const char *name)
{
ufbxi_value_array *arr = ufbxi_find_array(node, name, 'i');
if (arr) {
dst->data = (uint32_t*)arr->data;
dst->count = arr->size;
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_selection_node(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_selection_node *sel = ufbxi_push_element(uc, info, ufbx_selection_node, UFBX_ELEMENT_SELECTION_NODE);
ufbxi_check(sel);
int32_t in_set = 0;
if (ufbxi_find_val1(node, ufbxi_IsTheNodeInSet, "I", &in_set) && in_set) {
sel->include_node = true;
}
ufbxi_find_uint32_list(&sel->vertices, node, ufbxi_VertexIndexArray);
ufbxi_find_uint32_list(&sel->edges, node, ufbxi_EdgeIndexArray);
ufbxi_find_uint32_list(&sel->faces, node, ufbxi_PolygonIndexArray);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_character(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
(void)node;
ufbx_character *character = ufbxi_push_element(uc, info, ufbx_character, UFBX_ELEMENT_CHARACTER);
ufbxi_check(character);
// TODO: There's some extremely cursed all-caps data in characters
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_audio_clip(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_audio_clip *audio = ufbxi_push_element(uc, info, ufbx_audio_clip, UFBX_ELEMENT_AUDIO_CLIP);
ufbxi_check(audio);
audio->filename = ufbx_empty_string;
audio->absolute_filename = ufbx_empty_string;
audio->relative_filename = ufbx_empty_string;
ufbxi_node *content_node = ufbxi_find_child(node, ufbxi_Content);
ufbxi_check(ufbxi_read_embedded_blob(uc, &audio->content, content_node));
return 1;
}
typedef struct {
ufbx_constraint_type type;
const char *name;
} ufbxi_constraint_type;
static const ufbxi_constraint_type ufbxi_constraint_types[] = {
{ UFBX_CONSTRAINT_AIM, "Aim" },
{ UFBX_CONSTRAINT_PARENT, "Parent-Child" },
{ UFBX_CONSTRAINT_POSITION, "Position From Positions" },
{ UFBX_CONSTRAINT_ROTATION, "Rotation From Rotations" },
{ UFBX_CONSTRAINT_SCALE, "Scale From Scales" },
{ UFBX_CONSTRAINT_SINGLE_CHAIN_IK, "Single Chain IK" },
};
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_constraint(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
(void)node;
ufbx_constraint *constraint = ufbxi_push_element(uc, info, ufbx_constraint, UFBX_ELEMENT_CONSTRAINT);
ufbxi_check(constraint);
if (!ufbxi_find_val1(node, ufbxi_Type, "S", &constraint->type_name)) {
constraint->type_name = ufbx_empty_string;
}
ufbxi_for(const ufbxi_constraint_type, ctype, ufbxi_constraint_types, ufbxi_arraycount(ufbxi_constraint_types)) {
if (!strcmp(constraint->type_name.data, ctype->name)) {
constraint->type = ctype->type;
break;
}
}
// TODO: There's some extremely cursed all-caps data in characters
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_attribute(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info, ufbx_string type_str, const char *sub_type, const char *super_type)
{
if ((sub_type == ufbxi_empty_char || sub_type == ufbxi_Model) && type_str.data == ufbxi_Model) {
// Plain model
return 1;
}
ufbxi_element_info attrib_info = *info;
ufbxi_check(ufbxi_push_synthetic_id(uc, &attrib_info.fbx_id));
// Use type and name from NodeAttributeName if it exists *uniquely*
ufbx_string type_and_name;
if (ufbxi_find_val1(node, ufbxi_NodeAttributeName, "s", &type_and_name)) {
ufbx_string attrib_type_str, attrib_name_str;
ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &attrib_type_str, &attrib_name_str));
if (attrib_name_str.length > 0) {
attrib_info.name = attrib_name_str;
uint64_t attrib_id = ufbxi_synthetic_id_from_string(type_and_name.data);
if (info->fbx_id != attrib_id && !ufbxi_fbx_id_exists(uc, attrib_id)) {
attrib_info.fbx_id = attrib_id;
}
}
}
// 6x00: Link the node to the node attribute so property connections can be
// redirected from connections if necessary.
if (uc->version < 7000) {
ufbxi_check(ufbxi_insert_fbx_attr(uc, info->fbx_id, attrib_info.fbx_id));
// Split properties between the node and the attribute
ufbx_prop *ps = info->props.props.data;
size_t dst = 0, src = 0, end = info->props.props.count;
while (src < end) {
if (!ufbxi_is_node_property(uc, ps[src].name.data)) {
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, 1, &ps[src]));
src++;
} else if (dst != src) {
ps[dst++] = ps[src++];
} else {
dst++; src++;
}
}
attrib_info.props.props.count = end - dst;
attrib_info.props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, attrib_info.props.props.count);
ufbxi_check(attrib_info.props.props.data);
info->props.props.count = dst;
}
if (sub_type == ufbxi_Mesh) {
ufbxi_check(ufbxi_read_mesh(uc, node, &attrib_info));
} else if (sub_type == ufbxi_Light) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_light), UFBX_ELEMENT_LIGHT));
} else if (sub_type == ufbxi_Camera) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_camera), UFBX_ELEMENT_CAMERA));
} else if (sub_type == ufbxi_LimbNode || sub_type == ufbxi_Limb || sub_type == ufbxi_Root) {
ufbxi_check(ufbxi_read_bone(uc, node, &attrib_info, sub_type));
} else if (sub_type == ufbxi_Null || sub_type == ufbxi_Marker) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_empty), UFBX_ELEMENT_EMPTY));
} else if (sub_type == ufbxi_NurbsCurve) {
if (!ufbxi_find_child(node, ufbxi_KnotVector)) return 1;
ufbxi_check(ufbxi_read_nurbs_curve(uc, node, &attrib_info));
} else if (sub_type == ufbxi_NurbsSurface) {
if (!ufbxi_find_child(node, ufbxi_KnotVectorU)) return 1;
if (!ufbxi_find_child(node, ufbxi_KnotVectorV)) return 1;
ufbxi_check(ufbxi_read_nurbs_surface(uc, node, &attrib_info));
} else if (sub_type == ufbxi_Line) {
if (!ufbxi_find_child(node, ufbxi_Points)) return 1;
if (!ufbxi_find_child(node, ufbxi_PointsIndex)) return 1;
ufbxi_check(ufbxi_read_line(uc, node, &attrib_info));
} else if (sub_type == ufbxi_TrimNurbsSurface) {
if (!ufbxi_find_child(node, ufbxi_Layer)) return 1;
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_nurbs_trim_surface), UFBX_ELEMENT_NURBS_TRIM_SURFACE));
} else if (sub_type == ufbxi_Boundary) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_nurbs_trim_boundary), UFBX_ELEMENT_NURBS_TRIM_BOUNDARY));
} else if (sub_type == ufbxi_CameraStereo) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_stereo_camera), UFBX_ELEMENT_STEREO_CAMERA));
} else if (sub_type == ufbxi_CameraSwitcher) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_camera_switcher), UFBX_ELEMENT_CAMERA_SWITCHER));
} else if (sub_type == ufbxi_FKEffector) {
ufbxi_check(ufbxi_read_marker(uc, node, &attrib_info, sub_type, UFBX_MARKER_FK_EFFECTOR));
} else if (sub_type == ufbxi_IKEffector) {
ufbxi_check(ufbxi_read_marker(uc, node, &attrib_info, sub_type, UFBX_MARKER_IK_EFFECTOR));
} else if (sub_type == ufbxi_LodGroup) {
ufbxi_check(ufbxi_read_element(uc, node, &attrib_info, sizeof(ufbx_lod_group), UFBX_ELEMENT_LOD_GROUP));
} else {
ufbx_string sub_type_str = { sub_type, strlen(sub_type) };
ufbxi_check(ufbxi_read_unknown(uc, node, &attrib_info, type_str, sub_type_str, super_type));
}
ufbxi_check(ufbxi_connect_oo(uc, attrib_info.fbx_id, info->fbx_id));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_global_settings(ufbxi_context *uc, ufbxi_node *node)
{
ufbxi_check(ufbxi_read_properties(uc, node, &uc->scene.settings.props));
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_object(ufbxi_context *uc, ufbxi_node *node)
{
ufbxi_element_info info = { 0 };
info.dom_node = ufbxi_get_dom_node(uc, node);
if (node->name == ufbxi_GlobalSettings) {
ufbxi_check(ufbxi_read_global_settings(uc, node));
return 1;
}
ufbx_string type_and_name, sub_type_str;
// Failing to parse the object properties is not an error since
// there's some weird objects mixed in every now and then.
// FBX version 7000 and up uses 64-bit unique IDs per object,
// older FBX versions just use name/type pairs, which we can
// use as IDs since all strings are interned into a string pool.
if (uc->version >= 7000) {
if (!ufbxi_get_val3(node, "Lss", &info.fbx_id, &type_and_name, &sub_type_str)) return 1;
ufbxi_check((info.fbx_id & UFBXI_SYNTHETIC_ID_BIT) == 0);
} else {
if (!ufbxi_get_val2(node, "ss", &type_and_name, &sub_type_str)) return 1;
info.fbx_id = ufbxi_synthetic_id_from_string(type_and_name.data);
}
// Remove the "Fbx" prefix from sub-types, remember to re-intern!
if (sub_type_str.length > 3 && !memcmp(sub_type_str.data, "Fbx", 3)) {
sub_type_str.data += 3;
sub_type_str.length -= 3;
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &sub_type_str, false));
}
ufbx_string type_str;
ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type_str, &info.name));
const char *name = node->name, *sub_type = sub_type_str.data;
ufbxi_check(ufbxi_read_properties(uc, node, &info.props));
info.props.defaults = ufbxi_find_template(uc, name, sub_type);
if (name == ufbxi_Model) {
if (uc->version < 7000) {
ufbxi_check(ufbxi_read_synthetic_attribute(uc, node, &info, type_str, sub_type, name));
}
ufbxi_check(ufbxi_read_model(uc, node, &info));
} else if (name == ufbxi_NodeAttribute) {
if (sub_type == ufbxi_Light) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_light), UFBX_ELEMENT_LIGHT));
} else if (sub_type == ufbxi_Camera) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_camera), UFBX_ELEMENT_CAMERA));
} else if (sub_type == ufbxi_LimbNode || sub_type == ufbxi_Limb || sub_type == ufbxi_Root) {
ufbxi_check(ufbxi_read_bone(uc, node, &info, sub_type));
} else if (sub_type == ufbxi_Null || sub_type == ufbxi_Marker) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_empty), UFBX_ELEMENT_EMPTY));
} else if (sub_type == ufbxi_CameraStereo) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_stereo_camera), UFBX_ELEMENT_STEREO_CAMERA));
} else if (sub_type == ufbxi_CameraSwitcher) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_camera_switcher), UFBX_ELEMENT_CAMERA_SWITCHER));
} else if (sub_type == ufbxi_FKEffector) {
ufbxi_check(ufbxi_read_marker(uc, node, &info, sub_type, UFBX_MARKER_FK_EFFECTOR));
} else if (sub_type == ufbxi_IKEffector) {
ufbxi_check(ufbxi_read_marker(uc, node, &info, sub_type, UFBX_MARKER_IK_EFFECTOR));
} else if (sub_type == ufbxi_LodGroup) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_lod_group), UFBX_ELEMENT_LOD_GROUP));
} else {
ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name));
}
} else if (name == ufbxi_Geometry) {
if (sub_type == ufbxi_Mesh) {
ufbxi_check(ufbxi_read_mesh(uc, node, &info));
} else if (sub_type == ufbxi_Shape) {
ufbxi_check(ufbxi_read_shape(uc, node, &info));
} else if (sub_type == ufbxi_NurbsCurve) {
ufbxi_check(ufbxi_read_nurbs_curve(uc, node, &info));
} else if (sub_type == ufbxi_NurbsSurface) {
ufbxi_check(ufbxi_read_nurbs_surface(uc, node, &info));
} else if (sub_type == ufbxi_Line) {
ufbxi_check(ufbxi_read_line(uc, node, &info));
} else if (sub_type == ufbxi_TrimNurbsSurface) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_nurbs_trim_surface), UFBX_ELEMENT_NURBS_TRIM_SURFACE));
} else if (sub_type == ufbxi_Boundary) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_nurbs_trim_boundary), UFBX_ELEMENT_NURBS_TRIM_BOUNDARY));
} else {
ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name));
}
} else if (name == ufbxi_Deformer) {
if (sub_type == ufbxi_Skin) {
ufbxi_check(ufbxi_read_skin(uc, node, &info));
} else if (sub_type == ufbxi_Cluster) {
ufbxi_check(ufbxi_read_skin_cluster(uc, node, &info));
} else if (sub_type == ufbxi_BlendShape) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_blend_deformer), UFBX_ELEMENT_BLEND_DEFORMER));
} else if (sub_type == ufbxi_BlendShapeChannel) {
ufbxi_check(ufbxi_read_blend_channel(uc, node, &info));
} else if (sub_type == ufbxi_VertexCacheDeformer) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_cache_deformer), UFBX_ELEMENT_CACHE_DEFORMER));
} else {
ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name));
}
} else if (name == ufbxi_Material) {
ufbxi_check(ufbxi_read_material(uc, node, &info));
} else if (name == ufbxi_Texture) {
ufbxi_check(ufbxi_read_texture(uc, node, &info));
} else if (name == ufbxi_LayeredTexture) {
ufbxi_check(ufbxi_read_layered_texture(uc, node, &info));
} else if (name == ufbxi_Video) {
ufbxi_check(ufbxi_read_video(uc, node, &info));
} else if (name == ufbxi_AnimationStack) {
ufbxi_check(ufbxi_read_anim_stack(uc, node, &info));
} else if (name == ufbxi_AnimationLayer) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_anim_layer), UFBX_ELEMENT_ANIM_LAYER));
} else if (name == ufbxi_AnimationCurveNode) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_anim_value), UFBX_ELEMENT_ANIM_VALUE));
} else if (name == ufbxi_AnimationCurve) {
ufbxi_check(ufbxi_read_animation_curve(uc, node, &info));
} else if (name == ufbxi_Pose) {
ufbxi_check(ufbxi_read_pose(uc, node, &info, sub_type));
} else if (name == ufbxi_Implementation) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_shader), UFBX_ELEMENT_SHADER));
} else if (name == ufbxi_BindingTable) {
ufbxi_check(ufbxi_read_binding_table(uc, node, &info));
} else if (name == ufbxi_Collection) {
if (sub_type == ufbxi_SelectionSet) {
ufbxi_check(ufbxi_read_selection_set(uc, node, &info));
}
} else if (name == ufbxi_CollectionExclusive) {
if (sub_type == ufbxi_DisplayLayer) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_display_layer), UFBX_ELEMENT_DISPLAY_LAYER));
}
} else if (name == ufbxi_SelectionNode) {
ufbxi_check(ufbxi_read_selection_node(uc, node, &info));
} else if (name == ufbxi_Constraint) {
if (sub_type == ufbxi_Character) {
ufbxi_check(ufbxi_read_character(uc, node, &info));
} else {
ufbxi_check(ufbxi_read_constraint(uc, node, &info));
}
} else if (name == ufbxi_SceneInfo) {
ufbxi_check(ufbxi_read_scene_info(uc, node));
} else if (name == ufbxi_Cache) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_cache_file), UFBX_ELEMENT_CACHE_FILE));
} else if (name == ufbxi_ObjectMetaData) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_metadata_object), UFBX_ELEMENT_METADATA_OBJECT));
} else if (name == ufbxi_AudioLayer) {
ufbxi_check(ufbxi_read_element(uc, node, &info, sizeof(ufbx_audio_layer), UFBX_ELEMENT_AUDIO_LAYER));
} else if (name == ufbxi_Audio) {
ufbxi_check(ufbxi_read_audio_clip(uc, node, &info));
} else {
ufbxi_check(ufbxi_read_unknown(uc, node, &info, type_str, sub_type_str, name));
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects(ufbxi_context *uc)
{
for (;;) {
// Push a deferred element ID for tagging warnings
uc->p_element_id = ufbxi_push(&uc->tmp_element_id, uint32_t, 1);
ufbxi_check(uc->p_element_id);
*uc->p_element_id = UFBX_NO_INDEX;
uc->warnings.deferred_element_id_plus_one = (uint32_t)uc->tmp_element_id.num_items;
ufbxi_node *node;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL));
if (!node) break;
ufbxi_check(ufbxi_read_object(uc, node));
uc->warnings.deferred_element_id_plus_one = 0;
uc->p_element_id = NULL;
}
return 1;
}
typedef struct {
ufbxi_node **nodes;
size_t num_nodes;
uint32_t task_index;
} ufbxi_object_batch;
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects_threaded(ufbxi_context *uc)
{
uc->parse_threaded = true;
bool parsed_to_end = false;
ufbxi_object_batch batches[UFBX_THREAD_GROUP_COUNT]; // ufbxi_uninit
memset(batches, 0, sizeof(batches));
size_t empty_count = 0;
size_t batch_index = 0;
while (empty_count < UFBX_THREAD_GROUP_COUNT) {
ufbxi_object_batch *batch = &batches[batch_index];
ufbxi_check(ufbxi_thread_pool_wait_group(&uc->thread_pool));
if (batch->num_nodes > 0) {
ufbxi_for_ptr(ufbxi_node, p_node, batch->nodes, batch->num_nodes) {
ufbxi_buf_clear(&uc->tmp_parse);
// Push a deferred element ID for tagging warnings
uc->p_element_id = ufbxi_push(&uc->tmp_element_id, uint32_t, 1);
ufbxi_check(uc->p_element_id);
*uc->p_element_id = UFBX_NO_INDEX;
uc->warnings.deferred_element_id_plus_one = (uint32_t)uc->tmp_element_id.num_items;
ufbxi_check(ufbxi_read_object(uc, *p_node));
uc->warnings.deferred_element_id_plus_one = 0;
uc->p_element_id = NULL;
}
batch->num_nodes = 0;
}
ufbxi_buf *tmp_buf = &uc->tmp_thread_parse[batch_index];
// ASCII data may be in `tmp_buf`, so copy it to safety in case
if (uc->ascii.src_buf == tmp_buf) {
ufbxi_ascii *ua = &uc->ascii;
size_t size = ufbxi_to_size(ua->src_end - ua->src);
if (uc->read_buffer_size < size) {
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->read_buffer, &uc->read_buffer_size, size));
}
memcpy(uc->read_buffer, ua->src, size);
uc->data = uc->data_begin = ua->src = uc->read_buffer;
ua->src_end = uc->read_buffer + size;
ua->src_is_retained = false;
ua->src_buf = NULL;
if (ufbxi_to_size(ua->src_end - ua->src) < uc->progress_interval) {
ua->src_yield = ua->src_end;
} else {
ua->src_yield = ua->src + uc->progress_interval;
}
uc->data = ua->src;
}
ufbxi_buf_clear(tmp_buf);
if (!parsed_to_end) {
size_t num_nodes = 0;
uint32_t task_start = uc->thread_pool.start_index;
uint32_t max_tasks = uc->thread_pool.num_tasks / UFBX_THREAD_GROUP_COUNT;
max_tasks = ufbxi_min32(max_tasks, ufbxi_thread_pool_available_tasks(&uc->thread_pool));
size_t max_memory = uc->opts.thread_opts.memory_limit / UFBX_THREAD_GROUP_COUNT;
for (;;) {
ufbxi_node *node = NULL;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, tmp_buf));
if (!node) {
parsed_to_end = true;
break;
}
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbxi_node*, 1, &node));
num_nodes++;
uint32_t num_tasks = uc->thread_pool.start_index - task_start;
if (num_tasks >= max_tasks) break;
size_t memory_used = tmp_buf->pushed_size + tmp_buf->pos;
if (memory_used >= max_memory) break;
}
batch->num_nodes = num_nodes;
batch->nodes = ufbxi_push_pop(tmp_buf, &uc->tmp_stack, ufbxi_node*, num_nodes);
ufbxi_check(batch->nodes);
batch->task_index = uc->thread_pool.start_index;
}
// Not safe to refer to this buffer anymore
uc->ascii.src_is_retained = false;
ufbxi_thread_pool_flush_group(&uc->thread_pool);
if (batch->num_nodes == 0) {
empty_count += 1;
}
batch_index = (batch_index + 1) % UFBX_THREAD_GROUP_COUNT;
}
ufbxi_check(ufbxi_thread_pool_wait_all(&uc->thread_pool));
uc->parse_threaded = false;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_connections(ufbxi_context *uc)
{
// Read the connections to the list first
for (;;) {
ufbxi_node *node;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL));
if (!node) break;
char *type;
uint64_t src_id = 0, dst_id = 0;
ufbx_string src_prop = ufbx_empty_string, dst_prop = ufbx_empty_string;
if (uc->version < 7000) {
char *src_name = NULL, *dst_name = NULL;
// Pre-7000 versions use Type::Name pairs as identifiers
if (!ufbxi_get_val1(node, "c", &type)) continue;
if (type == ufbxi_OO) {
if (!ufbxi_get_val3(node, "_cc", NULL, &src_name, &dst_name)) continue;
} else if (type == ufbxi_OP) {
if (!ufbxi_get_val4(node, "_ccs", NULL, &src_name, &dst_name, &dst_prop)) continue;
} else if (type == ufbxi_PO) {
if (!ufbxi_get_val4(node, "_csc", NULL, &src_name, &src_prop, &dst_name)) continue;
} else if (type == ufbxi_PP) {
if (!ufbxi_get_val5(node, "_cscs", NULL, &src_name, &src_prop, &dst_name, &dst_prop)) continue;
} else {
// TODO: Strict mode?
continue;
}
if (src_prop.length > 0) {
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &src_prop, false));
}
if (dst_prop.length > 0) {
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst_prop, false));
}
src_id = ufbxi_synthetic_id_from_string(src_name);
dst_id = ufbxi_synthetic_id_from_string(dst_name);
} else {
// Post-7000 versions use proper unique 64-bit IDs
if (!ufbxi_get_val1(node, "C", &type)) continue;
if (type == ufbxi_OO) {
if (!ufbxi_get_val3(node, "_LL", NULL, &src_id, &dst_id)) continue;
} else if (type == ufbxi_OP) {
if (!ufbxi_get_val4(node, "_LLS", NULL, &src_id, &dst_id, &dst_prop)) continue;
} else if (type == ufbxi_PO) {
if (!ufbxi_get_val4(node, "_LSL", NULL, &src_id, &src_prop, &dst_id)) continue;
} else if (type == ufbxi_PP) {
if (!ufbxi_get_val5(node, "_LSLS", NULL, &src_id, &src_prop, &dst_id, &dst_prop)) continue;
} else {
// TODO: Strict mode?
continue;
}
}
ufbxi_tmp_connection *conn = ufbxi_push(&uc->tmp_connections, ufbxi_tmp_connection, 1);
ufbxi_check(conn);
conn->src = src_id;
conn->dst = dst_id;
conn->src_prop = src_prop;
conn->dst_prop = dst_prop;
}
return 1;
}
// -- Pre-7000 "Take" based animation
ufbxi_forceinline static char ufbxi_double_to_char(double value)
{
if (value >= 0.0 && value <= 127.0) {
return (char)(int)value;
} else {
return 0;
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_anim_channel(ufbxi_context *uc, ufbxi_node *node, uint64_t value_fbx_id, const char *name, ufbx_real *p_default)
{
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Default, "R", p_default));
// Find the key array, early return with success if not found as we may have only a default
ufbxi_value_array *keys = ufbxi_find_array(node, ufbxi_Key, 'd');
if (!keys) return 1;
uint64_t curve_fbx_id = 0;
ufbx_anim_curve *curve = ufbxi_push_synthetic_element(uc, &curve_fbx_id, node, name, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE);
ufbxi_check(curve);
ufbxi_check(ufbxi_connect_op(uc, curve_fbx_id, value_fbx_id, curve->name));
if (uc->opts.ignore_animation) return 1;
size_t num_keys = 0;
ufbxi_check(ufbxi_find_val1(node, ufbxi_KeyCount, "Z", &num_keys));
curve->keyframes.data = ufbxi_push(&uc->result, ufbx_keyframe, num_keys);
curve->keyframes.count = num_keys;
ufbxi_check(curve->keyframes.data);
float slope_left = 0.0f;
float weight_left = 0.333333f;
double next_time = 0.0;
double next_value = 0.0;
double prev_time = 0.0;
// The pre-7000 keyframe data is stored as a _heterogenous_ array containing 64-bit integers,
// floating point values, and _bare characters_. We cast all values to double and interpret them.
double *data = (double*)keys->data, *data_end = data + keys->size;
if (num_keys > 0) {
ufbxi_check(data_end - data >= 2);
next_time = data[0] / uc->ktime_sec_double;
next_value = data[1];
}
for (size_t i = 0; i < num_keys; i++) {
ufbx_keyframe *key = &curve->keyframes.data[i];
if (i == 0) {
curve->min_value = (ufbx_real)next_value;
curve->max_value = (ufbx_real)next_value;
} else {
curve->min_value = ufbxi_min_real(curve->min_value, (ufbx_real)next_value);
curve->max_value = ufbxi_max_real(curve->max_value, (ufbx_real)next_value);
}
// First three values: Time, Value, InterpolationMode
ufbxi_check(data_end - data >= 3);
key->time = next_time;
key->value = (ufbx_real)next_value;
char mode = ufbxi_double_to_char(data[2]);
data += 3;
float slope_right = 0.0f;
float weight_right = 0.333333f;
float next_slope_left = 0.0f;
float next_weight_left = 0.333333f;
bool auto_slope = false;
if (mode == 'U') {
// Cubic interpolation
key->interpolation = UFBX_INTERPOLATION_CUBIC;
ufbxi_check(data_end - data >= 1);
char slope_mode = ufbxi_double_to_char(data[0]);
data += 1;
size_t num_weights = 1;
if (slope_mode == 's' || slope_mode == 'b') {
// Slope mode 's'/'b' (standard? broken?) always have two explicit slopes
// TODO: `b` might actually be some kind of TCB curve
ufbxi_check(data_end - data >= 2);
slope_right = (float)data[0];
next_slope_left = (float)data[1];
data += 2;
} else if (slope_mode == 'a') {
// Parameterless slope mode 'a' seems to appear in baked animations. Let's just assume
// automatic tangents for now as they're the least likely to break with
// objectionable artifacts. We need to defer the automatic tangent resolve
// until we have read the next time/value.
// TODO: Solve what this is more throroughly
auto_slope = true;
if (uc->version == 5000) {
num_weights = 0;
}
} else if (slope_mode == 'p') {
// TODO: What is this mode? It seems to have negative values sometimes?
// Also it seems to have _two_ trailing weights values, currently observed:
// `n,n` and `a,X,Y,n`...
// Ignore unknown values for now
ufbxi_check(data_end - data >= 2);
data += 2;
num_weights = 2;
} else if (slope_mode == 't') {
// TODO: What is this mode? It seems that it does not have any weights and the
// third value seems _tiny_ (around 1e-30?)
ufbxi_check(data_end - data >= 3);
data += 3;
num_weights = 0;
} else {
ufbxi_fail("Unknown slope mode");
}
for (; num_weights > 0; num_weights--) {
ufbxi_check(data_end - data >= 1);
char weight_mode = ufbxi_double_to_char(data[0]);
data += 1;
if (weight_mode == 'n') {
// Automatic weights (0.3333...)
} else if (weight_mode == 'a') {
// Manual weights: RightWeight, NextLeftWeight
ufbxi_check(data_end - data >= 2);
weight_right = (float)data[0];
next_weight_left = (float)data[1];
data += 2;
} else if (weight_mode == 'l') {
// Next left tangent is weighted
ufbxi_check(data_end - data >= 1);
next_weight_left = (float)data[0];
data += 1;
} else if (weight_mode == 'r') {
// Right tangent is weighted
ufbxi_check(data_end - data >= 1);
weight_right = (float)data[0];
data += 1;
} else if (weight_mode == 'c') {
// TODO: What is this mode? At least it has no parameters so let's
// just assume automatic weights for the time being (0.3333...)
} else {
ufbxi_fail("Unknown weight mode");
}
}
} else if (mode == 'L') {
// Linear interpolation: No parameters
key->interpolation = UFBX_INTERPOLATION_LINEAR;
} else if (mode == 'C') {
// Constant interpolation: Single parameter (use prev/next)
ufbxi_check(data_end - data >= 1);
key->interpolation = ufbxi_double_to_char(data[0]) == 'n' ? UFBX_INTERPOLATION_CONSTANT_NEXT : UFBX_INTERPOLATION_CONSTANT_PREV;
data += 1;
} else {
ufbxi_fail("Unknown key mode");
}
// Retrieve next key and value
if (i + 1 < num_keys) {
ufbxi_check(data_end - data >= 2);
next_time = data[0] / uc->ktime_sec_double;
next_value = data[1];
}
if (auto_slope) {
if (i > 0) {
slope_left = slope_right = ufbxi_solve_auto_tangent(uc,
prev_time, key->time, next_time,
key[-1].value, key->value, (ufbx_real)next_value,
weight_left, weight_right, 0.0f, UFBXI_KEY_CLAMP_PROGRESSIVE|UFBXI_KEY_TIME_INDEPENDENT);
} else {
slope_left = slope_right = 0.0f;
}
}
// Set up linear cubic tangents if necessary
if (key->interpolation == UFBX_INTERPOLATION_LINEAR) {
if (next_time > key->time) {
double slope = (next_value - key->value) / (next_time - key->time);
slope_right = next_slope_left = (float)slope;
} else {
slope_right = next_slope_left = 0.0f;
}
}
if (key->time > prev_time) {
double delta = key->time - prev_time;
key->left.dx = (float)(weight_left * delta);
key->left.dy = key->left.dx * slope_left;
} else {
key->left.dx = 0.0f;
key->left.dy = 0.0f;
}
if (next_time > key->time) {
double delta = next_time - key->time;
key->right.dx = (float)(weight_right * delta);
key->right.dy = key->right.dx * slope_right;
} else {
key->right.dx = 0.0f;
key->right.dy = 0.0f;
}
slope_left = next_slope_left;
weight_left = next_weight_left;
prev_time = key->time;
}
ufbxi_check(data == data_end);
return 1;
}
// Recursion limited as it is further called only for `name="T"/"R"/"S"` and
// cannot enter the `name=="Transform"` branch.
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_prop_channel(ufbxi_context *uc, ufbxi_node *node, uint64_t target_fbx_id, uint64_t layer_fbx_id, ufbx_string name)
ufbxi_recursive_function(int, ufbxi_read_take_prop_channel, (uc, node, target_fbx_id, layer_fbx_id, name), 2,
(ufbxi_context *uc, ufbxi_node *node, uint64_t target_fbx_id, uint64_t layer_fbx_id, ufbx_string name))
{
if (name.data == ufbxi_Transform) {
// Pre-7000 have transform keyframes in a deeply nested structure,
// flatten it to make it resemble post-7000 structure a bit closer:
// old: Model: { Channel: "Transform" { Channel: "T" { Channel "X": { ... } } } }
// new: Model: { Channel: "Lcl Translation" { Channel "X": { ... } } }
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
if (child->name != ufbxi_Channel) continue;
const char *old_name = NULL;
ufbxi_check(ufbxi_get_val1(child, "C", (char**)&old_name));
ufbx_string new_name;
if (old_name == ufbxi_T) { new_name.data = ufbxi_Lcl_Translation; new_name.length = sizeof(ufbxi_Lcl_Translation) - 1; }
else if (old_name == ufbxi_R) { new_name.data = ufbxi_Lcl_Rotation; new_name.length = sizeof(ufbxi_Lcl_Rotation) - 1; }
else if (old_name == ufbxi_S) { new_name.data = ufbxi_Lcl_Scaling; new_name.length = sizeof(ufbxi_Lcl_Scaling) - 1; }
else {
continue;
}
// Read child as a top-level property channel
ufbxi_check(ufbxi_read_take_prop_channel(uc, child, target_fbx_id, layer_fbx_id, new_name));
}
} else {
// Pre-6000 FBX files store blend shape keys with a " (Shape)" suffix
if (uc->version < 6000) {
const char *const suffix = " (Shape)";
size_t suffix_len = strlen(suffix);
if (name.length > suffix_len && !memcmp(name.data + name.length - suffix_len, suffix, suffix_len)) {
name.length -= suffix_len;
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &name, false));
}
}
// Find 1-3 channel nodes that contain a `Key:` node
ufbxi_node *channel_nodes[3] = { 0 };
const char *channel_names[3] = { 0 };
size_t num_channel_nodes = 0;
if (ufbxi_find_child(node, ufbxi_Key) || ufbxi_find_child(node, ufbxi_Default)) {
// Channel has only a single curve
channel_nodes[0] = node;
channel_names[0] = name.data;
num_channel_nodes = 1;
} else {
// Channel is a compound of multiple curves
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
if (child->name != ufbxi_Channel) continue;
if (!ufbxi_find_child(child, ufbxi_Key) && !ufbxi_find_child(child, ufbxi_Default)) continue;
if (!ufbxi_get_val1(child, "C", (char**)&channel_names[num_channel_nodes])) continue;
channel_nodes[num_channel_nodes] = child;
if (++num_channel_nodes == 3) break;
}
}
// Early return: No valid channels found, not an error
if (num_channel_nodes == 0) return 1;
uint64_t value_fbx_id = 0;
ufbx_anim_value *value = ufbxi_push_synthetic_element(uc, &value_fbx_id, node, name.data, ufbx_anim_value, UFBX_ELEMENT_ANIM_VALUE);
// Add a "virtual" connection between the animated property and the layer/target
ufbxi_check(ufbxi_connect_oo(uc, value_fbx_id, layer_fbx_id));
ufbxi_check(ufbxi_connect_op(uc, value_fbx_id, target_fbx_id, name));
for (size_t i = 0; i < num_channel_nodes; i++) {
ufbxi_check(ufbxi_read_take_anim_channel(uc, channel_nodes[i], value_fbx_id, channel_names[i], &value->default_value.v[i]));
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_object(ufbxi_context *uc, ufbxi_node *node, uint64_t layer_fbx_id)
{
// Takes are used only in pre-7000 FBX versions so objects are identified
// by their unique Type::Name pair that we use as unique IDs through the
// pooled interned string pointers.
const char *type_and_name = NULL;
ufbxi_check(ufbxi_get_val1(node, "c", (char**)&type_and_name));
uint64_t target_fbx_id = ufbxi_synthetic_id_from_string(type_and_name);
// Add all suitable Channels as animated properties
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
ufbx_string name;
if (child->name != ufbxi_Channel) continue;
if (!ufbxi_get_val1(child, "S", &name)) continue;
ufbxi_check(ufbxi_read_take_prop_channel(uc, child, target_fbx_id, layer_fbx_id, name));
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take(ufbxi_context *uc, ufbxi_node *node)
{
ufbx_prop tmp_props[4];
uint32_t num_props = 0;
memset(tmp_props, 0, sizeof(tmp_props));
int64_t start = 0, stop = 0;
if (ufbxi_find_val2(node, ufbxi_LocalTime, "LL", &start, &stop)) {
ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_LocalStart, start, UFBX_PROP_INTEGER);
ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_LocalStop, stop, UFBX_PROP_INTEGER);
}
if (ufbxi_find_val2(node, ufbxi_ReferenceTime, "LL", &start, &stop)) {
ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_ReferenceStart, start, UFBX_PROP_INTEGER);
ufbxi_init_synthetic_int_prop(&tmp_props[num_props++], ufbxi_ReferenceStop, stop, UFBX_PROP_INTEGER);
}
const char *name;
ufbxi_check(ufbxi_get_val1(node, "C", (char**)&name));
// Hack: For post-7000 files we are only interested in the animation times
// for fallback in case the information is missing in the stacks.
if (uc->version >= 7000) {
uint32_t hash = ufbxi_hash_ptr(name);
ufbxi_tmp_anim_stack *entry = ufbxi_map_find(&uc->anim_stack_map, ufbxi_tmp_anim_stack, hash, &name);
if (entry) {
ufbx_anim_stack *stack = entry->stack;
if (stack->props.props.count == 0) {
stack->props.props.count = num_props;
stack->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props);
ufbxi_check(stack->props.props.data);
}
}
return 1;
}
uint64_t stack_fbx_id = 0, layer_fbx_id = 0;
// Treat the Take as a post-7000 version animation stack and layer.
ufbx_anim_stack *stack = ufbxi_push_synthetic_element(uc, &stack_fbx_id, node, name, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK);
ufbxi_check(stack);
stack->props.props.count = num_props;
stack->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props);
ufbxi_check(stack->props.props.data);
ufbx_anim_layer *layer = ufbxi_push_synthetic_element(uc, &layer_fbx_id, node, ufbxi_BaseLayer, ufbx_anim_layer, UFBX_ELEMENT_ANIM_LAYER);
ufbxi_check(layer);
ufbxi_check(ufbxi_connect_oo(uc, layer_fbx_id, stack_fbx_id));
// Read all properties of objects included in the take
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
// TODO: Do some object types have another name?
if (child->name != ufbxi_Model) continue;
ufbxi_check(ufbxi_read_take_object(uc, child, layer_fbx_id));
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_takes(ufbxi_context *uc)
{
for (;;) {
ufbxi_node *node;
ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, NULL));
if (!node) break;
if (node->name == ufbxi_Take) {
ufbxi_check(ufbxi_read_take(uc, node));
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_settings(ufbxi_context *uc, ufbxi_node *node)
{
if (uc->read_legacy_settings) return 1;
uc->read_legacy_settings = true;
ufbx_prop tmp_props[2];
uint32_t num_props = 0;
memset(tmp_props, 0, sizeof(tmp_props));
ufbxi_node *frame_rate = ufbxi_find_child_strcmp(node, "FrameRate");
if (frame_rate) {
double fps = 0.0;
if (!ufbxi_get_val1(frame_rate, "D", &fps)) {
ufbx_string str;
if (ufbxi_get_val1(frame_rate, "S", &str)) {
char *end;
double val = ufbxi_parse_double(str.data, str.length, &end, uc->double_parse_flags);
if (end == str.data + str.length) {
fps = val;
}
}
}
if (fps > 0.0) {
ufbxi_init_synthetic_real_prop(&tmp_props[num_props++], ufbxi_CustomFrameRate, (ufbx_real)fps, UFBX_PROP_NUMBER);
ufbxi_init_synthetic_real_prop(&tmp_props[num_props++], ufbxi_TimeMode, UFBX_TIME_MODE_CUSTOM, UFBX_PROP_INTEGER);
}
}
if (num_props > 0) {
ufbx_props *props = &uc->scene.settings.props;
size_t num_existing = props->props.count;
size_t new_count = num_props + num_existing;
ufbx_prop *new_props = ufbxi_push(&uc->result, ufbx_prop, new_count);
ufbxi_check(new_props);
memcpy(new_props, tmp_props, num_props * sizeof(ufbx_prop));
if (num_existing > 0) {
memcpy(new_props + num_props, props->props.data, num_existing * sizeof(ufbx_prop));
}
ufbxi_check(ufbxi_sort_properties(uc, new_props, new_count));
props->props.data = new_props;
props->props.count = new_count;
ufbxi_deduplicate_properties(&props->props);
ufbxi_check(uc->scene.settings.props.props.data);
}
return 1;
}
ufbxi_noinline static ufbx_matrix ufbxi_unscaled_transform_to_matrix(const ufbx_transform *t)
{
ufbx_transform transform = *t;
transform.scale.x = 1.0f;
transform.scale.y = 1.0f;
transform.scale.z = 1.0f;
return ufbx_transform_to_matrix(&transform);
}
ufbxi_noinline static void ufbxi_setup_root_node(ufbxi_context *uc, ufbx_node *root)
{
if (uc->opts.use_root_transform) {
root->local_transform = uc->opts.root_transform;
root->node_to_parent = ufbx_transform_to_matrix(&uc->opts.root_transform);
} else {
root->local_transform = ufbx_identity_transform;
root->node_to_parent = ufbx_identity_matrix;
}
root->is_root = true;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_root(ufbxi_context *uc)
{
// FBXHeaderExtension: Some metadata (optional)
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_FBXHeaderExtension));
ufbxi_check(ufbxi_read_header_extension(uc));
// The ASCII exporter version is stored in top-level
if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) {
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Creator));
if (uc->top_node) {
ufbxi_ignore(ufbxi_get_val1(uc->top_node, "S", &uc->scene.metadata.creator));
}
}
// Resolve the exporter before continuing
ufbxi_check(ufbxi_match_exporter(uc));
if (uc->version < 7000) {
ufbxi_check(ufbxi_init_node_prop_names(uc));
}
// Don't allow changing version from this point onwards
uc->ascii.found_version = true;
// Document: Read root ID
if (uc->version >= 7000) {
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Documents));
ufbxi_check(ufbxi_read_document(uc));
} else {
// Pre-7000: Root node has a specific type-name pair "Model::Scene"
// (or reversed in binary). Use the interned name as ID as usual.
const char *root_name = uc->from_ascii ? "Model::Scene" : "Scene\x00\x01Model";
root_name = ufbxi_push_string_imp(&uc->string_pool, root_name, 12, NULL, false, true);
ufbxi_check(root_name);
uc->root_id = ufbxi_synthetic_id_from_string(root_name);
}
// Add a nameless root node with the root ID
{
ufbxi_element_info root_info = { uc->root_id };
root_info.name = ufbx_empty_string;
ufbx_node *root = ufbxi_push_element(uc, &root_info, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(root);
ufbxi_setup_root_node(uc, root);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id));
}
// Definitions: Object type counts and property templates (optional)
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Definitions));
ufbxi_check(ufbxi_read_definitions(uc));
// Objects: Actual scene data
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Objects));
if (!uc->sure_fbx) {
// If the file is a bit iffy about being a real FBX file reject it if
// even the objects are not found.
ufbxi_check_msg(uc->top_node, "Not an FBX file");
}
if (uc->thread_pool.enabled) {
ufbxi_check(ufbxi_read_objects_threaded(uc));
} else {
ufbxi_check(ufbxi_read_objects(uc));
}
// Connections: Relationships between nodes
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Connections));
ufbxi_check(ufbxi_read_connections(uc));
// Takes: Pre-7000 animation data
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Takes));
ufbxi_check(ufbxi_read_takes(uc));
// Check if there's a top-level GlobalSettings that we skimmed over
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_GlobalSettings));
if (uc->top_node) {
ufbxi_check(ufbxi_read_global_settings(uc, uc->top_node));
}
// Version5: Pre-6000 settings
ufbxi_check(ufbxi_parse_toplevel(uc, ufbxi_Version5));
if (uc->top_node) {
ufbxi_node *settings = ufbxi_find_child_strcmp(uc->top_node, "Settings");
if (settings) {
ufbxi_check(ufbxi_read_legacy_settings(uc, settings));
}
}
// Force parsing all the nodes by parsing a toplevel that cannot be found
if (uc->opts.retain_dom) {
ufbxi_check(ufbxi_parse_toplevel(uc, NULL));
}
return 1;
}
typedef struct {
const char *prop_name;
ufbx_prop_type prop_type;
const char *node_name;
const char *node_fmt;
} ufbxi_legacy_prop;
// Must be alphabetically sorted!
static const ufbxi_legacy_prop ufbxi_legacy_light_props[] = {
{ ufbxi_CastLight, UFBX_PROP_BOOLEAN, ufbxi_CastLight, "L" },
{ ufbxi_CastShadows, UFBX_PROP_BOOLEAN, ufbxi_CastShadows, "L" },
{ ufbxi_Color, UFBX_PROP_COLOR, ufbxi_Color, "RRR" },
{ ufbxi_ConeAngle, UFBX_PROP_NUMBER, ufbxi_ConeAngle, "R" },
{ ufbxi_HotSpot, UFBX_PROP_NUMBER, ufbxi_HotSpot, "R" },
{ ufbxi_Intensity, UFBX_PROP_NUMBER, ufbxi_Intensity, "R" },
{ ufbxi_LightType, UFBX_PROP_INTEGER, ufbxi_LightType, "L" },
};
// Must be alphabetically sorted!
static const ufbxi_legacy_prop ufbxi_legacy_camera_props[] = {
{ ufbxi_ApertureMode, UFBX_PROP_INTEGER, ufbxi_ApertureMode, "L" },
{ ufbxi_AspectH, UFBX_PROP_NUMBER, ufbxi_AspectH, "R" },
{ ufbxi_AspectRatioMode, UFBX_PROP_INTEGER, "AspectType", "L" },
{ ufbxi_AspectW, UFBX_PROP_NUMBER, ufbxi_AspectW, "R" },
{ ufbxi_FieldOfView, UFBX_PROP_NUMBER, "Aperture", "R" },
{ ufbxi_FieldOfViewX, UFBX_PROP_NUMBER, "FieldOfViewXProperty", "R" },
{ ufbxi_FieldOfViewY, UFBX_PROP_NUMBER, "FieldOfViewYProperty", "R" },
{ ufbxi_FilmHeight, UFBX_PROP_NUMBER, "CameraAperture", "_R" },
{ ufbxi_FilmSqueezeRatio, UFBX_PROP_NUMBER, "SqueezeRatio", "R" },
{ ufbxi_FilmWidth, UFBX_PROP_NUMBER, "CameraAperture", "R_" },
{ ufbxi_FocalLength, UFBX_PROP_NUMBER, ufbxi_FocalLength, "R" },
};
// Must be alphabetically sorted!
static const ufbxi_legacy_prop ufbxi_legacy_bone_props[] = {
{ ufbxi_Size, UFBX_PROP_NUMBER, ufbxi_Size, "R" },
};
// Must be alphabetically sorted!
static const ufbxi_legacy_prop ufbxi_legacy_material_props[] = {
{ ufbxi_AmbientColor, UFBX_PROP_COLOR, "Ambient", "RRR" },
{ ufbxi_DiffuseColor, UFBX_PROP_COLOR, "Diffuse", "RRR" },
{ ufbxi_EmissiveColor, UFBX_PROP_COLOR, "Emissive", "RRR" },
{ ufbxi_ShadingModel, UFBX_PROP_COLOR, ufbxi_ShadingModel, "S" },
{ ufbxi_Shininess, UFBX_PROP_NUMBER, "Shininess", "R" },
{ ufbxi_SpecularColor, UFBX_PROP_COLOR, "Specular", "RRR" },
};
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_prop(ufbxi_node *node, ufbx_prop *prop, const ufbxi_legacy_prop *legacy_prop)
{
size_t value_ix = 0;
uint32_t flags = 0;
const char *fmt = legacy_prop->node_fmt;
for (size_t fmt_ix = 0; fmt[fmt_ix]; fmt_ix++) {
char c = fmt[fmt_ix];
switch (c) {
case 'L':
ufbx_assert(value_ix == 0);
if (!ufbxi_get_val_at(node, fmt_ix, 'L', &prop->value_int)) return 0;
prop->value_real = (ufbx_real)prop->value_int;
prop->value_real_arr[1] = 0.0f;
prop->value_real_arr[2] = 0.0f;
prop->value_real_arr[3] = 0.0f;
prop->value_str = ufbx_empty_string;
prop->value_blob = ufbx_empty_blob;
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_INT;
value_ix++;
break;
case 'R':
ufbx_assert(value_ix < 4);
if (!ufbxi_get_val_at(node, fmt_ix, 'R', &prop->value_real_arr[value_ix])) return 0;
if (value_ix == 0) {
prop->value_int = ufbxi_f64_to_i64(prop->value_real);
prop->value_real_arr[1] = 0.0f;
prop->value_real_arr[2] = 0.0f;
prop->value_real_arr[3] = 0.0f;
prop->value_str = ufbx_empty_string;
prop->value_blob = ufbx_empty_blob;
}
flags &= ~(uint32_t)(UFBX_PROP_FLAG_VALUE_REAL|UFBX_PROP_FLAG_VALUE_VEC2|UFBX_PROP_FLAG_VALUE_VEC3|UFBX_PROP_FLAG_VALUE_VEC4);
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << value_ix;
value_ix++;
break;
case 'S':
ufbx_assert(value_ix == 0);
if (!ufbxi_get_val_at(node, fmt_ix, 'S', &prop->value_str)) return 0;
if (prop->value_str.length > 0) {
int found = ufbxi_get_val_at(node, fmt_ix, 'b', &prop->value_blob);
ufbxi_ignore(found);
ufbx_assert(found);
} else {
prop->value_blob = ufbx_empty_blob;
}
prop->value_real = 0.0f;
prop->value_real_arr[1] = 0.0f;
prop->value_real_arr[2] = 0.0f;
prop->value_real_arr[3] = 0.0f;
prop->value_int = 0;
flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_STR;
value_ix++;
break;
case '_':
break;
default:
ufbxi_unreachable("Unhandled legacy fmt");
}
}
prop->flags = (ufbx_prop_flags)flags;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static size_t ufbxi_read_legacy_props(ufbxi_node *node, ufbx_prop *props, const ufbxi_legacy_prop *legacy_props, size_t num_legacy)
{
size_t num_props = 0;
for (size_t legacy_ix = 0; legacy_ix < num_legacy; legacy_ix++) {
const ufbxi_legacy_prop *legacy_prop = &legacy_props[legacy_ix];
ufbx_prop *prop = &props[num_props];
ufbxi_node *n = ufbxi_find_child_strcmp(node, legacy_prop->node_name);
if (!n) continue;
if (!ufbxi_read_legacy_prop(n, prop, legacy_prop)) continue;
prop->name.data = legacy_prop->prop_name;
prop->name.length = strlen(legacy_prop->prop_name);
prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length);
prop->flags = (ufbx_prop_flags)0;
prop->type = legacy_prop->prop_type;
num_props++;
}
return num_props;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_material(ufbxi_context *uc, ufbxi_node *node, uint64_t *p_fbx_id, const char *name)
{
ufbx_material *ufbxi_restrict material = ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, ufbx_material, UFBX_ELEMENT_MATERIAL);
ufbxi_check(material);
ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_material_props)];
size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_material_props, ufbxi_arraycount(ufbxi_legacy_material_props));
material->shading_model_name = ufbx_empty_string;
material->props.props.count = num_props;
material->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props);
ufbxi_check(material->props.props.data);
material->shader_prop_prefix = ufbx_empty_string;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_link(ufbxi_context *uc, ufbxi_node *node, uint64_t *p_fbx_id, const char *name)
{
ufbx_skin_cluster *ufbxi_restrict cluster = ufbxi_push_synthetic_element(uc, p_fbx_id, node, name, ufbx_skin_cluster, UFBX_ELEMENT_SKIN_CLUSTER);
ufbxi_check(cluster);
// TODO: Merge with ufbxi_read_skin_cluster(), at least partially?
ufbxi_value_array *indices = ufbxi_find_array(node, ufbxi_Indexes, 'i');
ufbxi_value_array *weights = ufbxi_find_array(node, ufbxi_Weights, 'r');
if (indices && weights) {
ufbxi_check(indices->size == weights->size);
cluster->num_weights = indices->size;
cluster->vertices.data = (uint32_t*)indices->data;
cluster->weights.data = (ufbx_real*)weights->data;
cluster->vertices.count = cluster->num_weights;
cluster->weights.count = cluster->num_weights;
}
ufbxi_value_array *transform = ufbxi_find_array(node, ufbxi_Transform, 'r');
ufbxi_value_array *transform_link = ufbxi_find_array(node, ufbxi_TransformLink, 'r');
if (transform && transform_link) {
ufbxi_check(transform->size >= 16);
ufbxi_check(transform_link->size >= 16);
ufbxi_read_transform_matrix(&cluster->mesh_node_to_bone, (ufbx_real*)transform->data);
ufbxi_read_transform_matrix(&cluster->bind_to_world, (ufbx_real*)transform_link->data);
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_light(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_light *ufbxi_restrict light = ufbxi_push_element(uc, info, ufbx_light, UFBX_ELEMENT_LIGHT);
ufbxi_check(light);
ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_light_props)];
size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_light_props, ufbxi_arraycount(ufbxi_legacy_light_props));
light->props.props.count = num_props;
light->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props);
ufbxi_check(light->props.props.data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_camera(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_camera *ufbxi_restrict camera = ufbxi_push_element(uc, info, ufbx_camera, UFBX_ELEMENT_CAMERA);
ufbxi_check(camera);
ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_camera_props)];
size_t num_props = ufbxi_read_legacy_props(node, tmp_props, ufbxi_legacy_camera_props, ufbxi_arraycount(ufbxi_legacy_camera_props));
camera->props.props.count = num_props;
camera->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props);
ufbxi_check(camera->props.props.data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_limb_node(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
ufbx_bone *ufbxi_restrict bone = ufbxi_push_element(uc, info, ufbx_bone, UFBX_ELEMENT_BONE);
ufbxi_check(bone);
ufbx_prop tmp_props[ufbxi_arraycount(ufbxi_legacy_bone_props)];
size_t num_props = 0;
ufbxi_node *prop_node = ufbxi_find_child_strcmp(node, "Properties");
if (prop_node) {
num_props = ufbxi_read_legacy_props(prop_node, tmp_props, ufbxi_legacy_bone_props, ufbxi_arraycount(ufbxi_legacy_bone_props));
}
bone->props.props.count = num_props;
bone->props.props.data = ufbxi_push_copy(&uc->result, ufbx_prop, num_props, tmp_props);
ufbxi_check(bone->props.props.data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_mesh(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info)
{
// Only read polygon meshes, ignore eg. NURBS without error
ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices);
ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex);
if (!node_vertices || !node_indices) return 1;
ufbx_mesh *ufbxi_restrict mesh = ufbxi_push_element(uc, info, ufbx_mesh, UFBX_ELEMENT_MESH);
ufbxi_check(mesh);
ufbxi_check(ufbxi_read_synthetic_blend_shapes(uc, node, info));
ufbxi_patch_mesh_reals(mesh);
if (uc->opts.ignore_geometry) return 1;
ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r');
ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i');
ufbxi_check(vertices && indices);
ufbxi_check(vertices->size % 3 == 0);
mesh->num_vertices = vertices->size / 3;
mesh->num_indices = indices->size;
uint32_t *index_data = (uint32_t*)indices->data;
// Duplicate `index_data` for modification if we retain DOM
if (uc->opts.retain_dom) {
index_data = ufbxi_push_copy(&uc->result, uint32_t, indices->size, index_data);
ufbxi_check(index_data);
}
mesh->vertices.data = (ufbx_vec3*)vertices->data;
mesh->vertex_indices.data = index_data;
mesh->vertices.count = mesh->num_vertices;
mesh->vertex_indices.count = mesh->num_indices;
mesh->vertex_position.exists = true;
mesh->vertex_position.values.data = (ufbx_vec3*)vertices->data;
mesh->vertex_position.values.count = mesh->num_vertices;
mesh->vertex_position.indices.data = index_data;
mesh->vertex_position.indices.count = mesh->num_indices;
mesh->vertex_position.unique_per_vertex = true;
// Check/make sure that the last index is negated (last of polygon)
if (mesh->num_indices > 0) {
if ((int32_t)index_data[mesh->num_indices - 1] >= 0) {
if (uc->opts.strict) ufbxi_fail("Non-negated last index");
index_data[mesh->num_indices - 1] = ~index_data[mesh->num_indices - 1];
}
}
ufbxi_check(ufbxi_process_indices(uc, mesh, index_data));
// Normals are either per-vertex or per-index in legacy FBX files?
// If the version is 5000 prefer per-vertex, otherwise per-index...
ufbxi_value_array *normals = ufbxi_find_array(node, ufbxi_Normals, 'r');
if (normals) {
size_t num_normals = normals->size / 3;
bool per_vertex = num_normals == mesh->num_vertices;
bool per_index = num_normals == mesh->num_indices;
if (per_vertex && (!per_index || uc->version == 5000)) {
mesh->vertex_normal.exists = true;
mesh->vertex_normal.values.count = num_normals;
mesh->vertex_normal.indices.count = mesh->num_indices;
mesh->vertex_normal.unique_per_vertex = true;
mesh->vertex_normal.values.data = (ufbx_vec3*)normals->data;
mesh->vertex_normal.indices.data = mesh->vertex_indices.data;
} else if (per_index) {
uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, mesh->num_indices);
mesh->vertex_normal.exists = true;
mesh->vertex_normal.values.count = num_normals;
mesh->vertex_normal.indices.count = mesh->num_indices;
mesh->vertex_normal.unique_per_vertex = false;
mesh->vertex_normal.values.data = (ufbx_vec3*)normals->data;
mesh->vertex_normal.indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive;
}
}
// Optional UV values are stored pretty much like a modern vertex element
ufbxi_node *uv_info = ufbxi_find_child(node, ufbxi_GeometryUVInfo);
if (uv_info) {
ufbx_uv_set *set = ufbxi_push_zero(&uc->result, ufbx_uv_set, 1);
ufbxi_check(set);
set->index = 0;
set->name.data = ufbxi_empty_char;
ufbxi_check(ufbxi_read_vertex_element(uc, mesh, uv_info, (ufbx_vertex_attrib*)&set->vertex_uv,
ufbxi_TextureUV, ufbxi_TextureUVVerticeIndex, NULL, 'r', 2));
mesh->uv_sets.data = set;
mesh->uv_sets.count = 1;
mesh->vertex_uv = set->vertex_uv;
}
// Material indices
{
const char *mapping = NULL;
ufbxi_check(ufbxi_find_val1(node, ufbxi_MaterialAssignation, "C", (char**)&mapping));
if (mapping == ufbxi_ByPolygon) {
ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->face_material.data, &mesh->face_material.count, node, ufbxi_Materials, 'i', mesh->num_faces));
} else if (mapping == ufbxi_AllSame) {
ufbxi_value_array *arr = ufbxi_find_array(node, ufbxi_Materials, 'i');
uint32_t material = 0;
if (arr && arr->size >= 1) {
material = ((uint32_t*)arr->data)[0];
}
mesh->face_material.count = mesh->num_faces;
if (material == 0) {
mesh->face_material.data = (uint32_t*)ufbxi_sentinel_index_zero;
} else {
mesh->face_material.data = ufbxi_push(&uc->result, uint32_t, mesh->num_faces);
ufbxi_check(mesh->face_material.data);
ufbxi_for_list(uint32_t, p_mat, mesh->face_material) {
*p_mat = material;
}
}
}
}
uint64_t skin_fbx_id = 0;
ufbx_skin_deformer *skin = NULL;
// Materials, Skin Clusters
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
if (child->name == ufbxi_Material) {
uint64_t fbx_id = 0;
ufbx_string type_and_name, type, name;
ufbxi_check(ufbxi_get_val1(child, "s", &type_and_name));
ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name));
ufbxi_check(ufbxi_read_legacy_material(uc, child, &fbx_id, name.data));
ufbxi_check(ufbxi_connect_oo(uc, fbx_id, info->fbx_id));
} else if (child->name == ufbxi_Link) {
uint64_t fbx_id = 0;
ufbx_string type_and_name, type, name;
ufbxi_check(ufbxi_get_val1(child, "s", &type_and_name));
ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name));
ufbxi_check(ufbxi_read_legacy_link(uc, child, &fbx_id, name.data));
uint64_t node_fbx_id = ufbxi_synthetic_id_from_string(type_and_name.data);
ufbxi_check(ufbxi_connect_oo(uc, node_fbx_id, fbx_id));
if (!skin) {
skin = ufbxi_push_synthetic_element(uc, &skin_fbx_id, NULL, info->name.data, ufbx_skin_deformer, UFBX_ELEMENT_SKIN_DEFORMER);
ufbxi_check(skin);
ufbxi_check(ufbxi_connect_oo(uc, skin_fbx_id, info->fbx_id));
}
ufbxi_check(ufbxi_connect_oo(uc, fbx_id, skin_fbx_id));
}
}
mesh->skinned_is_local = true;
mesh->skinned_position = mesh->vertex_position;
mesh->skinned_normal = mesh->vertex_normal;
ufbxi_patch_mesh_reals(mesh);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_media(ufbxi_context *uc, ufbxi_node *node)
{
ufbxi_node *videos = ufbxi_find_child(node, ufbxi_Video);
if (videos) {
ufbxi_for(ufbxi_node, child, videos->children, videos->num_children) {
ufbxi_element_info video_info = { 0 };
ufbxi_check(ufbxi_get_val1(child, "S", &video_info.name));
ufbxi_check(ufbxi_push_synthetic_id(uc, &video_info.fbx_id));
video_info.dom_node = ufbxi_get_dom_node(uc, node);
ufbxi_check(ufbxi_read_video(uc, child, &video_info));
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_model(ufbxi_context *uc, ufbxi_node *node)
{
ufbx_string type_and_name, type, name;
ufbxi_check(ufbxi_get_val1(node, "s", &type_and_name));
ufbxi_check(ufbxi_split_type_and_name(uc, type_and_name, &type, &name));
ufbxi_element_info info = { 0 };
info.fbx_id = ufbxi_synthetic_id_from_string(type_and_name.data);
info.name = name;
info.dom_node = ufbxi_get_dom_node(uc, node);
ufbx_node *elem_node = ufbxi_push_element(uc, &info, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(elem_node);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &elem_node->element.element_id));
ufbxi_element_info attrib_info = { 0 };
ufbxi_check(ufbxi_push_synthetic_id(uc, &attrib_info.fbx_id));
attrib_info.name = name;
attrib_info.dom_node = info.dom_node;
// If we make unused connections it doesn't matter..
ufbxi_check(ufbxi_connect_oo(uc, attrib_info.fbx_id, info.fbx_id));
const char *attrib_type = ufbxi_empty_char;
ufbxi_ignore(ufbxi_find_val1(node, ufbxi_Type, "C", (char**)&attrib_type));
bool has_attrib = true;
if (attrib_type == ufbxi_Light) {
ufbxi_check(ufbxi_read_legacy_light(uc, node, &attrib_info));
} else if (attrib_type == ufbxi_Camera) {
ufbxi_check(ufbxi_read_legacy_camera(uc, node, &attrib_info));
} else if (attrib_type == ufbxi_LimbNode) {
ufbxi_check(ufbxi_read_legacy_limb_node(uc, node, &attrib_info));
} else if (ufbxi_find_child(node, ufbxi_Vertices)) {
ufbxi_check(ufbxi_read_legacy_mesh(uc, node, &attrib_info));
} else {
has_attrib = false;
}
// Mark the node as having an attribute so property connections can be forwarded
if (has_attrib) {
ufbxi_check(ufbxi_insert_fbx_attr(uc, info.fbx_id, attrib_info.fbx_id));
}
// Children are represented as an array of strings
ufbxi_value_array *children = ufbxi_find_array(node, ufbxi_Children, 's');
if (children) {
ufbx_string *names = (ufbx_string*)children->data;
for (size_t i = 0; i < children->size; i++) {
uint64_t child_fbx_id = ufbxi_synthetic_id_from_string(names[i].data);
ufbxi_check(ufbxi_connect_oo(uc, child_fbx_id, info.fbx_id));
}
}
// Non-take animation channels
ufbxi_for(ufbxi_node, child, node->children, node->num_children) {
if (child->name == ufbxi_Channel) {
ufbx_string channel_name;
if (ufbxi_get_val1(child, "S", &channel_name)) {
if (uc->legacy_implicit_anim_layer_id == 0) {
// Defer creation so we won't be the first animation stack..
ufbxi_check(ufbxi_push_synthetic_id(uc, &uc->legacy_implicit_anim_layer_id));
}
ufbxi_check(ufbxi_read_take_prop_channel(uc, child, info.fbx_id, uc->legacy_implicit_anim_layer_id, channel_name));
}
}
}
return 1;
}
// Read a pre-6000 FBX file where everything is stored at the root level
ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_legacy_root(ufbxi_context *uc)
{
ufbxi_check(ufbxi_init_node_prop_names(uc));
// Some legacy FBX files have an `Fbx_Root` node that could be used as the
// root node. However no other formats have root node with transforms so it
// might be better to leave it as-is and create an empty one.
{
ufbx_node *root = ufbxi_push_synthetic_element(uc, &uc->root_id, NULL, ufbxi_empty_char, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(root);
ufbxi_setup_root_node(uc, root);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id));
}
// NOTE: `ufbxi_read_header_extension()` is optional so use default KTime definition
uc->ktime_sec = 46186158000;
uc->ktime_sec_double = (double)uc->ktime_sec;
for (;;) {
ufbxi_check(ufbxi_parse_legacy_toplevel(uc));
if (!uc->top_node) break;
ufbxi_node *node = uc->top_node;
if (node->name == ufbxi_FBXHeaderExtension) {
ufbxi_check(ufbxi_read_header_extension(uc));
} else if (node->name == ufbxi_Media) {
ufbxi_check(ufbxi_read_legacy_media(uc, node));
} else if (node->name == ufbxi_Takes) {
ufbxi_check(ufbxi_read_takes(uc));
} else if (node->name == ufbxi_Model) {
ufbxi_check(ufbxi_read_legacy_model(uc, node));
} else if (!strcmp(node->name, "Settings")) {
ufbxi_check(ufbxi_read_legacy_settings(uc, node));
}
}
if (uc->opts.retain_dom) {
ufbxi_check(ufbxi_retain_toplevel(uc, NULL));
}
// Create the implicit animation stack if necessary
if (uc->legacy_implicit_anim_layer_id) {
ufbxi_element_info layer_info = { 0 };
layer_info.fbx_id = uc->legacy_implicit_anim_layer_id;
layer_info.name.data = "(internal)";
layer_info.name.length = strlen(layer_info.name.data);
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &layer_info.name, true));
ufbx_anim_layer *layer = ufbxi_push_element(uc, &layer_info, ufbx_anim_layer, UFBX_ELEMENT_ANIM_LAYER);
ufbxi_check(layer);
ufbxi_element_info stack_info = layer_info;
ufbxi_check(ufbxi_push_synthetic_id(uc, &stack_info.fbx_id));
ufbx_anim_stack *stack = ufbxi_push_element(uc, &stack_info, ufbx_anim_stack, UFBX_ELEMENT_ANIM_STACK);
ufbxi_check(stack);
ufbxi_check(ufbxi_connect_oo(uc, layer_info.fbx_id, stack_info.fbx_id));
}
return 1;
}
// Filename manipulation
ufbxi_nodiscard ufbxi_noinline static size_t ufbxi_trim_delimiters(ufbxi_context *uc, const char *data, size_t length)
{
for (; length > 0; length--) {
char c = data[length - 1];
bool is_separator = c == '/' || c == uc->opts.path_separator;
if (is_separator) {
length--;
break;
}
}
return length;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_init_file_paths(ufbxi_context *uc)
{
if (uc->opts.filename.length > 0) {
uc->scene.metadata.filename = uc->opts.filename;
} else if (uc->opts.raw_filename.size > 0) {
uc->scene.metadata.filename.data = (const char*)uc->opts.raw_filename.data;
uc->scene.metadata.filename.length = uc->opts.raw_filename.size;
}
if (uc->opts.raw_filename.size > 0) {
uc->scene.metadata.raw_filename = uc->opts.raw_filename;
} else if (uc->opts.filename.length > 0) {
uc->scene.metadata.raw_filename.data = uc->opts.filename.data;
uc->scene.metadata.raw_filename.size = uc->opts.filename.length;
}
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->scene.metadata.filename, false));
ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &uc->scene.metadata.raw_filename, true));
uc->scene.metadata.relative_root.data = uc->scene.metadata.filename.data;
uc->scene.metadata.relative_root.length = ufbxi_trim_delimiters(uc, uc->scene.metadata.filename.data, uc->scene.metadata.filename.length);
uc->scene.metadata.raw_relative_root.data = uc->scene.metadata.raw_filename.data;
uc->scene.metadata.raw_relative_root.size = ufbxi_trim_delimiters(uc, (const char*)uc->scene.metadata.raw_filename.data, uc->scene.metadata.raw_filename.size);
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->scene.metadata.relative_root, false));
ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &uc->scene.metadata.raw_relative_root, true));
return 1;
}
typedef union {
ufbx_string str;
ufbx_blob blob;
} ufbxi_strblob;
static ufbxi_noinline void ufbxi_strblob_set(ufbxi_strblob *dst, const char *data, size_t length, bool raw)
{
if (raw) {
dst->blob.data = data;
dst->blob.size = length;
} else {
dst->str.data = length == 0 ? ufbxi_empty_char : data;
dst->str.length = length;
}
}
static ufbxi_forceinline const char *ufbxi_strblob_data(const ufbxi_strblob *strblob, bool raw)
{
return raw ? (const char*)strblob->blob.data : strblob->str.data;
}
static ufbxi_forceinline size_t ufbxi_strblob_length(const ufbxi_strblob *strblob, bool raw)
{
return raw ? strblob->blob.size : strblob->str.length;
}
ufbxi_nodiscard ufbxi_noinline static bool ufbxi_is_absolute_path(const char *path, size_t length)
{
if (length > 0 && (path[0] == '/' || path[0] == '\\')) {
return true;
} else if (length > 2 && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
return true;
}
return false;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_relative_filename(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_src, bool raw)
{
const char *src = ufbxi_strblob_data(p_src, raw);
size_t src_length = ufbxi_strblob_length(p_src, raw);
// Skip leading directory separators and early return if the relative path is empty
while (src_length > 0 && (src[0] == '/' || src[0] == '\\')) {
src++;
src_length--;
}
if (src_length == 0) {
ufbxi_strblob_set(p_dst, NULL, 0, raw);
return 1;
}
const char *prefix_data;
size_t prefix_length;
if (raw) {
prefix_data = (const char*)uc->scene.metadata.raw_relative_root.data;
prefix_length = uc->scene.metadata.raw_relative_root.size;
} else {
prefix_data = (const char*)uc->scene.metadata.relative_root.data;
prefix_length = uc->scene.metadata.relative_root.length;
}
// Retain absolute paths
if (ufbxi_is_absolute_path(src, src_length)) {
prefix_length = 0;
}
// Undo directories from `prefix` for every `..`
while (prefix_length > 0 && src_length >= 3 && src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\\')) {
size_t part_start = prefix_length;
while (part_start > 0 && !(prefix_data[part_start - 1] == '/' || prefix_data[part_start - 1] == '\\')) {
part_start--;
}
size_t part_len = prefix_length - part_start;
if (part_len == 2 && prefix_data[part_start] == '.' && prefix_data[part_start + 1] == '.') {
// Prefix itself ends in `..`, cannot cancel out a leading `../`
break;
}
// Eat the leading '/' before the part segment
prefix_length = part_start > 0 ? part_start - 1 : 0;
if (part_len == 1 && prefix_data[part_start] == '.') {
// Single '.' -> remove and continue without cancelling out a leading `../`
continue;
}
src += 3;
src_length -= 3;
}
size_t result_cap = prefix_length + src_length + 1;
char *result = ufbxi_push(&uc->tmp_stack, char, result_cap);
ufbxi_check(result);
char *ptr = result;
// Copy prefix and suffix converting separators in the process
if (prefix_length > 0) {
memcpy(ptr, prefix_data, prefix_length);
ptr[prefix_length] = uc->opts.path_separator;
ptr += prefix_length + 1;
}
for (size_t i = 0; i < src_length; i++) {
char c = src[i];
if (c == '/' || c == '\\') {
c = uc->opts.path_separator;
}
*ptr++ = c;
}
// Intern the string and pop the temporary buffer
ufbx_string dst = { result, ufbxi_to_size(ptr - result) };
ufbx_assert(dst.length <= result_cap);
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &dst, raw));
ufbxi_pop(&uc->tmp_stack, char, result_cap, NULL);
ufbxi_strblob_set(p_dst, dst.data, dst.length, raw);
return 1;
}
// Open file utility
static void *ufbxi_ator_alloc(void *user, size_t size)
{
ufbxi_allocator *ator = (ufbxi_allocator*)user;
return ufbxi_alloc(ator, char, size);
}
static void *ufbxi_ator_realloc(void *user, void *old_ptr, size_t old_size, size_t new_size)
{
ufbxi_allocator *ator = (ufbxi_allocator*)user;
return ufbxi_realloc(ator, char, old_ptr, old_size, new_size);
}
static void ufbxi_ator_free(void *user, void *ptr, size_t size)
{
ufbxi_allocator *ator = (ufbxi_allocator*)user;
ufbxi_free(ator, char, ptr, size);
}
static ufbxi_noinline void ufbxi_setup_ator_allocator(ufbx_allocator *allocator, ufbxi_allocator *ator)
{
allocator->alloc_fn = &ufbxi_ator_alloc;
allocator->realloc_fn = &ufbxi_ator_realloc;
allocator->free_fn = &ufbxi_ator_free;
allocator->free_allocator_fn = NULL;
allocator->user = ator;
}
static ufbxi_noinline bool ufbxi_open_file(const ufbx_open_file_cb *cb, ufbx_stream *stream, const char *path, size_t path_len, const ufbx_blob *original_filename, ufbxi_allocator *ator, ufbx_open_file_type type)
{
if (!cb || !cb->fn) return false;
ufbx_open_file_info info; // ufbxi_uninit
if (ator) {
ufbxi_setup_ator_allocator(&info.temp_allocator, ator);
} else {
memset(&info.temp_allocator, 0, sizeof(info.temp_allocator));
}
if (original_filename) {
info.original_filename = *original_filename;
} else {
info.original_filename.data = path;
info.original_filename.size = path_len;
}
info.type = type;
return cb->fn(cb->user, stream, path, path_len, &info);
}
#define ufbxi_patch_zero(dst, src) do { \
ufbx_assert((dst) == 0 || (dst) == (src)); \
(dst) = (src); \
} while (0)
static void ufbxi_update_vertex_first_index(ufbx_mesh *mesh)
{
ufbxi_for_list(uint32_t, p_vx_ix, mesh->vertex_first_index) {
*p_vx_ix = UFBX_NO_INDEX;
}
uint32_t num_vertices = (uint32_t)mesh->num_vertices;
for (size_t ix = 0; ix < mesh->num_indices; ix++) {
uint32_t vx = mesh->vertex_indices.data[ix];
if (vx < num_vertices && mesh->vertex_first_index.data[vx] == UFBX_NO_INDEX) {
mesh->vertex_first_index.data[vx] = (uint32_t)ix;
}
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh)
{
if (mesh->vertices.count == 0) {
mesh->vertices = mesh->vertex_position.values;
}
if (mesh->vertex_indices.count == 0) {
mesh->vertex_indices = mesh->vertex_position.indices;
}
ufbxi_patch_zero(mesh->num_vertices, mesh->vertices.count);
ufbxi_patch_zero(mesh->num_indices, mesh->vertex_indices.count);
ufbxi_patch_zero(mesh->num_faces, mesh->faces.count);
if (mesh->num_triangles == 0 || mesh->max_face_triangles == 0) {
size_t num_triangles = 0;
size_t max_face_triangles = 0;
size_t num_bad_faces[3] = { 0 };
ufbxi_nounroll ufbxi_for_list(ufbx_face, face, mesh->faces) {
if (face->num_indices >= 3) {
size_t tris = face->num_indices - 2;
num_triangles += tris;
max_face_triangles = ufbxi_max_sz(max_face_triangles, tris);
} else {
num_bad_faces[face->num_indices]++;
}
}
ufbxi_patch_zero(mesh->num_triangles, num_triangles);
ufbxi_patch_zero(mesh->max_face_triangles, max_face_triangles);
ufbxi_patch_zero(mesh->num_empty_faces, num_bad_faces[0]);
ufbxi_patch_zero(mesh->num_point_faces, num_bad_faces[1]);
ufbxi_patch_zero(mesh->num_line_faces, num_bad_faces[2]);
}
if (!mesh->skinned_position.exists) {
mesh->skinned_is_local = true;
mesh->skinned_position = mesh->vertex_position;
mesh->skinned_normal = mesh->vertex_normal;
}
if (mesh->vertex_first_index.count == 0) {
mesh->vertex_first_index.count = mesh->num_vertices;
mesh->vertex_first_index.data = ufbxi_push(buf, uint32_t, mesh->num_vertices);
ufbxi_check_err(error, mesh->vertex_first_index.data);
ufbxi_update_vertex_first_index(mesh);
}
if (mesh->uv_sets.count == 0 && mesh->vertex_uv.exists) {
ufbx_uv_set *uv_set = ufbxi_push_zero(buf, ufbx_uv_set, 1);
ufbxi_check_err(error, uv_set);
uv_set->name.data = ufbxi_empty_char;
uv_set->vertex_uv = mesh->vertex_uv;
uv_set->vertex_tangent = mesh->vertex_tangent;
uv_set->vertex_bitangent = mesh->vertex_bitangent;
mesh->uv_sets.data = uv_set;
mesh->uv_sets.count = 1;
}
if (mesh->color_sets.count == 0 && mesh->vertex_color.exists) {
ufbx_color_set *color_set = ufbxi_push_zero(buf, ufbx_color_set, 1);
ufbxi_check_err(error, color_set);
color_set->name.data = ufbxi_empty_char;
color_set->vertex_color = mesh->vertex_color;
mesh->color_sets.data = color_set;
mesh->color_sets.count = 1;
}
ufbxi_patch_mesh_reals(mesh);
return 1;
}
// -- .obj file
#if UFBXI_FEATURE_FORMAT_OBJ
static const uint8_t ufbxi_obj_attrib_stride[] = {
3, 2, 3, 4,
};
ufbx_static_assert(obj_attrib_strides, ufbxi_arraycount(ufbxi_obj_attrib_stride) == UFBXI_OBJ_NUM_ATTRIBS_EXT);
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_props(ufbxi_context *uc, ufbx_prop_list *dst, size_t count)
{
ufbx_prop_list props; // ufbxi_uninit
props.count = count;
props.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_props, ufbx_prop, count);
ufbxi_check(props.data);
ufbxi_for_list(ufbx_prop, prop, props) {
prop->_internal_key = ufbxi_get_name_key(prop->name.data, prop->name.length);
if (prop->value_str.length == 0) {
prop->value_str.data = ufbxi_empty_char;
}
if (!prop->value_int) {
prop->value_int = ufbxi_f64_to_i64(prop->value_real);
}
if (prop->value_blob.size == 0 && prop->value_str.length > 0) {
prop->value_blob.data = prop->value_str.data;
prop->value_blob.size = prop->value_str.length;
}
}
if (props.count > 1) {
ufbxi_check(ufbxi_sort_properties(uc, props.data, props.count));
ufbxi_deduplicate_properties(&props);
}
*dst = props;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_push_mesh(ufbxi_context *uc)
{
ufbxi_obj_mesh *mesh = ufbxi_push_zero(&uc->obj.tmp_meshes, ufbxi_obj_mesh, 1);
ufbxi_check(mesh);
uc->obj.mesh = mesh;
ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS; i++) {
mesh->vertex_range[i].min_ix = UINT64_MAX;
}
const char *name = "";
if (uc->opts.obj_split_groups && uc->obj.group.length > 0) {
name = uc->obj.group.data;
} else if (!uc->opts.obj_merge_objects && uc->obj.object.length > 0) {
name = uc->obj.object.data;
} else if (!uc->opts.obj_merge_groups && uc->obj.group.length > 0) {
name = uc->obj.group.data;
}
mesh->fbx_node = ufbxi_push_synthetic_element(uc, &mesh->fbx_node_id, NULL, name, ufbx_node, UFBX_ELEMENT_NODE);
mesh->fbx_mesh = ufbxi_push_synthetic_element(uc, &mesh->fbx_mesh_id, NULL, name, ufbx_mesh, UFBX_ELEMENT_MESH);
ufbxi_check(mesh->fbx_node && mesh->fbx_mesh);
mesh->fbx_mesh->vertex_position.unique_per_vertex = true;
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &mesh->fbx_node->element_id));
uc->obj.face_material = UFBX_NO_INDEX;
uc->obj.face_group = 0;
uc->obj.face_group_dirty = true;
uc->obj.material_dirty = true;
ufbxi_check(ufbxi_connect_oo(uc, mesh->fbx_mesh_id, mesh->fbx_node_id));
ufbxi_check(ufbxi_connect_oo(uc, mesh->fbx_node_id, 0));
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_flush_mesh(ufbxi_context *uc)
{
if (!uc->obj.mesh) return 1;
size_t num_props = uc->obj.tmp_props.num_items;
ufbxi_check(ufbxi_obj_pop_props(uc, &uc->obj.mesh->fbx_mesh->props.props, num_props));
size_t num_groups = uc->obj.tmp_face_group_infos.num_items;
ufbx_face_group *groups = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_group_infos, ufbx_face_group, num_groups);
ufbxi_check(groups);
uc->obj.mesh->fbx_mesh->face_groups.data = groups;
uc->obj.mesh->fbx_mesh->face_groups.count = num_groups;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_init(ufbxi_context *uc)
{
uc->from_ascii = true;
uc->obj.initialized = true;
ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS_EXT; i++) {
uc->obj.tmp_vertices[i].ator = &uc->ator_tmp;
uc->obj.tmp_indices[i].ator = &uc->ator_tmp;
}
uc->obj.tmp_color_valid.ator = &uc->ator_tmp;
uc->obj.tmp_faces.ator = &uc->ator_tmp;
uc->obj.tmp_face_material.ator = &uc->ator_tmp;
uc->obj.tmp_face_smoothing.ator = &uc->ator_tmp;
uc->obj.tmp_face_group.ator = &uc->ator_tmp;
uc->obj.tmp_face_group_infos.ator = &uc->ator_tmp;
uc->obj.tmp_meshes.ator = &uc->ator_tmp;
uc->obj.tmp_props.ator = &uc->ator_tmp;
// .obj parsing does its own yield logic
uc->data_size += uc->yield_size;
uc->obj.object.data = ufbxi_empty_char;
uc->obj.group.data = ufbxi_empty_char;
ufbxi_map_init(&uc->obj.group_map, &uc->ator_tmp, ufbxi_map_cmp_const_char_ptr, NULL);
// Add a nameless root node with the root ID
{
ufbxi_element_info root_info = { uc->root_id };
root_info.name = ufbx_empty_string;
ufbx_node *root = ufbxi_push_element(uc, &root_info, ufbx_node, UFBX_ELEMENT_NODE);
ufbxi_check(root);
ufbxi_setup_root_node(uc, root);
ufbxi_check(ufbxi_push_copy(&uc->tmp_node_ids, uint32_t, 1, &root->element.element_id));
}
return 1;
}
static ufbxi_noinline void ufbxi_obj_free(ufbxi_context *uc)
{
if (!uc->obj.initialized) return;
ufbxi_nounroll for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS_EXT; i++) {
ufbxi_buf_free(&uc->obj.tmp_vertices[i]);
ufbxi_buf_free(&uc->obj.tmp_indices[i]);
}
ufbxi_buf_free(&uc->obj.tmp_color_valid);
ufbxi_buf_free(&uc->obj.tmp_faces);
ufbxi_buf_free(&uc->obj.tmp_face_material);
ufbxi_buf_free(&uc->obj.tmp_face_smoothing);
ufbxi_buf_free(&uc->obj.tmp_face_group);
ufbxi_buf_free(&uc->obj.tmp_face_group_infos);
ufbxi_buf_free(&uc->obj.tmp_meshes);
ufbxi_buf_free(&uc->obj.tmp_props);
ufbxi_map_free(&uc->obj.group_map);
ufbxi_free(&uc->ator_tmp, ufbx_string, uc->obj.tokens, uc->obj.tokens_cap);
ufbxi_free(&uc->ator_tmp, ufbx_material*, uc->obj.tmp_materials, uc->obj.tmp_materials_cap);
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_read_line(ufbxi_context *uc)
{
ufbxi_dev_assert(!uc->obj.eof);
size_t offset = 0;
for (;;) {
const char *begin = ufbxi_add_ptr(uc->data, offset);
const char *end = begin ? (const char*)memchr(begin, '\n', uc->data_size - offset) : NULL;
if (!end) {
if (uc->eof) {
offset = uc->data_size;
uc->obj.eof = true;
break;
} else {
size_t new_cap = ufbxi_max_sz(1, uc->data_size * 2);
ufbxi_check(ufbxi_refill(uc, new_cap, false));
continue;
}
}
offset += ufbxi_to_size(end - begin) + 1;
// Handle line continuations
const char *esc = end;
if (esc > begin && esc[-1] == '\r') esc--;
if (esc > begin && esc[-1] == '\\') {
continue;
}
break;
}
size_t line_len = offset;
uc->obj.line.data = uc->data;
uc->obj.line.length = line_len;
uc->data += line_len;
uc->data_size -= line_len;
uc->obj.read_progress += line_len;
if (uc->obj.read_progress >= uc->progress_interval) {
ufbxi_check(ufbxi_report_progress(uc));
uc->obj.read_progress %= uc->progress_interval;
}
if (uc->obj.eof) {
char *new_data = ufbxi_push(&uc->tmp, char, line_len + 1);
ufbxi_check(new_data);
memcpy(new_data, uc->obj.line.data, line_len);
new_data[line_len] = '\n';
uc->obj.line.data = new_data;
uc->obj.line.length++;
}
return 1;
}
static ufbxi_noinline ufbx_string ufbxi_obj_span_token(ufbxi_context *uc, size_t start_token, size_t end_token)
{
ufbx_assert(start_token < uc->obj.num_tokens);
end_token = ufbxi_min_sz(end_token, uc->obj.num_tokens - 1);
ufbx_assert(start_token <= end_token);
ufbx_string start = uc->obj.tokens[start_token];
ufbx_string end = uc->obj.tokens[end_token];
size_t num_between = ufbxi_to_size(end.data - start.data);
ufbx_string result;
result.data = start.data;
result.length = num_between + end.length;
return result;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_tokenize(ufbxi_context *uc)
{
const char *ptr = uc->obj.line.data, *end = ptr + uc->obj.line.length;
uc->obj.num_tokens = 0;
for (;;) {
char c;
// Skip whitespace
for (;;) {
c = *ptr;
if (c == ' ' || c == '\t' || c == '\r') {
ptr++;
continue;
}
// Treat line continuations as whitespace
if (c == '\\') {
const char *p = ptr + 1;
if (*p == '\r') p++;
if (*p == '\n' && p < end - 1) {
ptr = p + 1;
continue;
}
}
break;
}
c = *ptr;
if (c == '\n') break;
if (c == '#' && uc->obj.num_tokens > 0) break;
size_t index = uc->obj.num_tokens++;
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->obj.tokens, &uc->obj.tokens_cap, index + 1));
ufbx_string *tok = &uc->obj.tokens[index];
tok->data = ptr;
// Treat comment start as a single token
if (c == '#') {
ptr++;
tok->length = 1;
continue;
}
for (;;) {
c = *++ptr;
if (ufbxi_is_space(c)) {
break;
}
if (c == '\\') {
const char *p = ptr + 1;
if (*p == '\r') p++;
if (*p == '\n' && p < end - 1) {
break;
}
}
}
tok->length = ufbxi_to_size(ptr - tok->data);
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_tokenize_line(ufbxi_context *uc)
{
ufbxi_check(ufbxi_obj_read_line(uc));
ufbxi_check(ufbxi_obj_tokenize(uc));
return 1;
}
static ufbxi_noinline int ufbxi_obj_parse_vertex(ufbxi_context *uc, ufbxi_obj_attrib attrib, size_t offset)
{
if (uc->opts.ignore_geometry) return 1;
ufbxi_buf *dst = &uc->obj.tmp_vertices[attrib];
size_t num_values = ufbxi_obj_attrib_stride[attrib];
uc->obj.vertex_count[attrib]++;
size_t read_values = num_values;
if (attrib == UFBXI_OBJ_ATTRIB_COLOR) {
if (offset + read_values > uc->obj.num_tokens) {
read_values = 3;
}
}
ufbxi_check(offset + read_values <= uc->obj.num_tokens);
uint32_t parse_flags = uc->double_parse_flags;
ufbx_real *vals = ufbxi_push_fast(dst, ufbx_real, num_values);
ufbxi_check(vals);
for (size_t i = 0; i < read_values; i++) {
ufbx_string str = uc->obj.tokens[offset + i];
char *end; // ufbxi_uninit
double val = ufbxi_parse_double(str.data, str.length, &end, parse_flags);
ufbxi_check(end == str.data + str.length);
vals[i] = (ufbx_real)val;
}
if (read_values < num_values) {
ufbx_assert(read_values + 1 == num_values);
ufbx_assert(attrib == UFBXI_OBJ_ATTRIB_COLOR);
vals[read_values] = 1.0f;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_index(ufbxi_context *uc, ufbx_string *s, uint32_t attrib)
{
const char *ptr = s->data, *end = ptr + s->length;
bool negative = false;
if (*ptr == '-') {
negative = true;
ptr++;
}
// As .obj indices are never zero we can detect missing indices
// by simply not writing to it.
uint64_t index = 0;
for (; ptr != end; ptr++) {
char c = *ptr;
if (c >= '0' && c <= '9') {
ufbxi_check(index < UINT64_MAX / 10 - 10);
index = index * 10 + (uint64_t)(c - '0');
} else if (c == '/') {
ptr++;
break;
}
}
if (negative) {
size_t count = uc->obj.vertex_count[attrib];
index = index <= count ? count - index : UINT64_MAX;
} else {
// Corrects to zero based indices and wraps 0 to UINT64_MAX (missing)
index -= 1;
}
ufbxi_obj_fast_indices *fast_indices = &uc->obj.fast_indices[attrib];
if (fast_indices->num_left == 0) {
size_t num_push = 128;
uint64_t *dst = ufbxi_push(&uc->obj.tmp_indices[attrib], uint64_t, num_push);
ufbxi_check(dst);
uc->obj.fast_indices[attrib].indices = dst;
uc->obj.fast_indices[attrib].num_left = num_push;
}
*fast_indices->indices++ = index;
fast_indices->num_left--;
ufbxi_obj_mesh *mesh = uc->obj.mesh;
if (index != UINT64_MAX) {
ufbxi_obj_index_range *range = &mesh->vertex_range[attrib];
range->min_ix = ufbxi_min64(range->min_ix, index);
range->max_ix = ufbxi_max64(range->max_ix, index);
}
s->data = ptr;
s->length = ufbxi_to_size(end - ptr);
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_indices(ufbxi_context *uc, size_t token_begin, size_t num_tokens)
{
bool flush_mesh = false;
if (uc->obj.object_dirty) {
if (!uc->opts.obj_merge_objects) {
flush_mesh = true;
}
uc->obj.object_dirty = false;
}
if (uc->obj.group_dirty) {
if (((uc->obj.object.length == 0 || uc->opts.obj_merge_objects) && !uc->opts.obj_merge_groups) || uc->opts.obj_split_groups) {
flush_mesh = true;
}
uc->obj.group_dirty = false;
uc->obj.face_group_dirty = true;
}
if (!uc->obj.mesh || flush_mesh) {
ufbxi_check(ufbxi_obj_flush_mesh(uc));
ufbxi_check(ufbxi_obj_push_mesh(uc));
}
ufbxi_obj_mesh *mesh = uc->obj.mesh;
if (uc->obj.material_dirty) {
if (uc->obj.usemtl_fbx_id != 0) {
ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, uc->obj.usemtl_fbx_id);
ufbx_assert(entry);
if (mesh->usemtl_base == 0 || entry->user_id < mesh->usemtl_base) {
ufbxi_check(ufbxi_connect_oo(uc, uc->obj.usemtl_fbx_id, mesh->fbx_node_id));
uint32_t index = ++uc->obj.usemtl_index;
ufbxi_check(index < UINT32_MAX);
entry->user_id = index;
if (mesh->usemtl_base == 0) {
mesh->usemtl_base = index;
}
uc->obj.face_material = index - mesh->usemtl_base;
}
uc->obj.face_material = entry->user_id - mesh->usemtl_base;
}
}
// EARLY RETURN: Rest of the function should only be related to geometry!
if (uc->opts.ignore_geometry) return 1;
if (num_tokens == 0 && !uc->opts.allow_empty_faces) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_EMPTY_FACE_REMOVED, "Empty face has been removed"));
return 1;
}
if (uc->obj.face_group_dirty) {
ufbx_string name = ufbx_empty_string;
if (uc->obj.group.length > 0 && (uc->obj.object.length > 0 || uc->opts.obj_merge_groups) && !uc->opts.obj_split_groups) {
name = uc->obj.group;
}
uint32_t hash = ufbxi_hash_ptr(name.data);
ufbxi_obj_group_entry *entry = ufbxi_map_find(&uc->obj.group_map, ufbxi_obj_group_entry, hash, &name.data);
if (!entry) {
entry = ufbxi_map_insert(&uc->obj.group_map, ufbxi_obj_group_entry, hash, &name.data);
ufbxi_check(entry);
entry->name = name.data;
entry->mesh_id = 0;
entry->local_id = 0;
}
uint32_t mesh_id = mesh->fbx_mesh->element_id;
if (entry->mesh_id != mesh_id) {
uint32_t id = mesh->num_groups++;
entry->mesh_id = mesh_id;
entry->local_id = id;
ufbx_face_group *group = ufbxi_push_zero(&uc->obj.tmp_face_group_infos, ufbx_face_group, 1);
ufbxi_check(group);
group->id = 0;
group->name = name;
}
uc->obj.face_group = entry->local_id;
if (!uc->obj.has_face_group) {
uc->obj.has_face_group = true;
ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_face_group, uint32_t, uc->obj.tmp_faces.num_items));
}
uc->obj.face_group_dirty = false;
}
size_t num_indices = num_tokens;
ufbxi_check(UINT32_MAX - mesh->num_indices >= num_indices);
ufbx_face *face = ufbxi_push_fast(&uc->obj.tmp_faces, ufbx_face, 1);
ufbxi_check(face);
face->index_begin = (uint32_t)mesh->num_indices;
face->num_indices = (uint32_t)num_indices;
mesh->num_faces++;
mesh->num_indices += num_indices;
uint32_t *p_face_mat = ufbxi_push_fast(&uc->obj.tmp_face_material, uint32_t, 1);
ufbxi_check(p_face_mat);
*p_face_mat = uc->obj.face_material;
if (uc->obj.has_face_smoothing) {
bool *p_face_smooth = ufbxi_push_fast(&uc->obj.tmp_face_smoothing, bool, 1);
ufbxi_check(p_face_smooth);
*p_face_smooth = uc->obj.face_smoothing;
}
if (uc->obj.has_face_group) {
uint32_t *p_face_group = ufbxi_push_fast(&uc->obj.tmp_face_group, uint32_t, 1);
ufbxi_check(p_face_group);
*p_face_group = uc->obj.face_group;
}
for (size_t ix = 0; ix < num_indices; ix++) {
ufbx_string tok = uc->obj.tokens[token_begin + ix];
for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) {
ufbxi_check(ufbxi_obj_parse_index(uc, &tok, attrib));
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_multi_indices(ufbxi_context *uc, size_t window)
{
for (size_t begin = 1; begin + window <= uc->obj.num_tokens; begin++) {
ufbxi_check(ufbxi_obj_parse_indices(uc, begin, window));
}
return 1;
}
static ufbxi_noinline uint32_t ufbxi_parse_hex(const char *digits, size_t length)
{
uint32_t value = 0;
for (size_t i = 0; i < length; i++) {
char c = digits[i];
uint32_t v = 0;
if (c >= '0' && c <= '9') {
v = (uint32_t)(c - '0');
} else if (c >= 'A' && c <= 'F') {
v = (uint32_t)(c - 'A') + 10;
} else if (c >= 'a' && c <= 'f') {
v = (uint32_t)(c - 'a') + 10;
}
value = (value << 4) | v;
}
return value;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_comment(ufbxi_context *uc)
{
if (uc->obj.num_tokens >= 3 && ufbxi_str_equal(uc->obj.tokens[1], ufbxi_str_c("MRGB"))) {
size_t num_color = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR];
// Pop standard vertex colors and replace them with MRGB colors
if (num_color > uc->obj.mrgb_vertex_count) {
size_t num_pop = num_color - uc->obj.mrgb_vertex_count;
ufbxi_pop(&uc->obj.tmp_color_valid, bool, num_pop, NULL);
ufbxi_pop(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, num_pop * 4, NULL);
uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] -= num_pop;
}
ufbx_string mrgb = uc->obj.tokens[2];
for (size_t i = 0; i + 8 <= mrgb.length; i += 8) {
ufbx_real *p_rgba = ufbxi_push(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, 4);
bool *p_valid = ufbxi_push(&uc->obj.tmp_color_valid, bool, 1);
ufbxi_check(p_rgba && p_valid);
*p_valid = true;
uint32_t hex = ufbxi_parse_hex(mrgb.data + i, 8);
p_rgba[0] = (ufbx_real)((hex >> 16u) & 0xff) / 255.0f;
p_rgba[1] = (ufbx_real)((hex >> 8u) & 0xff) / 255.0f;
p_rgba[2] = (ufbx_real)((hex >> 0u) & 0xff) / 255.0f;
p_rgba[3] = (ufbx_real)((hex >> 24u) & 0xff) / 255.0f;
}
uc->obj.has_vertex_color = true;
}
if (!uc->opts.disable_quirks) {
if (ufbxi_match(&uc->obj.line, "\\s*#\\s*File exported by ZBrush.*")) {
if (!uc->obj.mesh) {
uc->opts.obj_merge_groups = true;
}
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_material(ufbxi_context *uc)
{
ufbxi_check(uc->obj.num_tokens >= 2);
ufbx_string name = ufbxi_obj_span_token(uc, 1, SIZE_MAX);
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &name, false));
uint64_t fbx_id = ufbxi_synthetic_id_from_string(name.data);
ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, fbx_id);
uc->obj.usemtl_fbx_id = fbx_id;
uc->obj.usemtl_name = name;
if (!entry) {
ufbxi_element_info info = { 0 };
info.fbx_id = fbx_id;
info.name = name;
ufbx_material *material = ufbxi_push_element(uc, &info, ufbx_material, UFBX_ELEMENT_MATERIAL);
ufbxi_check(material);
material->shader_type = UFBX_SHADER_WAVEFRONT_MTL;
material->shading_model_name.data = ufbxi_empty_char;
material->shader_prop_prefix.data = ufbxi_empty_char;
size_t id = material->element_id;
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->obj.tmp_materials, &uc->obj.tmp_materials_cap, id + 1));
uc->obj.tmp_materials[id] = material;
}
uc->obj.material_dirty = true;
return 1;
}
#define ufbxi_obj_cmd1(a) ((uint32_t)(a)<<24u)
#define ufbxi_obj_cmd2(a,b) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16)
#define ufbxi_obj_cmd3(a,b,c) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16 | (uint32_t)(c)<<8u)
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_vertices(ufbxi_context *uc, ufbx_real_list *dst, uint32_t attrib, uint64_t min_index)
{
size_t stride = ufbxi_obj_attrib_stride[attrib];
ufbxi_check(min_index < uc->obj.tmp_vertices[attrib].num_items / stride);
size_t count = uc->obj.tmp_vertices[attrib].num_items - (size_t)min_index * stride;
ufbx_real *data = ufbxi_push(&uc->result, ufbx_real, count + 4);
ufbxi_check(data);
data[0] = 0.0f;
data[1] = 0.0f;
data[2] = 0.0f;
data[3] = 0.0f;
data += 4;
ufbxi_pop(&uc->obj.tmp_vertices[attrib], ufbx_real, count, data);
dst->data = data;
dst->count = count;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_setup_attrib(ufbxi_context *uc, ufbxi_obj_mesh *mesh, uint64_t *tmp_indices,
ufbx_vertex_attrib *dst, const ufbx_real_list *p_data, uint32_t attrib, bool non_disjoint, bool required)
{
ufbx_real_list data = *p_data;
size_t num_indices = mesh->num_indices;
size_t stride = ufbxi_obj_attrib_stride[attrib];
size_t num_values = data.count / stride;
uint64_t mesh_min_ix = mesh->vertex_range[attrib].min_ix;
if (num_indices == 0 || num_values == 0 || mesh_min_ix == UINT64_MAX) {
ufbxi_check(num_indices == 0 || !required);
// Pop indices without copying if the attribute is not used
ufbxi_pop(&uc->obj.tmp_indices[attrib], uint64_t, num_indices, NULL);
return 1;
}
uint64_t min_index = non_disjoint ? 0 : mesh_min_ix;
ufbxi_pop(&uc->obj.tmp_indices[attrib], uint64_t, num_indices, tmp_indices);
uint32_t *dst_indices = ufbxi_push(&uc->result, uint32_t, num_indices);
ufbxi_check(dst_indices);
dst->exists = true;
dst->values.data = data.data;
dst->values.count = num_values;
dst->indices.data = dst_indices;
dst->indices.count = num_indices;
ufbxi_nounroll for (size_t i = 0; i < num_indices; i++) {
uint64_t ix = tmp_indices[i];
if (ix != UINT64_MAX) {
ix -= min_index;
ufbxi_check(ix < UINT32_MAX);
}
if (ix < num_values) {
dst_indices[i] = (uint32_t)ix;
} else {
ufbxi_check(ufbxi_fix_index(uc, &dst_indices[i], (uint32_t)ix, num_values));
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pad_colors(ufbxi_context *uc, size_t num_vertices)
{
if (uc->opts.ignore_geometry) return 1;
size_t num_colors = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR];
if (num_vertices > num_colors) {
size_t num_pad = num_vertices - num_colors;
ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_vertices[UFBXI_OBJ_ATTRIB_COLOR], ufbx_real, num_pad * 4));
ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_color_valid, bool, num_pad));
uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] += num_pad;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_meshes(ufbxi_context *uc)
{
size_t num_meshes = uc->obj.tmp_meshes.num_items;
ufbxi_obj_mesh *meshes = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_meshes, ufbxi_obj_mesh, num_meshes);
ufbxi_check(meshes);
if (uc->obj.has_vertex_color) {
ufbxi_check(ufbxi_obj_pad_colors(uc, uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_POSITION]));
}
// Pop unused fast indices
for (size_t i = 0; i < UFBXI_OBJ_NUM_ATTRIBS; i++) {
ufbxi_pop(&uc->obj.tmp_indices[i], uint64_t, uc->obj.fast_indices[i].num_left, NULL);
}
// Check if the file has disjoint vertices
bool non_disjoint[UFBXI_OBJ_NUM_ATTRIBS] = { 0 };
uint64_t next_min[UFBXI_OBJ_NUM_ATTRIBS] = { 0 };
ufbx_real_list vertices[UFBXI_OBJ_NUM_ATTRIBS_EXT] = { 0 };
bool *color_valid = NULL;
size_t max_indices = 0;
for (size_t i = 0; i < num_meshes; i++) {
ufbxi_obj_mesh *mesh = &meshes[i];
max_indices = ufbxi_max_sz(max_indices, mesh->num_indices);
ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) {
ufbxi_obj_index_range range = mesh->vertex_range[attrib];
if (range.min_ix > range.max_ix) continue;
if (range.min_ix < next_min[attrib]) {
non_disjoint[attrib] = true;
}
next_min[attrib] = range.max_ix + 1;
}
}
uint64_t *tmp_indices = ufbxi_push(&uc->tmp, uint64_t, max_indices);
ufbxi_check(tmp_indices);
ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) {
if (!non_disjoint[attrib]) continue;
ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[attrib], attrib, 0));
}
if (uc->obj.has_vertex_color && non_disjoint[UFBXI_OBJ_ATTRIB_POSITION]) {
ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[UFBXI_OBJ_ATTRIB_COLOR], UFBXI_OBJ_ATTRIB_COLOR, 0));
color_valid = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_color_valid, bool, vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4);
ufbxi_check(color_valid);
}
for (size_t i = num_meshes; i > 0; i--) {
ufbxi_obj_mesh *mesh = &meshes[i - 1];
ufbx_mesh *fbx_mesh = mesh->fbx_mesh;
size_t num_faces = mesh->num_faces;
if (!uc->opts.ignore_geometry) {
ufbxi_nounroll for (uint32_t attrib = 0; attrib < UFBXI_OBJ_NUM_ATTRIBS; attrib++) {
if (non_disjoint[attrib]) continue;
uint64_t min_ix = mesh->vertex_range[attrib].min_ix;
if (min_ix < UINT64_MAX) {
ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[attrib], attrib, min_ix));
}
}
if (uc->obj.has_vertex_color && !non_disjoint[UFBXI_OBJ_ATTRIB_POSITION]) {
uint64_t min_ix = mesh->vertex_range[UFBXI_OBJ_ATTRIB_POSITION].min_ix;
ufbxi_check(min_ix < UINT64_MAX);
ufbxi_check(ufbxi_obj_pop_vertices(uc, &vertices[UFBXI_OBJ_ATTRIB_COLOR], UFBXI_OBJ_ATTRIB_COLOR, min_ix));
color_valid = ufbxi_push_pop(&uc->tmp, &uc->obj.tmp_color_valid, bool, vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4);
ufbxi_check(color_valid);
}
fbx_mesh->faces.count = num_faces;
fbx_mesh->face_material.count = num_faces;
fbx_mesh->faces.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_faces, ufbx_face, num_faces);
fbx_mesh->face_material.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_material, uint32_t, num_faces);
ufbxi_check(fbx_mesh->faces.data);
ufbxi_check(fbx_mesh->face_material.data);
if (uc->obj.has_face_smoothing) {
fbx_mesh->face_smoothing.count = num_faces;
fbx_mesh->face_smoothing.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_smoothing, bool, num_faces);
ufbxi_check(fbx_mesh->face_smoothing.data);
}
if (uc->obj.has_face_group) {
if (mesh->num_groups > 1) {
fbx_mesh->face_group.count = num_faces;
fbx_mesh->face_group.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_face_group, uint32_t, num_faces);
ufbxi_check(fbx_mesh->face_group.data);
} else {
ufbxi_pop(&uc->obj.tmp_face_group, uint32_t, num_faces, NULL);
}
}
ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_position,
&vertices[UFBXI_OBJ_ATTRIB_POSITION], UFBXI_OBJ_ATTRIB_POSITION, non_disjoint[UFBXI_OBJ_ATTRIB_POSITION], true));
ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_uv,
&vertices[UFBXI_OBJ_ATTRIB_UV], UFBXI_OBJ_ATTRIB_UV, non_disjoint[UFBXI_OBJ_ATTRIB_UV], false));
ufbxi_check(ufbxi_obj_setup_attrib(uc, mesh, tmp_indices, (ufbx_vertex_attrib*)&fbx_mesh->vertex_normal,
&vertices[UFBXI_OBJ_ATTRIB_NORMAL], UFBXI_OBJ_ATTRIB_NORMAL, non_disjoint[UFBXI_OBJ_ATTRIB_NORMAL], false));
if (uc->obj.has_vertex_color) {
ufbx_assert(color_valid);
bool has_color = false;
bool all_valid = true;
size_t max_index = fbx_mesh->vertex_position.values.count;
ufbxi_for_list(uint32_t, p_ix, fbx_mesh->vertex_position.indices) {
if (*p_ix < max_index) {
if (color_valid[*p_ix]) {
has_color = true;
} else {
all_valid = false;
}
}
}
if (has_color) {
fbx_mesh->vertex_color.exists = true;
fbx_mesh->vertex_color.values.data = (ufbx_vec4*)vertices[UFBXI_OBJ_ATTRIB_COLOR].data;
fbx_mesh->vertex_color.values.count = vertices[UFBXI_OBJ_ATTRIB_COLOR].count / 4;
fbx_mesh->vertex_color.indices = fbx_mesh->vertex_position.indices;
fbx_mesh->vertex_color.unique_per_vertex = true;
if (!all_valid) {
uint32_t *indices = fbx_mesh->vertex_color.indices.data;
indices = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, indices);
ufbxi_check(indices);
size_t num_values = fbx_mesh->vertex_color.values.count;
ufbxi_for(uint32_t, p_ix, indices, mesh->num_indices) {
if (*p_ix >= num_values || !color_valid[*p_ix]) {
ufbxi_check(ufbxi_fix_index(uc, p_ix, *p_ix, num_values));
}
}
fbx_mesh->vertex_color.indices.data = indices;
}
}
}
}
ufbxi_check(ufbxi_finalize_mesh(&uc->result, &uc->error, fbx_mesh));
if (uc->retain_mesh_parts) {
fbx_mesh->face_group_parts.count = mesh->num_groups;
fbx_mesh->face_group_parts.data = ufbxi_push_zero(&uc->result, ufbx_mesh_part, mesh->num_groups);
ufbxi_check(fbx_mesh->face_group_parts.data);
}
if (mesh->num_groups > 1) {
ufbxi_check(ufbxi_update_face_groups(&uc->result, &uc->error, fbx_mesh, false));
} else if (mesh->num_groups == 1) {
fbx_mesh->face_group.data = (uint32_t*)ufbxi_sentinel_index_zero;
fbx_mesh->face_group.count = num_faces;
// NOTE: Consecutive and zero indices are always allocated so we can skip doing it here,
// see HACK(consecutiv-faces)..
if (fbx_mesh->face_group_parts.count > 0) {
ufbx_mesh_part *part = &fbx_mesh->face_group_parts.data[0];
part->num_faces = fbx_mesh->num_faces;
part->num_faces = num_faces;
part->num_empty_faces = fbx_mesh->num_empty_faces;
part->num_point_faces = fbx_mesh->num_point_faces;
part->num_line_faces = fbx_mesh->num_line_faces;
part->num_triangles = fbx_mesh->num_triangles;
part->face_indices.data = (uint32_t*)ufbxi_sentinel_index_consecutive;
part->face_indices.count = num_faces;
}
}
// HACK(consecutive-faces): Prepare for finalize to re-use a consecutive/zero
// index buffer for face materials..
uc->max_zero_indices = ufbxi_max_sz(uc->max_zero_indices, num_faces);
uc->max_consecutive_indices = ufbxi_max_sz(uc->max_consecutive_indices, num_faces);
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_file(ufbxi_context *uc)
{
while (!uc->obj.eof) {
ufbxi_check(ufbxi_obj_tokenize_line(uc));
size_t num_tokens = uc->obj.num_tokens;
if (num_tokens == 0) continue;
ufbx_string cmd = uc->obj.tokens[0];
uint32_t key = ufbxi_get_name_key(cmd.data, cmd.length);
if (key == ufbxi_obj_cmd1('v')) {
ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_POSITION, 1));
if (num_tokens >= 7) {
size_t num_vertices = uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_POSITION];
uc->obj.has_vertex_color = true;
ufbxi_check(ufbxi_obj_pad_colors(uc, num_vertices - 1));
if (uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] < num_vertices) {
ufbx_assert(uc->obj.vertex_count[UFBXI_OBJ_ATTRIB_COLOR] == num_vertices - 1);
ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_COLOR, 4));
bool *valid = ufbxi_push(&uc->obj.tmp_color_valid, bool, 1);
ufbxi_check(valid);
*valid = true;
}
}
} else if (key == ufbxi_obj_cmd2('v','t')) {
ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_UV, 1));
} else if (key == ufbxi_obj_cmd2('v','n')) {
ufbxi_check(ufbxi_obj_parse_vertex(uc, UFBXI_OBJ_ATTRIB_NORMAL, 1));
} else if (key == ufbxi_obj_cmd1('f')) {
ufbxi_check(ufbxi_obj_parse_indices(uc, 1, uc->obj.num_tokens - 1));
} else if (key == ufbxi_obj_cmd1('p')) {
ufbxi_check(ufbxi_obj_parse_multi_indices(uc, 1));
} else if (key == ufbxi_obj_cmd1('l')) {
ufbxi_check(ufbxi_obj_parse_multi_indices(uc, 2));
} else if (key == ufbxi_obj_cmd1('s')) {
if (num_tokens >= 2) {
uc->obj.has_face_smoothing = true;
uc->obj.face_smoothing = !ufbxi_str_equal(uc->obj.tokens[1], ufbxi_str_c("off"));
// Fill in previously missed face smoothing data
if (uc->obj.tmp_face_smoothing.num_items == 0 && uc->obj.tmp_faces.num_items > 0) {
ufbxi_check(ufbxi_push_zero(&uc->obj.tmp_face_smoothing, bool, uc->obj.tmp_faces.num_items));
}
}
} else if (key == ufbxi_obj_cmd1('o')) {
if (num_tokens >= 2) {
uc->obj.object = ufbxi_obj_span_token(uc, 1, SIZE_MAX);
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->obj.object, false));
uc->obj.object_dirty = true;
}
} else if (key == ufbxi_obj_cmd1('g')) {
if (num_tokens >= 2) {
uc->obj.group = ufbxi_obj_span_token(uc, 1, SIZE_MAX);
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &uc->obj.group, false));
uc->obj.group_dirty = true;
} else {
uc->obj.group = ufbx_empty_string;
uc->obj.group_dirty = true;
}
} else if (key == ufbxi_obj_cmd1('#')) {
ufbxi_check(ufbxi_obj_parse_comment(uc));
} else if (ufbxi_str_equal(cmd, ufbxi_str_c("mtllib"))) {
ufbxi_check(uc->obj.num_tokens >= 2);
ufbx_string lib = ufbxi_obj_span_token(uc, 1, SIZE_MAX);
lib.data = ufbxi_push_copy(&uc->tmp, char, lib.length + 1, lib.data);
ufbxi_check(lib.data);
uc->obj.mtllib_relative_path.data = lib.data;
uc->obj.mtllib_relative_path.size = lib.length;
} else if (ufbxi_str_equal(cmd, ufbxi_str_c("usemtl"))) {
ufbxi_check(ufbxi_obj_parse_material(uc));
} else {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, "Unknown .obj directive, skipped line"));
}
}
ufbxi_check(ufbxi_obj_flush_mesh(uc));
ufbxi_check(ufbxi_obj_pop_meshes(uc));
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_flush_material(ufbxi_context *uc)
{
if (uc->obj.usemtl_fbx_id == 0) return 1;
ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, uc->obj.usemtl_fbx_id);
ufbx_assert(entry);
ufbx_material *material = uc->obj.tmp_materials[entry->element_id];
size_t num_props = uc->obj.tmp_props.num_items;
ufbxi_check(ufbxi_obj_pop_props(uc, &material->props.props, num_props));
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_prop(ufbxi_context *uc, ufbx_string name, size_t start, bool include_rest, size_t *p_next)
{
if (start >= uc->obj.num_tokens) {
if (p_next) {
*p_next = start;
}
return 1;
}
ufbx_prop *prop = ufbxi_push_zero(&uc->obj.tmp_props, ufbx_prop, 1);
ufbxi_check(prop);
prop->name = name;
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop->name, false));
uint32_t flags = UFBX_PROP_FLAG_VALUE_STR;
size_t num_reals = 0;
for (; num_reals < 4; num_reals++) {
if (start + num_reals >= uc->obj.num_tokens) break;
ufbx_string tok = uc->obj.tokens[start + num_reals];
char *end; // ufbxi_uninit
double val = ufbxi_parse_double(tok.data, tok.length, &end, uc->double_parse_flags);
if (end != tok.data + tok.length) break;
prop->value_real_arr[num_reals] = (ufbx_real)val;
if (num_reals == 0) {
prop->value_int = ufbxi_f64_to_i64(val);
flags |= UFBX_PROP_FLAG_VALUE_INT;
}
}
size_t num_args = 0;
if (!include_rest) {
for (; start + num_args < uc->obj.num_tokens - 1; num_args++) {
if (ufbxi_match(&uc->obj.tokens[start + num_args], "-[A-Za-z][\\-A-Za-z0-9_]*")) break;
}
}
if (num_args > 0 || include_rest) {
ufbx_string span = ufbxi_obj_span_token(uc, start, include_rest ? SIZE_MAX : start + num_args - 1);
prop->value_str = span;
prop->value_blob.data = span.data;
prop->value_blob.size = span.length;
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop->value_str, false));
ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &prop->value_blob, true));
} else {
prop->value_str.data = ufbxi_empty_char;
}
if (num_reals > 0) {
flags = (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (num_reals - 1);
} else {
if (!strcmp(prop->value_str.data, "on")) {
prop->value_int = 1;
prop->value_real = 1.0f;
flags |= UFBX_PROP_FLAG_VALUE_INT;
} else if (!strcmp(prop->value_str.data, "off")) {
prop->value_int = 0;
prop->value_real = 0.0f;
flags |= UFBX_PROP_FLAG_VALUE_INT;
}
}
prop->flags = (ufbx_prop_flags)flags;
if (p_next) {
*p_next = start + num_args;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_mtl_map(ufbxi_context *uc, size_t prefix_len)
{
if (uc->obj.num_tokens < 2) return 1;
size_t num_props = 1;
ufbxi_check(ufbxi_obj_parse_prop(uc, ufbxi_str_c("obj|args"), 1, true, NULL));
size_t start = 1;
for (; start + 1 < uc->obj.num_tokens; ) {
ufbx_string tok = uc->obj.tokens[start];
if (ufbxi_match(&tok, "-[A-Za-z][\\-A-Za-z0-9_]*")) {
tok.data += 1;
tok.length -= 1;
ufbxi_check(ufbxi_obj_parse_prop(uc, tok, start + 1, false, &start));
num_props++;
} else {
break;
}
}
ufbx_string tex_str = ufbxi_obj_span_token(uc, start, SIZE_MAX);
ufbx_blob tex_raw = { tex_str.data, tex_str.length };
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &tex_str, false));
ufbxi_check(ufbxi_push_string_place_blob(&uc->string_pool, &tex_raw, true));
uint64_t fbx_id = 0;
ufbx_texture *texture = ufbxi_push_synthetic_element(uc, &fbx_id, NULL, "", ufbx_texture, UFBX_ELEMENT_TEXTURE);
ufbxi_check(texture);
texture->filename.data = ufbxi_empty_char;
texture->absolute_filename.data = ufbxi_empty_char;
texture->uv_set.data = ufbxi_empty_char;
texture->relative_filename = tex_str;
texture->raw_relative_filename = tex_raw;
ufbxi_check(ufbxi_obj_pop_props(uc, &texture->props.props, num_props));
ufbx_string prop = uc->obj.tokens[0];
ufbx_assert(prop.length >= prefix_len);
prop.data += prefix_len;
prop.length -= prefix_len;
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prop, false));
if (uc->obj.usemtl_fbx_id != 0) {
ufbxi_check(ufbxi_connect_op(uc, fbx_id, uc->obj.usemtl_fbx_id, prop));
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_mtl(ufbxi_context *uc)
{
uc->obj.mesh = NULL;
uc->obj.usemtl_fbx_id = 0;
while (!uc->obj.eof) {
ufbxi_check(ufbxi_obj_tokenize_line(uc));
size_t num_tokens = uc->obj.num_tokens;
if (num_tokens == 0) continue;
ufbx_string cmd = uc->obj.tokens[0];
if (ufbxi_str_equal(cmd, ufbxi_str_c("newmtl"))) {
// HACK: Reuse mesh material parsing
ufbxi_check(ufbxi_obj_flush_material(uc));
ufbxi_check(ufbxi_obj_parse_material(uc));
} else if (cmd.length > 4 && !memcmp(cmd.data, "map_", 4)) {
ufbxi_check(ufbxi_obj_parse_mtl_map(uc, 4));
} else if (cmd.length == 4 && (!memcmp(cmd.data, "bump", 4) || !memcmp(cmd.data, "disp", 4) || !memcmp(cmd.data, "norm", 4))) {
ufbxi_check(ufbxi_obj_parse_mtl_map(uc, 0));
} else if (cmd.length == 1 && cmd.data[0] == '#') {
// Implement .mtl magic comment handling here if necessary
} else {
ufbxi_check(ufbxi_obj_parse_prop(uc, uc->obj.tokens[0], 1, true, NULL));
}
}
ufbxi_check(ufbxi_obj_flush_material(uc));
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load_mtl(ufbxi_context *uc)
{
// HACK: Reset everything and switch to loading the .mtl file globally
if (uc->close_fn) {
uc->close_fn(uc->read_user);
}
uc->read_fn = NULL;
uc->close_fn = NULL;
uc->read_user = NULL;
uc->data_begin = NULL;
uc->data = NULL;
uc->data_size = 0;
uc->yield_size = 0;
uc->eof = false;
uc->obj.eof = false;
if (uc->opts.obj_mtl_data.size > 0) {
uc->data_begin = uc->data = (const char*)uc->opts.obj_mtl_data.data;
uc->data_size = uc->opts.obj_mtl_data.size;
ufbxi_check(ufbxi_obj_parse_mtl(uc));
return 1;
}
ufbx_stream stream = { 0 };
bool has_stream = false;
bool needs_stream = false;
ufbx_blob stream_path = { 0 };
if (uc->opts.open_file_cb.fn) {
if (uc->opts.obj_mtl_path.length > 0) {
has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, uc->opts.obj_mtl_path.data, uc->opts.obj_mtl_path.length, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL);
stream_path.data = uc->opts.obj_mtl_path.data;
stream_path.size = uc->opts.obj_mtl_path.length;
needs_stream = true;
if (!has_stream) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Could not open .mtl file: %s", uc->opts.obj_mtl_path.data));
}
}
if (!has_stream && uc->opts.load_external_files && uc->obj.mtllib_relative_path.size > 0) {
ufbx_blob dst; // ufbxi_uninit
ufbxi_check(ufbxi_resolve_relative_filename(uc, (ufbxi_strblob*)&dst, (const ufbxi_strblob*)&uc->obj.mtllib_relative_path, true));
has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, (const char*)dst.data, dst.size, &uc->obj.mtllib_relative_path, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL);
stream_path = uc->obj.mtllib_relative_path;
needs_stream = true;
if (!has_stream) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Could not open .mtl file: %s", dst.data));
}
}
ufbx_string path = uc->scene.metadata.filename;
if (!has_stream && uc->opts.load_external_files && uc->opts.obj_search_mtl_by_filename && path.length > 4) {
ufbx_string ext = { path.data + path.length - 4, 4 };
if (ufbxi_match(&ext, "\\c.obj")) {
char *copy = ufbxi_push_copy(&uc->tmp, char, path.length + 1, path.data);
ufbxi_check(copy);
copy[path.length - 3] = copy[path.length - 3] == 'O' ? 'M' : 'm';
copy[path.length - 2] = copy[path.length - 2] == 'B' ? 'T' : 't';
copy[path.length - 1] = copy[path.length - 1] == 'J' ? 'L' : 'l';
has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, copy, path.length, NULL, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL);
if (has_stream) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_IMPLICIT_MTL, "Opened .mtl file derived from .obj filename: %s", copy));
}
}
}
}
if (has_stream) {
// Adopt `stream` to ufbx read callbacks
uc->read_fn = stream.read_fn;
uc->close_fn = stream.close_fn;
uc->read_user = stream.user;
int ok = ufbxi_obj_parse_mtl(uc);
if (uc->close_fn) {
uc->close_fn(uc->read_user);
}
uc->read_fn = NULL;
uc->close_fn = NULL;
uc->read_user = NULL;
ufbxi_check(ok);
} else if (needs_stream && !uc->opts.ignore_missing_external_files) {
ufbxi_set_err_info(&uc->error, (const char*)stream_path.data, stream_path.size);
ufbxi_fail_msg("ufbxi_obj_load_mtl()", "External file not found");
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load(ufbxi_context *uc)
{
ufbxi_check(ufbxi_obj_init(uc));
ufbxi_check(ufbxi_obj_parse_file(uc));
ufbxi_check(ufbxi_init_file_paths(uc));
ufbxi_check(ufbxi_obj_load_mtl(uc));
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_mtl_load(ufbxi_context *uc)
{
ufbxi_check(ufbxi_obj_init(uc));
ufbxi_check(ufbxi_init_file_paths(uc));
ufbxi_check(ufbxi_obj_parse_mtl(uc));
return 1;
}
#else
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_obj_load(ufbxi_context *uc)
{
ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_FORMAT_OBJ");
ufbxi_fail_msg("UFBXI_FEATURE_FORMAT_OBJ", "Feature disabled");
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_mtl_load(ufbxi_context *uc)
{
ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_FORMAT_OBJ");
ufbxi_fail_msg("UFBXI_FEATURE_FORMAT_OBJ", "Feature disabled");
}
static ufbxi_forceinline void ufbxi_obj_free(ufbxi_context *uc)
{
}
#endif
// -- Scene pre-processing
typedef struct {
ufbx_element *src, *dst;
} ufbxi_pre_connection;
typedef struct {
bool has_constant_scale;
bool has_recursive_scale_helper;
bool has_skin_deformer;
ufbx_vec3 constant_scale;
uint32_t element_id;
uint32_t first_child;
uint32_t next_child;
uint32_t parent;
} ufbxi_pre_node;
typedef struct {
bool has_skin_deformer;
} ufbxi_pre_mesh;
typedef struct {
bool has_constant_value;
ufbx_vec3 constant_value;
} ufbxi_pre_anim_value;
// Called between parsing and `ufbxi_finalize_scene()`.
// This is a very messy function reminiscent of the _old_ ufbx, where we do
// multiple passes over connections without having a proper scene graph.
// This, however gives us the advantage of allowing us to modify elements
// and connections. We can, for example, add new helper nodes and redirect
// animated properties from source nodes to the helpers. The rest of ufbx
// will treat these as if they were a part of the source file.
ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context *uc)
{
bool required = false;
if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES || uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) required = true;
if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_HELPER_NODES || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) required = true;
if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) required = true;
#if defined(UFBX_REGRESSION)
required = true;
#endif
if (!required) return 1;
uint32_t num_elements = uc->num_elements;
size_t num_nodes = uc->tmp_node_ids.num_items;
ufbx_element **elements = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_ptrs, ufbx_element*, num_elements);
ufbxi_check(elements);
size_t num_connections = uc->tmp_connections.num_items;
ufbxi_tmp_connection *tmp_connections = ufbxi_push_peek(&uc->tmp_parse, &uc->tmp_connections, ufbxi_tmp_connection, num_connections);
ufbxi_check(tmp_connections);
ufbxi_pre_connection *pre_connections = ufbxi_push(&uc->tmp_parse, ufbxi_pre_connection, num_connections);
ufbxi_check(pre_connections);
uint32_t *instance_counts = ufbxi_push_zero(&uc->tmp_parse, uint32_t, num_elements);
ufbxi_check(instance_counts);
bool *modify_not_supported = ufbxi_push_zero(&uc->tmp_parse, bool, num_elements);
ufbxi_check(modify_not_supported);
bool *has_unscaled_children = ufbxi_push_zero(&uc->tmp_parse, bool, num_nodes);
ufbxi_check(has_unscaled_children);
bool *has_scale_animation = ufbxi_push_zero(&uc->tmp_parse, bool, num_nodes);
ufbxi_check(has_scale_animation);
ufbxi_pre_node *pre_nodes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_node, num_nodes);
ufbxi_check(pre_nodes);
size_t num_meshes = uc->tmp_typed_element_offsets[UFBX_ELEMENT_MESH].num_items;
ufbxi_pre_mesh *pre_meshes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_mesh, num_meshes);
ufbxi_check(pre_meshes);
size_t num_anim_values = uc->tmp_typed_element_offsets[UFBX_ELEMENT_ANIM_VALUE].num_items;
ufbxi_pre_anim_value *pre_anim_values = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_anim_value, num_anim_values);
ufbxi_check(pre_anim_values);
uint64_t *fbx_ids = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_fbx_ids, uint64_t, num_elements);
ufbxi_check(fbx_ids);
// TODO
const ufbx_real scale_epsilon = 0.001f;
const ufbx_real pivot_epsilon = 0.001f;
const ufbx_real compensate_epsilon = 0.01f;
for (size_t i = 0; i < num_elements; i++) {
ufbx_element *element = elements[i];
uint32_t id = element->typed_id;
if (element->type == UFBX_ELEMENT_NODE) {
ufbxi_pre_node *pre_node = &pre_nodes[id];
pre_node->has_constant_scale = true;
pre_node->constant_scale = ufbxi_find_vec3(&element->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f);
pre_node->element_id = element->element_id;
pre_node->first_child = ~0u;
pre_node->next_child = ~0u;
pre_node->parent = ~0u;
} if (element->type == UFBX_ELEMENT_ANIM_VALUE) {
ufbxi_pre_anim_value *pre_value = &pre_anim_values[id];
pre_value->has_constant_value = true;
pre_value->constant_value.x = ufbxi_find_real(&element->props, ufbxi_X, UFBX_NAN);
pre_value->constant_value.x = ufbxi_find_real(&element->props, ufbxi_d_X, pre_value->constant_value.x);
pre_value->constant_value.y = ufbxi_find_real(&element->props, ufbxi_Y, UFBX_NAN);
pre_value->constant_value.y = ufbxi_find_real(&element->props, ufbxi_d_Y, pre_value->constant_value.y);
pre_value->constant_value.z = ufbxi_find_real(&element->props, ufbxi_Z, UFBX_NAN);
pre_value->constant_value.z = ufbxi_find_real(&element->props, ufbxi_d_Z, pre_value->constant_value.z);
}
}
for (size_t i = 0; i < num_connections; i++) {
ufbxi_tmp_connection *tmp = &tmp_connections[i];
ufbxi_pre_connection *pre = &pre_connections[i];
ufbxi_fbx_id_entry *src_entry = ufbxi_find_fbx_id(uc, tmp->src);
ufbxi_fbx_id_entry *dst_entry = ufbxi_find_fbx_id(uc, tmp->dst);
ufbx_element *src = src_entry ? elements[src_entry->element_id] : NULL;
ufbx_element *dst = dst_entry ? elements[dst_entry->element_id] : NULL;
pre->src = src;
pre->dst = dst;
if (!src || !dst) continue;
if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) {
// Count number of instances of each attribute
if (dst->type == UFBX_ELEMENT_NODE) {
ufbx_node *dst_node = (ufbx_node*)dst;
if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) {
++instance_counts[src->element_id];
// These must match what can be trasnsformed in `ufbxi_modify_geometry()`
switch (src->type) {
case UFBX_ELEMENT_MESH:
case UFBX_ELEMENT_LINE_CURVE:
case UFBX_ELEMENT_NURBS_CURVE:
case UFBX_ELEMENT_NURBS_SURFACE:
break; // Nop, supported
default:
modify_not_supported[dst->element_id] = true;
break;
}
}
if (src->type == UFBX_ELEMENT_NODE) {
ufbx_node *src_node = (ufbx_node*)src;
ufbxi_pre_node *pre_dst = &pre_nodes[dst_node->typed_id];
ufbxi_pre_node *pre_src = &pre_nodes[src_node->typed_id];
// Remember parent and add children into a linked list
if (pre_src->parent == ~0u) {
pre_src->parent = dst_node->typed_id;
pre_src->next_child = pre_dst->first_child;
pre_dst->first_child = src_node->typed_id;
}
if (uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_PRESERVE) {
if (!dst_node->is_root && src_node->original_inherit_mode != UFBX_INHERIT_MODE_NORMAL) {
has_unscaled_children[dst->typed_id] = true;
}
}
}
} else if (dst->type == UFBX_ELEMENT_MESH) {
if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) {
ufbxi_pre_mesh *pre_mesh = &pre_meshes[dst->typed_id];
pre_mesh->has_skin_deformer = true;
}
}
} else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) {
const char *dst_prop = tmp->dst_prop.data;
if (dst->type == UFBX_ELEMENT_ANIM_VALUE && src->type == UFBX_ELEMENT_ANIM_CURVE) {
ufbx_anim_curve *src_curve = (ufbx_anim_curve*)src;
uint32_t index = 0;
if (dst_prop == ufbxi_Y || dst_prop == ufbxi_d_Y) {
index = 1;
} else if (dst_prop == ufbxi_Z || dst_prop == ufbxi_d_Z) {
index = 2;
}
ufbxi_pre_anim_value *pre_value = &pre_anim_values[dst->typed_id];
if (src_curve->max_value - src_curve->min_value >= scale_epsilon) {
pre_value->has_constant_value = false;
} else {
ufbx_real constant_value = (src_curve->min_value + src_curve->max_value) * 0.5f;
if (ufbx_isnan(pre_value->constant_value.v[index])) {
pre_value->constant_value.v[index] = constant_value;
}
if ((ufbx_real)ufbx_fabs(pre_value->constant_value.v[index] - constant_value) > scale_epsilon) {
pre_value->has_constant_value = false;
}
}
}
}
}
for (size_t i = 0; i < num_connections; i++) {
ufbxi_tmp_connection *tmp = &tmp_connections[i];
ufbxi_pre_connection *pre = &pre_connections[i];
ufbx_element *src = pre->src, *dst = pre->dst;
if (!src || !dst) continue;
if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) {
// Count maximum number of instanced attributes in a node
if (dst->type == UFBX_ELEMENT_NODE) {
if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) {
instance_counts[dst->element_id] = ufbxi_max32(instance_counts[dst->element_id], instance_counts[src->element_id]);
if (src->type == UFBX_ELEMENT_MESH) {
ufbxi_pre_mesh *pre_mesh = &pre_meshes[src->typed_id];
if (pre_mesh->has_skin_deformer) {
pre_nodes[dst->typed_id].has_skin_deformer = true;
}
}
} else if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) {
pre_nodes[dst->typed_id].has_skin_deformer = true;
}
}
} else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) {
if (dst->type == UFBX_ELEMENT_NODE) {
if (src->type == UFBX_ELEMENT_ANIM_VALUE) {
if (tmp->dst_prop.data == ufbxi_Lcl_Scaling) {
ufbxi_pre_node *pre_node = &pre_nodes[dst->typed_id];
if (pre_node->has_constant_scale) {
ufbxi_pre_anim_value *pre_value = &pre_anim_values[src->typed_id];
if (!pre_value->has_constant_value) {
pre_node->has_constant_scale = false;
} else {
ufbx_real error = 0.0f;
error += (ufbx_real)ufbx_fabs(pre_value->constant_value.x - pre_node->constant_scale.x);
error += (ufbx_real)ufbx_fabs(pre_value->constant_value.y - pre_node->constant_scale.y);
error += (ufbx_real)ufbx_fabs(pre_value->constant_value.z - pre_node->constant_scale.z);
if (error >= scale_epsilon) {
pre_node->has_constant_scale = false;
}
}
}
}
}
}
}
}
if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) {
for (size_t i = 0; i < num_nodes; i++) {
ufbxi_pre_node *pre_node = &pre_nodes[i];
ufbx_node *node = (ufbx_node*)elements[pre_node->element_id];
ufbx_vec3 rotation_pivot = ufbxi_find_vec3(&node->props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f);
ufbx_vec3 scaling_pivot = ufbxi_find_vec3(&node->props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f);
if (!ufbxi_is_vec3_zero(rotation_pivot)) {
ufbx_real err = 0.0f;
err += (ufbx_real)ufbx_fabs(rotation_pivot.x - scaling_pivot.x);
err += (ufbx_real)ufbx_fabs(rotation_pivot.y - scaling_pivot.y);
err += (ufbx_real)ufbx_fabs(rotation_pivot.z - scaling_pivot.z);
bool can_modify_geometry_transform = true;
if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) {
if (instance_counts[node->element_id] > 1 || modify_not_supported[node->element_id]) {
can_modify_geometry_transform = false;
}
}
// Currently, geometry transform messes up skinning
if (pre_node->has_skin_deformer) {
can_modify_geometry_transform = false;
}
if (err <= pivot_epsilon && can_modify_geometry_transform) {
size_t num_props = node->props.props.count;
ufbx_prop *new_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_props + 3);
ufbxi_check(new_props);
memcpy(new_props, node->props.props.data, num_props * sizeof(ufbx_prop));
ufbx_vec3 geometric_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f);
geometric_translation.x -= rotation_pivot.x;
geometric_translation.y -= rotation_pivot.y;
geometric_translation.z -= rotation_pivot.z;
ufbx_prop *dst = new_props + num_props;
ufbxi_init_synthetic_vec3_prop(&dst[0], ufbxi_RotationPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR);
ufbxi_init_synthetic_vec3_prop(&dst[1], ufbxi_ScalingPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR);
ufbxi_init_synthetic_vec3_prop(&dst[2], ufbxi_GeometricTranslation, &geometric_translation, UFBX_PROP_VECTOR);
node->props.props.data = new_props;
node->props.props.count = num_props + 3;
ufbxi_check(ufbxi_sort_properties(uc, node->props.props.data, node->props.props.count));
ufbxi_deduplicate_properties(&node->props.props);
node->adjust_pre_translation = ufbxi_add3(node->adjust_pre_translation, rotation_pivot);
node->has_adjust_transform = true;
uint32_t ix = pre_node->first_child;
while (ix != ~0u) {
ufbxi_pre_node *pre_child = &pre_nodes[ix];
ufbx_node *child = (ufbx_node*)elements[pre_child->element_id];
child->adjust_pre_translation = ufbxi_sub3(child->adjust_pre_translation, rotation_pivot);
child->has_adjust_transform = true;
ix = pre_child->next_child;
}
}
}
}
}
for (size_t i = 0; i < num_elements; i++) {
ufbx_element *element = elements[i];
uint64_t fbx_id = fbx_ids[i];
if (element->type == UFBX_ELEMENT_NODE) {
ufbx_node *node = (ufbx_node*)element;
bool requires_helper_node = false;
if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES) {
requires_helper_node = true;
} else if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) {
// Setup a geometry transform helper for nodes that have instanced attributes
requires_helper_node = instance_counts[i] > 1 || modify_not_supported[i];
}
if (requires_helper_node) {
ufbxi_check(ufbxi_setup_geometry_transform_helper(uc, node, fbx_id));
}
}
}
for (size_t i = 0; i < num_elements; i++) {
ufbx_element *element = elements[i];
uint64_t fbx_id = fbx_ids[i];
if (element->type == UFBX_ELEMENT_NODE) {
ufbx_node *node = (ufbx_node*)element;
if (has_unscaled_children[node->typed_id] && !node->scale_helper) {
ufbxi_pre_node *pre_node = &pre_nodes[node->typed_id];
ufbx_real ref = uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE
? pre_node->constant_scale.x : (ufbx_real)1.0f;
ufbx_vec3 scale = pre_node->constant_scale;
ufbx_real dx = (ufbx_real)ufbx_fabs(scale.x - ref);
ufbx_real dy = (ufbx_real)ufbx_fabs(scale.y - ref);
ufbx_real dz = (ufbx_real)ufbx_fabs(scale.z - ref);
if ((dx + dy + dz >= scale_epsilon || !pre_node->has_constant_scale || (ufbx_real)ufbx_fabs(scale.x) <= compensate_epsilon)
&& uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) {
ufbxi_check(ufbxi_setup_scale_helper(uc, node, fbx_id));
// If we added a geometry transform helper that may scale further helpers
// recursively for all child nodes using `UFBX_INHERIT_MODE_COMPONENTWISE_SCALE`
// This is guaranteed to terminate as `ufbxi_pre_node` may only have one parent,
// meaning any cycles must contain `node` itself.
uint32_t ix = pre_node->first_child;
while (ix != ~0u && ix != node->typed_id) {
ufbxi_pre_node *pre_child = &pre_nodes[ix];
ufbx_node *child = (ufbx_node*)elements[pre_child->element_id];
if (pre_child->parent != node->typed_id || child->original_inherit_mode == UFBX_INHERIT_MODE_COMPONENTWISE_SCALE) {
if (!pre_child->has_recursive_scale_helper && child->original_inherit_mode != UFBX_INHERIT_MODE_NORMAL) {
pre_child->has_recursive_scale_helper = true;
uint64_t child_fbx_id = fbx_ids[pre_child->element_id];
ufbxi_check(ufbxi_setup_scale_helper(uc, child, child_fbx_id));
child->is_scale_compensate_parent = false;
// Traverse to children if any
if (pre_child->first_child != ~0u) {
ix = pre_child->first_child;
continue;
}
}
}
// Move to next child, popping parents until we find one
while (pre_child->next_child == ~0u) {
ix = pre_child->parent;
if (ix == node->typed_id) break;
pre_child = &pre_nodes[ix];
}
if (ix != node->typed_id) {
ix = pre_child->next_child;
}
}
} else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE || uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK) {
if ((ufbx_real)ufbx_fabs(scale.x - 1.0f) >= scale_epsilon) {
node->is_scale_compensate_parent = true;
}
}
}
}
}
return 1;
}
// -- Scene processing
static ufbxi_noinline ufbx_element *ufbxi_find_element_by_fbx_id(ufbxi_context *uc, uint64_t fbx_id)
{
ufbxi_fbx_id_entry *entry = ufbxi_find_fbx_id(uc, fbx_id);
if (entry) {
return uc->scene.elements.data[entry->element_id];
}
return NULL;
}
ufbxi_forceinline static bool ufbxi_cmp_name_element_less(const ufbx_name_element *a, const ufbx_name_element *b)
{
if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key;
int cmp = strcmp(a->name.data, b->name.data);
if (cmp != 0) return cmp < 0;
return a->type < b->type;
}
ufbxi_forceinline static bool ufbxi_cmp_name_element_less_ref(const ufbx_name_element *a, ufbx_string name, ufbx_element_type type, uint32_t key)
{
if (a->_internal_key != key) return a->_internal_key < key;
int cmp = ufbxi_str_cmp(a->name, name);
if (cmp != 0) return cmp < 0;
return a->type < type;
}
ufbxi_forceinline static bool ufbxi_cmp_prop_less_ref(const ufbx_prop *a, ufbx_string name, uint32_t key)
{
if (a->_internal_key != key) return a->_internal_key < key;
return ufbxi_str_less(a->name, name);
}
ufbxi_forceinline static bool ufbxi_cmp_prop_less_concat(const ufbx_prop *a, const ufbx_string *parts, size_t num_parts, uint32_t key)
{
if (a->_internal_key != key) return a->_internal_key < key;
return ufbxi_concat_str_cmp(&a->name, parts, num_parts) < 0;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_name_elements(ufbxi_context *uc, ufbx_name_element *name_elems, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_name_element)));
ufbxi_macro_stable_sort(ufbx_name_element, 32, name_elems, uc->tmp_arr, count,
( ufbxi_cmp_name_element_less(a, b) ) );
return 1;
}
ufbxi_noinline static bool ufbxi_cmp_node_less(ufbx_node *a, ufbx_node *b)
{
if (a->node_depth != b->node_depth) return a->node_depth < b->node_depth;
if (a->parent && b->parent) {
uint32_t a_pid = a->parent->element.element_id, b_pid = b->parent->element.element_id;
if (a_pid != b_pid) return a_pid < b_pid;
} else {
ufbx_assert(a->parent == NULL && b->parent == NULL);
}
if (a->is_geometry_transform_helper != b->is_geometry_transform_helper) {
// Sort geometry transform helpers always before rest of the children.
return (unsigned)a->is_geometry_transform_helper > (unsigned)b->is_geometry_transform_helper;
}
if (a->is_scale_helper != b->is_scale_helper) {
// Sort scale helpers after geometry transform helpers.
return (unsigned)a->is_scale_helper > (unsigned)b->is_scale_helper;
}
return a->element.element_id < b->element.element_id;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_node_ptrs(ufbxi_context *uc, ufbx_node **nodes, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_node*)));
ufbxi_macro_stable_sort(ufbx_node*, 32, nodes, uc->tmp_arr, count,
( ufbxi_cmp_node_less(*a, *b) ) );
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_cmp_tmp_material_texture_less(const ufbxi_tmp_material_texture *a, const ufbxi_tmp_material_texture *b)
{
if (a->material_id != b->material_id) return a->material_id < b->material_id;
if (a->texture_id != b->texture_id) return a->texture_id < b->texture_id;
return ufbxi_str_less(a->prop_name, b->prop_name);
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_tmp_material_textures(ufbxi_context *uc, ufbxi_tmp_material_texture *mat_texs, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_tmp_material_texture)));
ufbxi_macro_stable_sort(ufbxi_tmp_material_texture, 32, mat_texs, uc->tmp_arr, count,
( ufbxi_cmp_tmp_material_texture_less(a, b) ));
return 1;
}
// We need to be able to assume no padding!
ufbx_static_assert(connection_size, sizeof(ufbx_connection) == sizeof(ufbx_element*)*2 + sizeof(ufbx_string)*2);
ufbxi_forceinline static bool ufbxi_cmp_connection_less(ufbx_connection *a, ufbx_connection *b, size_t index)
{
ufbx_element *a_elem = (&a->src)[index], *b_elem = (&b->src)[index];
if (a_elem != b_elem) return a_elem < b_elem;
int cmp = strcmp((&a->src_prop)[index].data, (&b->src_prop)[index].data);
if (cmp != 0) return cmp < 0;
cmp = strcmp((&a->src_prop)[index ^ 1].data, (&b->src_prop)[index ^ 1].data);
return cmp < 0;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_connections(ufbxi_context *uc, ufbx_connection *connections, size_t count, size_t index)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_connection)));
ufbxi_macro_stable_sort(ufbx_connection, 32, connections, uc->tmp_arr, count, ( ufbxi_cmp_connection_less(a, b, index) ));
return 1;
}
static uint64_t ufbxi_find_attribute_fbx_id(ufbxi_context *uc, uint64_t node_fbx_id)
{
uint32_t hash = ufbxi_hash64(node_fbx_id);
ufbxi_fbx_attr_entry *entry = ufbxi_map_find(&uc->fbx_attr_map, ufbxi_fbx_attr_entry, hash, &node_fbx_id);
if (entry) {
return entry->attr_fbx_id;
}
return node_fbx_id;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_connections(ufbxi_context *uc)
{
size_t num_connections = uc->tmp_connections.num_items;
ufbxi_tmp_connection *tmp_connections = ufbxi_push_pop(&uc->tmp, &uc->tmp_connections, ufbxi_tmp_connection, num_connections);
ufbxi_buf_free(&uc->tmp_connections);
ufbxi_check(tmp_connections);
// NOTE: We truncate this array in case not all connections are resolved
uc->scene.connections_src.data = ufbxi_push(&uc->result, ufbx_connection, num_connections);
ufbxi_check(uc->scene.connections_src.data);
// HACK: Translate property connections from node to attribute if
// the property name is not included in the known node properties.
if (uc->version > 0 && uc->version < 7000) {
ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) {
if (tmp_conn->src_prop.length > 0 && !ufbxi_is_node_property(uc, tmp_conn->src_prop.data)) {
tmp_conn->src = ufbxi_find_attribute_fbx_id(uc, tmp_conn->src);
}
if (tmp_conn->dst_prop.length > 0 && !ufbxi_is_node_property(uc, tmp_conn->dst_prop.data)) {
tmp_conn->dst = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst);
}
}
}
ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) {
ufbx_element *src = ufbxi_find_element_by_fbx_id(uc, tmp_conn->src);
ufbx_element *dst = ufbxi_find_element_by_fbx_id(uc, tmp_conn->dst);
if (!src || !dst) continue;
if (!uc->opts.disable_quirks) {
// Some exporters connect arbitrary non-nodes to root breaking further code, ignore those connections here!
if (dst->type == UFBX_ELEMENT_NODE && src->type != UFBX_ELEMENT_NODE && ((ufbx_node*)dst)->is_root) {
ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_BAD_ELEMENT_CONNECTED_TO_ROOT, src->element_id, "Non-node element connected to root"));
continue;
}
}
// Remap connections to geometry transform helpers if necessary, see `ufbxi_setup_geometry_transform_helper()` for how these are setup.
if (uc->has_geometry_transform_nodes) {
if (dst->type == UFBX_ELEMENT_NODE && src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) {
ufbx_node *node = (ufbx_node*)dst;
if (node->has_geometry_transform) {
ufbxi_node_extra *extra = (ufbxi_node_extra*)ufbxi_get_element_extra(uc, node->element_id);
ufbx_assert(extra);
dst = uc->scene.elements.data[extra->geometry_helper_id];
ufbx_assert(dst->type == UFBX_ELEMENT_NODE && ((ufbx_node*)dst)->is_geometry_transform_helper);
}
}
}
// Remap connections to scale helpers if necessary, see `ufbxi_setup_scale_helper()` for how these are setup.
if (uc->has_scale_helper_nodes) {
if (dst->type == UFBX_ELEMENT_NODE) {
ufbx_node *dst_node = (ufbx_node*)dst;
if (dst_node->scale_helper) {
if (src->type == UFBX_ELEMENT_NODE) {
ufbx_node *src_node = (ufbx_node*)src;
if (!src_node->is_scale_helper && src_node->original_inherit_mode == UFBX_INHERIT_MODE_NORMAL) {
dst = &dst_node->scale_helper->element;
}
} else if (src->type == UFBX_ELEMENT_ANIM_VALUE) {
if (tmp_conn->dst_prop.data == ufbxi_Lcl_Scaling) {
dst = &dst_node->scale_helper->element;
}
} else {
dst = &dst_node->scale_helper->element;
}
}
} else if (src->type == UFBX_ELEMENT_NODE) {
ufbx_node *src_node = (ufbx_node*)src;
if (src_node->scale_helper) {
if (dst->type == UFBX_ELEMENT_SKIN_CLUSTER) {
src = &src_node->scale_helper->element;
}
}
}
}
// Translate deformers to point to the geometry in 6100, we don't need to worry about
// blend shapes here as they're always connected synthetically in older files.
if (uc->version > 0 && uc->version < 7000 && dst->type == UFBX_ELEMENT_NODE) {
if (src->type == UFBX_ELEMENT_SKIN_DEFORMER || src->type == UFBX_ELEMENT_CACHE_DEFORMER) {
uint64_t dst_id = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst);
ufbx_element *dst_elem = ufbxi_find_element_by_fbx_id(uc, dst_id);
if (dst_elem) {
dst = dst_elem;
}
}
}
ufbx_connection *conn = &uc->scene.connections_src.data[uc->scene.connections_src.count++];
conn->src = src;
conn->dst = dst;
conn->src_prop = tmp_conn->src_prop;
conn->dst_prop = tmp_conn->dst_prop;
}
uc->scene.connections_dst.count = uc->scene.connections_src.count;
uc->scene.connections_dst.data = ufbxi_push_copy(&uc->result, ufbx_connection,
uc->scene.connections_src.count, uc->scene.connections_src.data);
ufbxi_check(uc->scene.connections_dst.data);
ufbxi_check(ufbxi_sort_connections(uc, uc->scene.connections_src.data, uc->scene.connections_src.count, 0));
ufbxi_check(ufbxi_sort_connections(uc, uc->scene.connections_dst.data, uc->scene.connections_dst.count, 1));
// We don't need the temporary connections at this point anymore
ufbxi_buf_free(&uc->tmp_connections);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_connections_to_elements(ufbxi_context *uc)
{
ufbx_connection *conn_src = uc->scene.connections_src.data;
ufbx_connection *conn_src_end = ufbxi_add_ptr(conn_src, uc->scene.connections_src.count);
ufbx_connection *conn_dst = uc->scene.connections_dst.data;
ufbx_connection *conn_dst_end = ufbxi_add_ptr(conn_dst, uc->scene.connections_dst.count);
ufbxi_for_ptr(ufbx_element, p_elem, uc->scene.elements.data, uc->scene.elements.count) {
ufbx_element *elem = *p_elem;
uint32_t id = elem->element_id;
while (conn_src < conn_src_end && conn_src->src->element_id < id) conn_src++;
while (conn_dst < conn_dst_end && conn_dst->dst->element_id < id) conn_dst++;
ufbx_connection *src_end = conn_src, *dst_end = conn_dst;
while (src_end < conn_src_end && src_end->src->element_id == id) src_end++;
while (dst_end < conn_dst_end && dst_end->dst->element_id == id) dst_end++;
elem->connections_src.data = conn_src;
elem->connections_src.count = ufbxi_to_size(src_end - conn_src);
elem->connections_dst.data = conn_dst;
elem->connections_dst.count = ufbxi_to_size(dst_end - conn_dst);
// Setup animated properties
// TODO: It seems we're invalidating a lot of properties here actually, maybe they
// should be initially pushed to `tmp` instead of result if this happens so much..
{
ufbx_prop *prop = elem->props.props.data, *prop_end = ufbxi_add_ptr(prop, elem->props.props.count);
ufbx_prop *copy_start = prop;
bool needs_copy = false;
size_t num_animated = 0, num_synthetic = 0;
for (;;) {
// Scan to the next animation connection
for (; conn_dst < dst_end; conn_dst++) {
if (conn_dst->dst_prop.length == 0) continue;
if (conn_dst->src_prop.length > 0) break;
if (conn_dst->src->type == UFBX_ELEMENT_ANIM_VALUE) break;
}
ufbx_string name = ufbx_empty_string;
if (conn_dst < dst_end) {
name = conn_dst->dst_prop;
}
if (name.length == 0) break;
// NOTE: "Animated" properties also include connected ones as we need
// to resolve them during evaluation
num_animated++;
ufbx_anim_value *anim_value = NULL;
uint32_t flags = 0;
for (; conn_dst < dst_end && conn_dst->dst_prop.data == name.data; conn_dst++) {
if (conn_dst->src_prop.length > 0) {
flags |= UFBX_PROP_FLAG_CONNECTED;
} else if (conn_dst->src->type == UFBX_ELEMENT_ANIM_VALUE) {
anim_value = (ufbx_anim_value*)conn_dst->src;
flags |= UFBX_PROP_FLAG_ANIMATED;
}
}
uint32_t key = ufbxi_get_name_key(name.data, name.length);
while (prop != prop_end && ufbxi_name_key_less(prop, name.data, name.length, key)) prop++;
if (prop != prop_end && prop->name.data == name.data) {
prop->flags = (ufbx_prop_flags)((uint32_t)prop->flags | flags);
} else {
// Animated property that is not in the element property list
// Copy the preceding properties to the stack, then push a
// synthetic property for the animated property.
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, ufbxi_to_size(prop - copy_start), copy_start));
copy_start = prop;
needs_copy = true;
// Let's hope we can find the property in the defaults at least
ufbx_prop anim_def_prop;
ufbx_prop *def_prop = NULL;
if (elem->props.defaults) {
def_prop = ufbxi_find_prop_with_key(elem->props.defaults, name.data, key);
} else if (anim_value) {
memset(&anim_def_prop, 0, sizeof(anim_def_prop));
// Hack a couple of common types
ufbx_prop_type type = UFBX_PROP_UNKNOWN;
if (name.data == ufbxi_Lcl_Translation) type = UFBX_PROP_TRANSLATION;
else if (name.data == ufbxi_Lcl_Rotation) type = UFBX_PROP_ROTATION;
else if (name.data == ufbxi_Lcl_Scaling) {
type = UFBX_PROP_SCALING;
anim_def_prop.value_vec3.x = 1.0f;
anim_def_prop.value_vec3.y = 1.0f;
anim_def_prop.value_vec3.z = 1.0f;
}
// Property values are only defined in anim_props on legacy files
if (uc->version < 6000) {
anim_def_prop.value_vec3 = anim_value->default_value;
}
anim_def_prop.type = type;
def_prop = &anim_def_prop;
} else {
flags |= UFBX_PROP_FLAG_NO_VALUE;
}
ufbx_prop *new_prop = ufbxi_push_zero(&uc->tmp_stack, ufbx_prop, 1);
ufbxi_check(new_prop);
if (def_prop) *new_prop = *def_prop;
flags |= (uint32_t)new_prop->flags;
new_prop->flags = (ufbx_prop_flags)(UFBX_PROP_FLAG_ANIMATABLE | UFBX_PROP_FLAG_SYNTHETIC | flags);
new_prop->name = name;
new_prop->_internal_key = key;
new_prop->value_str = ufbx_empty_string;
new_prop->value_blob = ufbx_empty_blob;
num_synthetic++;
}
}
// Copy the properties if necessary
if (needs_copy) {
size_t num_new_props = elem->props.props.count + num_synthetic;
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, ufbxi_to_size(prop_end - copy_start), copy_start));
elem->props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, num_new_props);
ufbxi_check(elem->props.props.data);
elem->props.props.count = num_new_props;
}
elem->props.num_animated = num_animated;
}
conn_src = src_end;
conn_dst = dst_end;
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_linearize_nodes(ufbxi_context *uc)
{
size_t num_nodes = uc->tmp_node_ids.num_items;
uint32_t *node_ids = ufbxi_push_pop(&uc->tmp, &uc->tmp_node_ids, uint32_t, num_nodes);
ufbxi_buf_free(&uc->tmp_node_ids);
ufbxi_check(node_ids);
ufbx_node **node_ptrs = ufbxi_push(&uc->tmp_stack, ufbx_node*, num_nodes);
ufbxi_check(node_ptrs);
// Fetch the node pointers
for (size_t i = 0; i < num_nodes; i++) {
node_ptrs[i] = (ufbx_node*)uc->scene.elements.data[node_ids[i]];
ufbx_assert(node_ptrs[i]->element.type == UFBX_ELEMENT_NODE);
}
uc->scene.root_node = node_ptrs[0];
size_t *node_offsets = ufbxi_push_pop(&uc->tmp_stack, &uc->tmp_typed_element_offsets[UFBX_ELEMENT_NODE], size_t, num_nodes);
ufbxi_check(node_offsets);
// Hook up the parent nodes, we'll assume that there's no cycles at this point
ufbxi_for_ptr(ufbx_node, p_node, node_ptrs, num_nodes) {
ufbx_node *node = *p_node;
// Pre-6000 files don't have any explicit root connections so they must always
// be connected to the root..
if (node->parent == NULL && !(uc->opts.allow_nodes_out_of_root && uc->version >= 6000)) {
if (node != uc->scene.root_node) {
node->parent = uc->scene.root_node;
}
}
ufbxi_for_list(ufbx_connection, conn, node->element.connections_dst) {
if (conn->src_prop.length > 0 || conn->dst_prop.length > 0) continue;
if (conn->src->type != UFBX_ELEMENT_NODE) continue;
((ufbx_node*)conn->src)->parent = node;
}
}
// Count the parent depths and child amounts
ufbxi_for_ptr(ufbx_node, p_node, node_ptrs, num_nodes) {
ufbx_node *node = *p_node;
uint32_t depth = 0;
for (ufbx_node *p = node->parent; p; p = p->parent) {
depth += p->node_depth + 1;
if (p->node_depth > 0) break;
ufbxi_check_msg(depth <= num_nodes, "Cyclic node hierarchy");
}
if (uc->opts.node_depth_limit > 0) {
ufbxi_check_msg(depth <= uc->opts.node_depth_limit, "Node depth limit exceeded");
}
node->node_depth = depth;
// Second pass to cache the depths to avoid O(n^2)
for (ufbx_node *p = node->parent; p; p = p->parent) {
if (--depth <= p->node_depth) break;
p->node_depth = depth;
}
}
ufbxi_check(ufbxi_sort_node_ptrs(uc, node_ptrs, num_nodes));
for (uint32_t i = 0; i < num_nodes; i++) {
size_t *p_offset = ufbxi_push(&uc->tmp_typed_element_offsets[UFBX_ELEMENT_NODE], size_t, 1);
ufbxi_check(p_offset);
ufbx_node *node = node_ptrs[i];
uint32_t original_id = node->element.typed_id;
node->element.typed_id = i;
*p_offset = node_offsets[original_id];
}
// Pop the temporary arrays
ufbxi_pop(&uc->tmp_stack, size_t, num_nodes, NULL);
ufbxi_pop(&uc->tmp_stack, ufbx_node*, num_nodes, NULL);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_connection_list ufbxi_find_dst_connections(ufbx_element *element, const char *prop)
{
if (!prop) prop = ufbxi_empty_char;
size_t begin = element->connections_dst.count, end = begin;
ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &begin,
element->connections_dst.data, 0, element->connections_dst.count,
(strcmp(a->dst_prop.data, prop) < 0),
(a->dst_prop.data == prop && a->src_prop.length == 0));
ufbxi_macro_upper_bound_eq(ufbx_connection, 32, &end,
element->connections_dst.data, begin, element->connections_dst.count,
(a->dst_prop.data == prop && a->src_prop.length == 0));
ufbx_connection_list result = { element->connections_dst.data + begin, end - begin };
return result;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_connection_list ufbxi_find_src_connections(ufbx_element *element, const char *prop)
{
if (!prop) prop = ufbxi_empty_char;
size_t begin = element->connections_src.count, end = begin;
ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &begin,
element->connections_src.data, 0, element->connections_src.count,
(strcmp(a->src_prop.data, prop) < 0),
(a->src_prop.data == prop && a->dst_prop.length == 0));
ufbxi_macro_upper_bound_eq(ufbx_connection, 32, &end,
element->connections_src.data, begin, element->connections_src.count,
(a->src_prop.data == prop && a->dst_prop.length == 0));
ufbx_connection_list result = { element->connections_src.data + begin, end - begin };
return result;
}
ufbxi_nodiscard static ufbx_element *ufbxi_get_element_node(ufbx_element *element)
{
if (!element) return NULL;
if (element->type == UFBX_ELEMENT_NODE) {
ufbx_node *node = (ufbx_node*)element;
if (node->is_geometry_transform_helper) return (ufbx_element*)node->parent;
return NULL;
} else {
return element->instances.count > 0 ? &element->instances.data[0]->element : NULL;
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_dst_elements(ufbxi_context *uc, void *p_dst_list, ufbx_element *element, bool search_node, bool ignore_duplicates, const char *prop, ufbx_element_type src_type)
{
size_t num_elements = 0;
do {
ufbx_connection_list conns = ufbxi_find_dst_connections(element, prop);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->src->type == src_type) {
if (ignore_duplicates) {
uint32_t element_id = conn->src->element_id;
if (uc->tmp_element_flag[element_id]) {
ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_DUPLICATE_CONNECTION, element_id, "Duplicate connection to %u", element->element_id));
continue;
}
uc->tmp_element_flag[element_id] = 1;
}
ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1);
ufbxi_check(p_elem);
*p_elem = conn->src;
num_elements++;
}
}
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
ufbx_element_list *list = (ufbx_element_list*)p_dst_list;
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_elements);
list->count = num_elements;
ufbxi_check(list->data);
if (ignore_duplicates) {
ufbxi_for_ptr_list(ufbx_element, p_elem, *list) {
uc->tmp_element_flag[(*p_elem)->element_id] = 0;
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_src_elements(ufbxi_context *uc, void *p_dst_list, ufbx_element *element, bool search_node, bool ignore_duplicates, const char *prop, ufbx_element_type dst_type)
{
size_t num_elements = 0;
do {
ufbx_connection_list conns = ufbxi_find_src_connections(element, prop);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->dst->type == dst_type) {
if (ignore_duplicates) {
uint32_t element_id = conn->dst->element_id;
if (uc->tmp_element_flag[element_id]) {
ufbxi_check(ufbxi_warnf_tag(UFBX_WARNING_DUPLICATE_CONNECTION, element_id, "Duplicate connection to %u", element->element_id));
continue;
}
uc->tmp_element_flag[element_id] = 1;
}
ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1);
ufbxi_check(p_elem);
*p_elem = conn->dst;
num_elements++;
}
}
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
ufbx_element_list *list = (ufbx_element_list*)p_dst_list;
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_elements);
list->count = num_elements;
ufbxi_check(list->data);
if (ignore_duplicates) {
ufbxi_for_ptr_list(ufbx_element, p_elem, *list) {
uc->tmp_element_flag[(*p_elem)->element_id] = 0;
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_fetch_dst_element(ufbx_element *element, bool search_node, const char *prop, ufbx_element_type src_type)
{
do {
ufbx_connection_list conns = ufbxi_find_dst_connections(element, prop);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->src->type == src_type) {
return conn->src;
}
}
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
return NULL;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_fetch_src_element(ufbx_element *element, bool search_node, const char *prop, ufbx_element_type dst_type)
{
do {
ufbx_connection_list conns = ufbxi_find_src_connections(element, prop);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->dst->type == dst_type) {
return conn->dst;
}
}
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
return NULL;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_textures(ufbxi_context *uc, ufbx_material_texture_list *list, ufbx_element *element, bool search_node)
{
size_t num_textures = 0;
do {
ufbxi_for_list(ufbx_connection, conn, element->connections_dst) {
if (conn->src_prop.length > 0) continue;
if (conn->src->type == UFBX_ELEMENT_TEXTURE) {
ufbx_material_texture *tex = ufbxi_push(&uc->tmp_stack, ufbx_material_texture, 1);
ufbxi_check(tex);
tex->shader_prop = tex->material_prop = conn->dst_prop;
tex->texture = (ufbx_texture*)conn->src;
num_textures++;
}
}
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material_texture, num_textures);
list->count = num_textures;
ufbxi_check(list->data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_mesh_materials(ufbxi_context *uc, ufbx_material_list *list, ufbx_element *element, bool search_node)
{
size_t num_materials = 0;
do {
ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->src->type == UFBX_ELEMENT_MATERIAL) {
ufbx_material *mat = (ufbx_material*)conn->src;
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_material*, 1, &mat));
num_materials++;
}
}
if (num_materials > 0) break;
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material*, num_materials);
list->count = num_materials;
ufbxi_check(list->data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_deformers(ufbxi_context *uc, ufbx_element_list *list, ufbx_element *element, bool search_node)
{
size_t num_deformers = 0;
do {
ufbxi_for_list(ufbx_connection, conn, element->connections_dst) {
if (conn->src_prop.length > 0) continue;
ufbx_element_type type = conn->src->type;
if (type == UFBX_ELEMENT_SKIN_DEFORMER || type == UFBX_ELEMENT_BLEND_DEFORMER || type == UFBX_ELEMENT_CACHE_DEFORMER) {
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &conn->src));
num_deformers++;
}
}
} while (search_node && (element = ufbxi_get_element_node(element)) != NULL);
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, num_deformers);
list->count = num_deformers;
ufbxi_check(list->data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_blend_keyframes(ufbxi_context *uc, ufbx_blend_keyframe_list *list, ufbx_element *element)
{
size_t num_keyframes = 0;
ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->src->type == UFBX_ELEMENT_BLEND_SHAPE) {
ufbx_blend_keyframe key = { (ufbx_blend_shape*)conn->src };
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_blend_keyframe, 1, &key));
num_keyframes++;
}
}
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_blend_keyframe, num_keyframes);
list->count = num_keyframes;
ufbxi_check(list->data);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_texture_layers(ufbxi_context *uc, ufbx_texture_layer_list *list, ufbx_element *element)
{
size_t num_layers = 0;
ufbx_connection_list conns = ufbxi_find_dst_connections(element, NULL);
ufbxi_for_list(ufbx_connection, conn, conns) {
if (conn->src->type == UFBX_ELEMENT_TEXTURE) {
ufbx_texture *texture = (ufbx_texture*)conn->src;
ufbx_texture_layer layer = { texture };
layer.alpha = ufbxi_find_real(&texture->props, ufbxi_Texture_alpha, 1.0f);
layer.blend_mode = (ufbx_blend_mode)ufbxi_find_enum(&texture->props, ufbxi_BlendMode, UFBX_BLEND_REPLACE, UFBX_BLEND_OVERLAY);
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture_layer, 1, &layer));
num_layers++;
}
}
list->data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_texture_layer, num_layers);
list->count = num_layers;
ufbxi_check(list->data);
return 1;
}
static ufbxi_forceinline bool ufbxi_prop_connection_less(const ufbx_connection *a, const char *prop)
{
int cmp = strcmp(a->dst_prop.data, prop);
if (cmp != 0) return cmp < 0;
return a->src_prop.length == 0;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_connection *ufbxi_find_prop_connection(const ufbx_element *element, const char *prop)
{
if (!prop) prop = ufbxi_empty_char;
size_t index = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_connection, 32, &index,
element->connections_dst.data, 0, element->connections_dst.count,
(ufbxi_prop_connection_less(a, prop)),
(a->dst_prop.data == prop && a->src_prop.length > 0));
return index < SIZE_MAX ? &element->connections_dst.data[index] : NULL;
}
ufbxi_forceinline static void ufbxi_patch_index_pointer(ufbxi_context *uc, uint32_t **p_index)
{
if (*p_index == ufbxi_sentinel_index_zero) {
*p_index = uc->zero_indices;
} else if (*p_index == ufbxi_sentinel_index_consecutive) {
*p_index = uc->consecutive_indices;
}
}
ufbxi_nodiscard static bool ufbxi_cmp_anim_prop_less(const ufbx_anim_prop *a, const ufbx_anim_prop *b)
{
if (a->element != b->element) return a->element < b->element;
if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key;
return ufbxi_str_less(a->prop_name, b->prop_name);
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_anim_props(ufbxi_context *uc, ufbx_anim_prop *aprops, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_anim_prop)));
ufbxi_macro_stable_sort(ufbx_anim_prop, 32, aprops, uc->tmp_arr, count, ( ufbxi_cmp_anim_prop_less(a, b) ));
return 1;
}
ufbxi_noinline static bool ufbxi_material_texture_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_material_texture *a = (const ufbx_material_texture*)va, *b = (const ufbx_material_texture*)vb;
return ufbxi_str_less(a->material_prop, b->material_prop);
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_material_textures(ufbxi_context *uc, ufbx_material_texture *textures, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_material_texture)));
ufbxi_stable_sort(sizeof(ufbx_material_texture), 32, textures, uc->tmp_arr, count, &ufbxi_material_texture_less, NULL);
return 1;
}
static ufbxi_noinline bool ufbxi_bone_pose_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_bone_pose *a = (const ufbx_bone_pose *)va, *b = (const ufbx_bone_pose *)vb;
return a->bone_node->typed_id < b->bone_node->typed_id;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_anim_prop *ufbxi_find_anim_prop_start(ufbx_anim_layer *layer, const ufbx_element *element)
{
size_t index = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_anim_prop, 16, &index, layer->anim_props.data, 0, layer->anim_props.count,
(a->element < element), (a->element == element));
return index != SIZE_MAX ? &layer->anim_props.data[index] : NULL;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_bone_poses(ufbxi_context *uc, ufbx_pose *pose)
{
size_t count = pose->bone_poses.count;
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, pose->bone_poses.count * sizeof(ufbx_bone_pose)));
ufbxi_stable_sort(sizeof(ufbx_bone_pose), 16, pose->bone_poses.data, uc->tmp_arr, count, &ufbxi_bone_pose_less, NULL);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_skin_weights(ufbxi_context *uc, ufbx_skin_deformer *skin)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, skin->max_weights_per_vertex * sizeof(ufbx_skin_weight)));
for (size_t i = 0; i < skin->vertices.count; i++) {
ufbx_skin_vertex v = skin->vertices.data[i];
ufbxi_macro_stable_sort(ufbx_skin_weight, 32, skin->weights.data + v.weight_begin, uc->tmp_arr, v.num_weights,
( a->weight > b->weight ));
}
return 1;
}
ufbxi_noinline static bool ufbxi_blend_keyframe_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_blend_keyframe *a = (const ufbx_blend_keyframe*)va, *b = (const ufbx_blend_keyframe*)vb;
return a->target_weight < b->target_weight;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_blend_keyframes(ufbxi_context *uc, ufbx_blend_keyframe *keyframes, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_blend_keyframe)));
ufbxi_stable_sort(sizeof(ufbx_blend_keyframe), 32, keyframes, uc->tmp_arr, count, &ufbxi_blend_keyframe_less, NULL);
return 1;
}
// Material tables
typedef void (*ufbxi_mat_transform_fn)(ufbx_vec4 *a);
static void ufbxi_mat_transform_invert_x(ufbx_vec4 *v) { v->x = 1.0f - v->x; }
static void ufbxi_mat_transform_unknown_shininess(ufbx_vec4 *v) { if (v->x >= 0.0f) v->x = (ufbx_real)(1.0f - ufbx_sqrt(v->x) * (ufbx_real)0.1); if (!(v->x >= 0.0f)) v->x = 0.0f; }
static void ufbxi_mat_transform_blender_opacity(ufbx_vec4 *v) { v->x = 1.0f - v->x; }
static void ufbxi_mat_transform_blender_shininess(ufbx_vec4 *v) { if (v->x >= 0.0f) v->x = (ufbx_real)(1.0f - ufbx_sqrt(v->x) * (ufbx_real)0.1); if (!(v->x >= 0.0f)) v->x = 0.0f; }
typedef enum {
UFBXI_MAT_TRANSFORM_IDENTITY,
UFBXI_MAT_TRANSFORM_INVERT_X,
UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS,
UFBXI_MAT_TRANSFORM_BLENDER_OPACITY,
UFBXI_MAT_TRANSFORM_BLENDER_SHININESS,
UFBXI_MAT_TRANSFORM_COUNT,
} ufbxi_mat_transform;
typedef enum {
// Set `value_vec4.w` (usually alpha) to 1.0 if not defined by the property
UFBXI_SHADER_MAPPING_DEFAULT_W_1 = 0x1,
// Widen values to RGB if only a single value is present.
UFBXI_SHADER_MAPPING_WIDEN_TO_RGB = 0x2,
} ufbxi_shader_mapping_flag;
typedef enum {
// Invert the feature flag
UFBXI_SHADER_FEATURE_INVERTED = 0x1,
// Enable the feature if the given property exists
UFBXI_SHADER_FEATURE_IF_EXISTS = 0x2,
// Enable the feature if the given property has a texture
UFBXI_SHADER_FEATURE_IF_TEXTURE = 0x4,
// Enable if the feature is in [0.5, 1.5], (ie. 2 won't enable this feature)
UFBXI_SHADER_FEATURE_IF_AROUND_1 = 0x8,
UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE = UFBXI_SHADER_FEATURE_IF_EXISTS|UFBXI_SHADER_FEATURE_IF_TEXTURE,
} ufbxi_shader_feature_flag;
static const ufbxi_mat_transform_fn ufbxi_mat_transform_fns[] = {
NULL,
&ufbxi_mat_transform_invert_x,
&ufbxi_mat_transform_unknown_shininess,
&ufbxi_mat_transform_blender_opacity,
&ufbxi_mat_transform_blender_shininess,
};
ufbx_static_assert(transform_count, ufbxi_arraycount(ufbxi_mat_transform_fns) == UFBXI_MAT_TRANSFORM_COUNT);
typedef struct {
uint8_t index; // < `ufbx_material_(fbx|pbr)_map`
uint8_t flags; // < Combination of `ufbxi_shader_mapping_flag`
uint8_t transform; // < `ufbxi_mat_transform`
uint8_t prop_len; // < Length of `prop` not including NULL terminator
const char *prop; // < Name of FBX material property or shader mapping
} ufbxi_shader_mapping;
typedef struct {
const ufbxi_shader_mapping *data;
size_t count;
const ufbxi_shader_mapping *features;
size_t feature_count;
uint32_t default_features;
ufbx_string texture_prefix;
ufbx_string texture_suffix;
ufbx_string texture_enabled_prefix;
ufbx_string texture_enabled_suffix;
} ufbxi_shader_mapping_list;
#define ufbxi_mat_string(str) sizeof(str) - 1, str
static const ufbxi_shader_mapping ufbxi_base_fbx_mapping[] = {
{ UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") },
{ UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") },
{ UFBX_MATERIAL_FBX_DIFFUSE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") },
{ UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") },
{ UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("SpecularColor") },
{ UFBX_MATERIAL_FBX_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("SpecularFactor") },
{ UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("Shininess") },
{ UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("ShininessExponent") },
{ UFBX_MATERIAL_FBX_REFLECTION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Reflection") },
{ UFBX_MATERIAL_FBX_REFLECTION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("ReflectionColor") },
{ UFBX_MATERIAL_FBX_REFLECTION_FACTOR, 0, 0, ufbxi_mat_string("ReflectionFactor") },
{ UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") },
{ UFBX_MATERIAL_FBX_TRANSPARENCY_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") },
{ UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") },
{ UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") },
{ UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") },
{ UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") },
{ UFBX_MATERIAL_FBX_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") },
{ UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Ambient") },
{ UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("AmbientColor") },
{ UFBX_MATERIAL_FBX_AMBIENT_FACTOR, 0, 0, ufbxi_mat_string("AmbientFactor") },
{ UFBX_MATERIAL_FBX_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") },
{ UFBX_MATERIAL_FBX_BUMP, 0, 0, ufbxi_mat_string("Bump") },
{ UFBX_MATERIAL_FBX_BUMP_FACTOR, 0, 0, ufbxi_mat_string("BumpFactor") },
{ UFBX_MATERIAL_FBX_DISPLACEMENT, 0, 0, ufbxi_mat_string("Displacement") },
{ UFBX_MATERIAL_FBX_DISPLACEMENT_FACTOR, 0, 0, ufbxi_mat_string("DisplacementFactor") },
{ UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT, 0, 0, ufbxi_mat_string("VectorDisplacement") },
{ UFBX_MATERIAL_FBX_VECTOR_DISPLACEMENT_FACTOR, 0, 0, ufbxi_mat_string("VectorDisplacementFactor") },
};
static const ufbxi_shader_mapping ufbxi_obj_fbx_mapping[] = {
{ UFBX_MATERIAL_FBX_AMBIENT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ka") },
{ UFBX_MATERIAL_FBX_DIFFUSE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Kd") },
{ UFBX_MATERIAL_FBX_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ks") },
{ UFBX_MATERIAL_FBX_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ke") },
{ UFBX_MATERIAL_FBX_SPECULAR_EXPONENT, 0, 0, ufbxi_mat_string("Ns") },
{ UFBX_MATERIAL_FBX_TRANSPARENCY_FACTOR, 0, UFBXI_MAT_TRANSFORM_INVERT_X, ufbxi_mat_string("d") },
{ UFBX_MATERIAL_FBX_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") },
{ UFBX_MATERIAL_FBX_DISPLACEMENT, 0, 0, ufbxi_mat_string("disp") },
{ UFBX_MATERIAL_FBX_BUMP, 0, 0, ufbxi_mat_string("bump") },
};
static const ufbxi_shader_mapping ufbxi_fbx_lambert_shader_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") },
{ UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") },
};
static const ufbxi_shader_mapping ufbxi_fbx_phong_shader_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Diffuse") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") },
{ UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("DiffuseFactor") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("SpecularColor") },
{ UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("SpecularFactor") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("Shininess") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("ShininessExponent") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Transparent") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("TransparentColor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparentFactor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("TransparencyFactor") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Emissive") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") },
};
static const ufbxi_shader_mapping ufbxi_osl_standard_shader_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specular_roughness") },
{ UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diffuse_roughness") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") },
{ UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular_color") },
{ UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specular_IOR") },
{ UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specular_anisotropy") },
{ UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("specular_rotation") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmission_color") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmission_depth") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmission_scatter") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmission_scatter_anisotropy") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmission_dispersion") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, 0, 0, ufbxi_mat_string("transmission_extra_roughness") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurface_color") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurface_radius") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurface_scale") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurface_anisotropy") },
{ UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("sheen") },
{ UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sheen_color") },
{ UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("sheen_roughness") },
{ UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") },
{ UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") },
{ UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") },
{ UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_IOR") },
{ UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coat_anisotropy") },
{ UFBX_MATERIAL_PBR_COAT_ROTATION, 0, 0, ufbxi_mat_string("coat_rotation") },
{ UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_normal") },
{ UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_affect_color") },
{ UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_affect_roughness") },
{ UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thin_film_thickness") },
{ UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thin_film_IOR") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emission_color") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normalCamera") },
{ UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("tangent") },
};
static const ufbxi_shader_mapping ufbxi_osl_standard_shader_features[] = {
{ UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thin_walled") },
};
static const ufbxi_shader_mapping ufbxi_arnold_shader_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specularRoughness") },
{ UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diffuseRoughness") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") },
{ UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specularColor") },
{ UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specularIOR") },
{ UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specularAnisotropy") },
{ UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("specularRotation") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmissionColor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmissionDepth") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmissionScatter") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmissionScatterAnisotropy") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmissionDispersion") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_EXTRA_ROUGHNESS, 0, 0, ufbxi_mat_string("transmissionExtraRoughness") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurfaceColor") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurfaceRadius") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurfaceScale") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurfaceAnisotropy") },
{ UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("sheen") },
{ UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sheenColor") },
{ UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("sheenRoughness") },
{ UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") },
{ UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coatColor") },
{ UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coatRoughness") },
{ UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coatIOR") },
{ UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coatAnisotropy") },
{ UFBX_MATERIAL_PBR_COAT_ROTATION, 0, 0, ufbxi_mat_string("coatRotation") },
{ UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coatNormal") },
{ UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thinFilmThickness") },
{ UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thinFilmIOR") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emissionColor") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") },
{ UFBX_MATERIAL_PBR_INDIRECT_DIFFUSE, 0, 0, ufbxi_mat_string("indirectDiffuse") },
{ UFBX_MATERIAL_PBR_INDIRECT_SPECULAR, 0, 0, ufbxi_mat_string("indirectSpecular") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normalCamera") },
{ UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("tangent") },
{ UFBX_MATERIAL_PBR_MATTE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("aiMatteColor") },
{ UFBX_MATERIAL_PBR_MATTE_FACTOR, 0, 0, ufbxi_mat_string("aiMatteColorA") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, 0, 0, ufbxi_mat_string("subsurfaceType") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_PRIORITY, 0, 0, ufbxi_mat_string("dielectricPriority") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_ENABLE_IN_AOV, 0, 0, ufbxi_mat_string("transmitAovs") },
};
static const ufbxi_shader_mapping ufbxi_arnold_shader_features[] = {
{ UFBX_MATERIAL_FEATURE_MATTE, 0, 0, ufbxi_mat_string("aiEnableMatte") },
{ UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thinWalled") },
{ UFBX_MATERIAL_FEATURE_CAUSTICS, 0, 0, ufbxi_mat_string("caustics") },
{ UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS, 0, 0, ufbxi_mat_string("internalReflections") },
{ UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND, 0, 0, ufbxi_mat_string("exitToBackground") },
};
static const ufbxi_shader_mapping ufbxi_3ds_max_physical_material_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base_weight") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") },
{ UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diff_rough") },
{ UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("diff_roughness") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") },
{ UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("reflectivity") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("refl_color") },
{ UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("anisotropy") },
{ UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("aniso_angle") },
{ UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("anisoangle") },
{ UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("trans_ior") }, // NOTE: Not a typo, IOR is same for transparency/specular
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transparency") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("trans_color") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("trans_depth") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, 0, 0, ufbxi_mat_string("trans_rough") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, 0, 0, ufbxi_mat_string("trans_roughness") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("scattering") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_TINT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sss_color") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("sss_scatter_color") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("sss_depth") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("sss_scale") },
{ UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat") },
{ UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coating") },
{ UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") },
{ UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_rough") },
{ UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") },
{ UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_ior") },
{ UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump") },
{ UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("clearcoat_bump_map_amt") },
{ UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_affect_color") },
{ UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_affect_roughness") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("cutout") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump_map_amt") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_map_amt") },
{ UFBX_MATERIAL_PBR_SUBSURFACE_TYPE, 0, 0, ufbxi_mat_string("subsurfaceType") },
};
static const ufbxi_shader_mapping ufbxi_3ds_max_physical_material_features[] = {
{ UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("thin_walled") },
{ UFBX_MATERIAL_FEATURE_SPECULAR, 0, 0, ufbxi_mat_string("material_mode") },
{ UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("material_mode") },
{ UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS, UFBXI_SHADER_FEATURE_INVERTED, 0, ufbxi_mat_string("trans_roughness_lock") },
{ UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("roughness_inv") },
{ UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("trans_roughness_inv") },
{ UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, 0, 0, ufbxi_mat_string("coat_roughness_inv") },
};
static const ufbxi_shader_mapping ufbxi_gltf_material_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|baseColor") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("main|roughness") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("main|metalness") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("main|normal") },
{ UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("main|ambientOcclusion") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|emission") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("main|emissionColor") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("main|Alpha") },
{ UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("extension|clearcoat") },
{ UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("extension|clearcoatRoughness") },
{ UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("extension|clearcoatNormal") },
{ UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|sheenColor") },
{ UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("extension|sheenRoughness") },
{ UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("extension|specular") },
{ UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("extension|Specular") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|specularcolor") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("extension|specularColor") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("extension|transmission") },
{ UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("extension|indexOfRefraction") },
};
static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_metal_rough_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("Roughness_Map") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metalness") },
{ UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_amt") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") },
};
static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("Specular") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("glossiness") },
{ UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emit_color") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_amt") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("opacity") },
};
static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_features[] = {
{ UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, UFBXI_SHADER_FEATURE_IF_AROUND_1, 0, ufbxi_mat_string("useGlossiness") },
};
static const ufbxi_shader_mapping ufbxi_gltf_material_features[] = {
{ UFBX_MATERIAL_FEATURE_DOUBLE_SIDED, 0, 0, ufbxi_mat_string("main|DoubleSided") },
{ UFBX_MATERIAL_FEATURE_SHEEN, 0, 0, ufbxi_mat_string("extension|enableSheen") },
{ UFBX_MATERIAL_FEATURE_COAT, 0, 0, ufbxi_mat_string("extension|enableClearCoat") },
{ UFBX_MATERIAL_FEATURE_TRANSMISSION, 0, 0, ufbxi_mat_string("extension|enableTransmission") },
{ UFBX_MATERIAL_FEATURE_IOR, 0, 0, ufbxi_mat_string("extension|enableIndexOfRefraction") },
{ UFBX_MATERIAL_FEATURE_SPECULAR, 0, 0, ufbxi_mat_string("extension|enableSpecular") },
{ UFBX_MATERIAL_FEATURE_UNLIT, 0, 0, ufbxi_mat_string("extension|unlit") },
};
// NOTE: These are just the names used by the standard PBS "preset".
// In _theory_ we could walk ShaderGraph but that's a bit out of scope for ufbx.
static const ufbxi_shader_mapping ufbxi_shaderfx_graph_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("color") },
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("roughness") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("metallic") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("normal") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emissive_intensity") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emissive") },
{ UFBX_MATERIAL_PBR_AMBIENT_OCCLUSION, 0, 0, ufbxi_mat_string("ao") },
};
static const ufbxi_shader_mapping ufbxi_blender_phong_shader_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("DiffuseColor") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, UFBXI_MAT_TRANSFORM_BLENDER_OPACITY, ufbxi_mat_string("TransparencyFactor") },
{ UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("EmissiveFactor") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("EmissiveColor") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, ufbxi_mat_string("Shininess") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_BLENDER_SHININESS, ufbxi_mat_string("ShininessExponent") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("ReflectionFactor") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("NormalMap") },
};
static const ufbxi_shader_mapping ufbxi_obj_pbr_mapping[] = {
{ UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Kd") },
{ UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ks") },
{ UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ke") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, UFBXI_MAT_TRANSFORM_UNKNOWN_SHININESS, ufbxi_mat_string("Ns") },
{ UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("Pr") },
{ UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("Ni") },
{ UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("Pm") },
{ UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("d") },
{ UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Tf") },
{ UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("disp") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") },
{ UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("norm") },
{ UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1|UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("Ps") },
{ UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("Pc") },
{ UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("Pcr") },
{ UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("aniso") },
{ UFBX_MATERIAL_PBR_SPECULAR_ROTATION, 0, 0, ufbxi_mat_string("anisor") },
};
static const ufbxi_shader_mapping ufbxi_obj_features[] = {
{ UFBX_MATERIAL_FEATURE_PBR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pr") },
{ UFBX_MATERIAL_FEATURE_PBR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pm") },
{ UFBX_MATERIAL_FEATURE_SHEEN, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ps") },
{ UFBX_MATERIAL_FEATURE_COAT, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pc") },
{ UFBX_MATERIAL_FEATURE_METALNESS, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Pm") },
{ UFBX_MATERIAL_FEATURE_IOR, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ni") },
{ UFBX_MATERIAL_FEATURE_OPACITY, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("d") },
{ UFBX_MATERIAL_FEATURE_TRANSMISSION, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Tf") },
{ UFBX_MATERIAL_FEATURE_EMISSION, UFBXI_SHADER_FEATURE_IF_EXISTS_OR_TEXTURE, 0, ufbxi_mat_string("Ke") },
};
enum {
UFBXI_MAT_PBR = 1 << UFBX_MATERIAL_FEATURE_PBR,
UFBXI_MAT_METALNESS = 1 << UFBX_MATERIAL_FEATURE_METALNESS,
UFBXI_MAT_DIFFUSE = 1 << UFBX_MATERIAL_FEATURE_DIFFUSE,
UFBXI_MAT_SPECULAR = 1 << UFBX_MATERIAL_FEATURE_SPECULAR,
UFBXI_MAT_EMISSION = 1 << UFBX_MATERIAL_FEATURE_EMISSION,
UFBXI_MAT_COAT = 1 << UFBX_MATERIAL_FEATURE_COAT,
UFBXI_MAT_SHEEN = 1 << UFBX_MATERIAL_FEATURE_SHEEN,
UFBXI_MAT_TRANSMISSION = 1 << UFBX_MATERIAL_FEATURE_TRANSMISSION,
UFBXI_MAT_OPACITY = 1 << UFBX_MATERIAL_FEATURE_OPACITY,
UFBXI_MAT_AMBIENT_OCCLUSION = 1 << UFBX_MATERIAL_FEATURE_AMBIENT_OCCLUSION,
UFBXI_MAT_MATTE = 1 << UFBX_MATERIAL_FEATURE_MATTE,
UFBXI_MAT_UNLIT = 1 << UFBX_MATERIAL_FEATURE_UNLIT,
UFBXI_MAT_IOR = 1 << UFBX_MATERIAL_FEATURE_IOR,
UFBXI_MAT_DIFFUSE_ROUGHNESS = 1 << UFBX_MATERIAL_FEATURE_DIFFUSE_ROUGHNESS,
UFBXI_MAT_TRANSMISSION_ROUGHNESS = 1 << UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS,
UFBXI_MAT_THIN_WALLED = 1 << UFBX_MATERIAL_FEATURE_THIN_WALLED,
UFBXI_MAT_CAUSTICS = 1 << UFBX_MATERIAL_FEATURE_CAUSTICS,
UFBXI_MAT_EXIT_TO_BACKGROUND = 1 << UFBX_MATERIAL_FEATURE_EXIT_TO_BACKGROUND,
UFBXI_MAT_INTERNAL_REFLECTIONS = 1 << UFBX_MATERIAL_FEATURE_INTERNAL_REFLECTIONS,
UFBXI_MAT_DOUBLE_SIDED = 1 << UFBX_MATERIAL_FEATURE_DOUBLE_SIDED,
};
static const ufbxi_shader_mapping_list ufbxi_shader_pbr_mappings[] = {
{ // UFBX_SHADER_UNKNOWN
ufbxi_fbx_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_phong_shader_pbr_mapping),
NULL, 0,
(uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION),
},
{ // UFBX_SHADER_FBX_LAMBERT
ufbxi_fbx_lambert_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_lambert_shader_pbr_mapping),
NULL, 0,
(uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION),
},
{ // UFBX_SHADER_FBX_PHONG
ufbxi_fbx_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_fbx_phong_shader_pbr_mapping),
NULL, 0,
(uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_EMISSION | UFBXI_MAT_TRANSMISSION),
},
{ // UFBX_SHADER_OSL_STANDARD_SURFACE
ufbxi_osl_standard_shader_pbr_mapping, ufbxi_arraycount(ufbxi_osl_standard_shader_pbr_mapping),
ufbxi_osl_standard_shader_features, ufbxi_arraycount(ufbxi_osl_standard_shader_features),
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT
| UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS),
},
{ // UFBX_SHADER_ARNOLD_STANDARD_SURFACE
ufbxi_arnold_shader_pbr_mapping, ufbxi_arraycount(ufbxi_arnold_shader_pbr_mapping),
ufbxi_arnold_shader_features, ufbxi_arraycount(ufbxi_arnold_shader_features),
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT
| UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS),
},
{ // UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL
ufbxi_3ds_max_physical_material_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_physical_material_pbr_mapping),
ufbxi_3ds_max_physical_material_features, ufbxi_arraycount(ufbxi_3ds_max_physical_material_features),
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_COAT
| UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR),
{ NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix
{ NULL, 0 }, ufbxi_string_literal("_map_on"), // texture_enabled_prefix/suffix
},
{ // UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH
ufbxi_3ds_max_pbr_metal_rough_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_pbr_metal_rough_pbr_mapping),
ufbxi_3ds_max_pbr_features, ufbxi_arraycount(ufbxi_3ds_max_pbr_features),
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_OPACITY),
{ NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix
{ NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix
},
{ // UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS
ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping, ufbxi_arraycount(ufbxi_3ds_max_pbr_spec_gloss_pbr_mapping),
ufbxi_3ds_max_pbr_features, ufbxi_arraycount(ufbxi_3ds_max_pbr_features),
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_SPECULAR | UFBXI_MAT_DIFFUSE | UFBXI_MAT_OPACITY),
{ NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix
{ NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix
},
{ // UFBX_SHADER_GLTF_MATERIAL
ufbxi_gltf_material_pbr_mapping, ufbxi_arraycount(ufbxi_gltf_material_pbr_mapping),
ufbxi_gltf_material_features, ufbxi_arraycount(ufbxi_gltf_material_features),
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_AMBIENT_OCCLUSION),
{ NULL, 0 }, ufbxi_string_literal("Map"), // texture_prefix/suffix
{ NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix
},
{ // UFBX_SHADER_SHADERFX_GRAPH
ufbxi_shaderfx_graph_pbr_mapping, ufbxi_arraycount(ufbxi_shaderfx_graph_pbr_mapping),
NULL, 0,
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION | UFBXI_MAT_AMBIENT_OCCLUSION),
ufbxi_string_literal("TEX_"), ufbxi_string_literal("_map"), // texture_prefix/suffix
ufbxi_string_literal("use_"), ufbxi_string_literal("_map"), // texture_enabled_prefix/suffix
},
{ // UFBX_SHADER_BLENDER_PHONG
ufbxi_blender_phong_shader_pbr_mapping, ufbxi_arraycount(ufbxi_blender_phong_shader_pbr_mapping),
NULL, 0,
(uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_EMISSION),
},
{ // UFBX_SHADER_WAVEFRONT_MTL
ufbxi_obj_pbr_mapping, ufbxi_arraycount(ufbxi_obj_pbr_mapping),
ufbxi_obj_features, ufbxi_arraycount(ufbxi_obj_features),
(uint32_t)(UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR),
},
};
ufbx_static_assert(shader_pbr_mapping_list, ufbxi_arraycount(ufbxi_shader_pbr_mappings) == UFBX_SHADER_TYPE_COUNT);
enum {
UFBXI_MAPPING_FETCH_VALUE = 0x1,
UFBXI_MAPPING_FETCH_TEXTURE = 0x2,
UFBXI_MAPPING_FETCH_TEXTURE_ENABLED = 0x4,
UFBXI_MAPPING_FETCH_FEATURE = 0x8,
};
ufbxi_noinline static void ufbxi_fetch_mapping_maps(ufbx_material *material, ufbx_material_map *maps, ufbx_material_feature_info *features,
ufbx_shader *shader, const ufbxi_shader_mapping *mappings, size_t count, ufbx_string prefix, ufbx_string prefix2, ufbx_string suffix, uint32_t flags)
{
char combined_name[512];
ufbx_shader_prop_binding identity_binding;
ufbxi_for(const ufbxi_shader_mapping, mapping, mappings, count) {
ufbx_string prop_name = { mapping->prop, mapping->prop_len };
if (prefix.length > 0 || prefix2.length > 0 || suffix.length > 0) {
if (prop_name.length + prefix.length + prefix2.length + suffix.length <= sizeof(combined_name)) {
char *dst = combined_name;
if (prefix.length > 0) {
memcpy(dst, prefix.data, prefix.length);
dst += prefix.length;
}
if (prefix2.length > 0) {
memcpy(dst, prefix2.data, prefix2.length);
dst += prefix2.length;
}
if (prop_name.length > 0) {
memcpy(dst, prop_name.data, prop_name.length);
dst += prop_name.length;
}
if (suffix.length > 0) {
memcpy(dst, suffix.data, suffix.length);
dst += suffix.length;
}
prop_name.data = combined_name;
prop_name.length = ufbxi_to_size(dst - combined_name);
}
}
ufbx_shader_prop_binding_list bindings = ufbx_find_shader_prop_bindings_len(shader, prop_name.data, prop_name.length);
if (bindings.count == 0) {
identity_binding.material_prop = prop_name;
identity_binding.shader_prop = ufbx_empty_string;
bindings.data = &identity_binding;
bindings.count = 1;
}
uint32_t mapping_flags = mapping->flags;
ufbxi_for_list(ufbx_shader_prop_binding, binding, bindings) {
ufbx_string name = binding->material_prop;
ufbx_prop *prop = ufbx_find_prop_len(&material->props, name.data, name.length);
if (flags & UFBXI_MAPPING_FETCH_FEATURE) {
ufbx_material_feature_info *feature = &features[mapping->index];
if (prop && prop->type != UFBX_PROP_REFERENCE) {
feature->enabled = prop->value_int != 0;
feature->is_explicit = true;
if (mapping_flags & UFBXI_SHADER_FEATURE_IF_AROUND_1) {
feature->enabled = (prop->value_real >= 0.5f && prop->value_real <= 1.5f);
}
if (mapping_flags & UFBXI_SHADER_FEATURE_INVERTED) {
feature->enabled = !feature->enabled;
}
if (mapping_flags & UFBXI_SHADER_FEATURE_IF_EXISTS) {
feature->enabled = true;
}
}
if (mapping_flags & UFBXI_SHADER_FEATURE_IF_TEXTURE) {
ufbx_texture *texture = ufbx_find_prop_texture_len(material, name.data, name.length);
if (texture) {
feature->enabled = true;
}
}
continue;
}
ufbx_material_map *map = &maps[mapping->index];
if (flags & UFBXI_MAPPING_FETCH_VALUE) {
if (prop && prop->type != UFBX_PROP_REFERENCE) {
map->value_vec4 = prop->value_vec4;
map->value_int = prop->value_int;
map->has_value = true;
if (mapping->transform) {
ufbxi_mat_transform_fn transform_fn = ufbxi_mat_transform_fns[mapping->transform];
transform_fn(&map->value_vec4);
}
uint32_t prop_flags = (uint32_t)prop->flags;
if ((mapping->flags & UFBXI_SHADER_MAPPING_DEFAULT_W_1) != 0 && (prop_flags & UFBX_PROP_FLAG_VALUE_VEC4) == 0) {
map->value_vec4.w = 1.0f;
}
if ((mapping->flags & UFBXI_SHADER_MAPPING_WIDEN_TO_RGB) != 0 && (prop_flags & UFBX_PROP_FLAG_VALUE_REAL) != 0) {
map->value_vec3.y = map->value_vec3.x;
map->value_vec3.z = map->value_vec3.x;
}
if ((prop_flags & UFBX_PROP_FLAG_VALUE_REAL) != 0) {
map->value_components = 1;
} else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC2) != 0) {
map->value_components = 2;
} else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC3) != 0) {
map->value_components = 3;
} else if ((prop_flags & UFBX_PROP_FLAG_VALUE_VEC4) != 0) {
map->value_components = 4;
} else {
map->value_components = 0;
}
}
}
if (flags & UFBXI_MAPPING_FETCH_TEXTURE) {
ufbx_texture *texture = ufbx_find_prop_texture_len(material, name.data, name.length);
if (texture) {
map->texture = texture;
map->texture_enabled = true;
}
}
if (flags & UFBXI_MAPPING_FETCH_TEXTURE_ENABLED) {
if (prop) {
map->texture_enabled = prop->value_int != 0;
}
}
}
}
}
ufbxi_noinline static void ufbxi_update_factor(ufbx_material_map *factor_map, ufbx_material_map *color_map)
{
if (!factor_map->has_value) {
if (color_map->has_value && !ufbxi_is_vec4_zero(color_map->value_vec4)) {
factor_map->value_real = 1.0f;
factor_map->value_int = 1;
} else {
factor_map->value_real = 0.0f;
factor_map->value_int = 0;
}
}
}
// Some material modes have toggleable roughness/glossiness mode, we read it initially
// always as roughness and if a matching feature such as `roughness_as_glossiness` is set
// we transfer the data into the glossiness and invert the roughness.
typedef struct {
uint8_t feature;
uint8_t roughness_map;
uint8_t glossiness_map;
} ufbxi_glossiness_remap;
static const ufbxi_glossiness_remap ufbxi_glossiness_remaps[] = {
{ UFBX_MATERIAL_FEATURE_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_ROUGHNESS, UFBX_MATERIAL_PBR_GLOSSINESS },
{ UFBX_MATERIAL_FEATURE_COAT_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_COAT_ROUGHNESS, UFBX_MATERIAL_PBR_COAT_GLOSSINESS },
{ UFBX_MATERIAL_FEATURE_TRANSMISSION_ROUGHNESS_AS_GLOSSINESS, UFBX_MATERIAL_PBR_TRANSMISSION_ROUGHNESS, UFBX_MATERIAL_PBR_TRANSMISSION_GLOSSINESS },
};
ufbxi_noinline static void ufbxi_fetch_maps(ufbx_scene *scene, ufbx_material *material)
{
(void)scene;
ufbx_shader *shader = material->shader;
ufbx_assert((uint32_t)material->shader_type < UFBX_SHADER_TYPE_COUNT);
memset(&material->fbx, 0, sizeof(material->fbx));
memset(&material->pbr, 0, sizeof(material->pbr));
memset(&material->features, 0, sizeof(material->features));
const ufbxi_shader_mapping *base_mapping = ufbxi_base_fbx_mapping;
size_t num_base_mapping = ufbxi_arraycount(ufbxi_base_fbx_mapping);
if (scene->metadata.file_format == UFBX_FILE_FORMAT_OBJ || scene->metadata.file_format == UFBX_FILE_FORMAT_MTL) {
base_mapping = ufbxi_obj_fbx_mapping;
num_base_mapping = ufbxi_arraycount(ufbxi_obj_fbx_mapping);
}
ufbxi_fetch_mapping_maps(material, material->fbx.maps, NULL, NULL,
base_mapping, num_base_mapping,
ufbx_empty_string, ufbx_empty_string, ufbx_empty_string,
UFBXI_MAPPING_FETCH_VALUE | UFBXI_MAPPING_FETCH_TEXTURE);
ufbxi_shader_mapping_list list = ufbxi_shader_pbr_mappings[material->shader_type];
for (uint32_t i = 0; i < UFBX_MATERIAL_FEATURE_COUNT; i++) {
if ((list.default_features & (1u << i)) != 0) {
material->features.features[i].enabled = true;
}
}
ufbx_string prefix = ufbx_empty_string;
if (!shader) {
prefix = material->shader_prop_prefix;
}
if (list.texture_prefix.length > 0 || list.texture_suffix.length > 0) {
ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader,
list.data, list.count, prefix, list.texture_prefix, list.texture_suffix,
UFBXI_MAPPING_FETCH_TEXTURE);
}
ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader,
list.data, list.count, prefix, ufbx_empty_string, ufbx_empty_string,
UFBXI_MAPPING_FETCH_VALUE | UFBXI_MAPPING_FETCH_TEXTURE);
if (list.texture_enabled_prefix.length > 0 || list.texture_enabled_suffix.length > 0) {
ufbxi_fetch_mapping_maps(material, material->pbr.maps, NULL, shader,
list.data, list.count, prefix, list.texture_enabled_prefix, list.texture_enabled_suffix,
UFBXI_MAPPING_FETCH_TEXTURE_ENABLED);
}
ufbxi_fetch_mapping_maps(material, NULL, material->features.features, shader,
list.features, list.feature_count, prefix, ufbx_empty_string, ufbx_empty_string,
UFBXI_MAPPING_FETCH_FEATURE);
ufbxi_update_factor(&material->fbx.diffuse_factor, &material->fbx.diffuse_color);
ufbxi_update_factor(&material->fbx.specular_factor, &material->fbx.specular_color);
ufbxi_update_factor(&material->fbx.reflection_factor, &material->fbx.reflection_color);
ufbxi_update_factor(&material->fbx.transparency_factor, &material->fbx.transparency_color);
ufbxi_update_factor(&material->fbx.emission_factor, &material->fbx.emission_color);
ufbxi_update_factor(&material->fbx.ambient_factor, &material->fbx.ambient_color);
ufbxi_update_factor(&material->pbr.base_factor, &material->pbr.base_color);
ufbxi_update_factor(&material->pbr.specular_factor, &material->pbr.specular_color);
ufbxi_update_factor(&material->pbr.emission_factor, &material->pbr.emission_color);
ufbxi_update_factor(&material->pbr.sheen_factor, &material->pbr.sheen_color);
ufbxi_update_factor(&material->pbr.transmission_factor, &material->pbr.transmission_color);
// Patch transmission roughness if only extra roughness is defined
if (!material->pbr.transmission_roughness.has_value && material->pbr.roughness.has_value && material->pbr.transmission_extra_roughness.has_value) {
material->pbr.transmission_roughness.value_real = material->pbr.roughness.value_real + material->pbr.transmission_extra_roughness.value_real;
}
// Map roughness to glossiness and vice versa
ufbxi_for(const ufbxi_glossiness_remap, remap, ufbxi_glossiness_remaps, ufbxi_arraycount(ufbxi_glossiness_remaps)) {
ufbx_material_map *roughness = &material->pbr.maps[remap->roughness_map];
ufbx_material_map *glossiness = &material->pbr.maps[remap->glossiness_map];
if (material->features.features[remap->feature].enabled) {
*glossiness = *roughness;
memset(roughness, 0, sizeof(ufbx_material_map));
if (glossiness->has_value) {
roughness->value_real = 1.0f - glossiness->value_real;
}
} else {
if (roughness->has_value) {
glossiness->value_real = 1.0f - roughness->value_real;
}
}
}
}
typedef enum {
UFBXI_CONSTRAINT_PROP_NODE,
UFBXI_CONSTRAINT_PROP_IK_EFFECTOR,
UFBXI_CONSTRAINT_PROP_IK_END_NODE,
UFBXI_CONSTRAINT_PROP_AIM_UP,
UFBXI_CONSTRAINT_PROP_TARGET,
} ufbxi_constraint_prop_type;
typedef struct {
ufbxi_constraint_prop_type type;
const char *name;
} ufbxi_constraint_prop;
static const ufbxi_constraint_prop ufbxi_constraint_props[] = {
{ UFBXI_CONSTRAINT_PROP_NODE, "Constrained Object" },
{ UFBXI_CONSTRAINT_PROP_NODE, "Constrained object (Child)" },
{ UFBXI_CONSTRAINT_PROP_NODE, "First Joint" },
{ UFBXI_CONSTRAINT_PROP_TARGET, "Source" },
{ UFBXI_CONSTRAINT_PROP_TARGET, "Source (Parent)" },
{ UFBXI_CONSTRAINT_PROP_TARGET, "Aim At Object" },
{ UFBXI_CONSTRAINT_PROP_TARGET, "Pole Vector Object" },
{ UFBXI_CONSTRAINT_PROP_IK_EFFECTOR, "Effector" },
{ UFBXI_CONSTRAINT_PROP_IK_END_NODE, "End Joint" },
{ UFBXI_CONSTRAINT_PROP_AIM_UP, "World Up Object" },
};
ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_constraint_prop(ufbxi_context *uc, ufbx_constraint *constraint, ufbx_node *node, const char *prop)
{
ufbxi_for(const ufbxi_constraint_prop, cprop, ufbxi_constraint_props, ufbxi_arraycount(ufbxi_constraint_props)) {
if (strcmp(cprop->name, prop) != 0) continue;
switch (cprop->type) {
case UFBXI_CONSTRAINT_PROP_NODE: constraint->node = node; break;
case UFBXI_CONSTRAINT_PROP_IK_EFFECTOR: constraint->ik_effector = node; break;
case UFBXI_CONSTRAINT_PROP_IK_END_NODE: constraint->ik_end_node = node; break;
case UFBXI_CONSTRAINT_PROP_AIM_UP: constraint->aim_up_node = node; break;
case UFBXI_CONSTRAINT_PROP_TARGET: {
ufbx_constraint_target *target = ufbxi_push_zero(&uc->tmp_stack, ufbx_constraint_target, 1);
ufbxi_check(target);
target->node = node;
target->weight = 1.0f;
target->transform = ufbx_identity_transform;
} break;
default:
ufbxi_unreachable("Unexpected constraint prop");
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_nurbs_basis(ufbxi_context *uc, ufbx_nurbs_basis *basis)
{
if (basis->topology == UFBX_NURBS_TOPOLOGY_CLOSED) {
basis->num_wrap_control_points = 1;
} else if (basis->topology == UFBX_NURBS_TOPOLOGY_PERIODIC) {
basis->num_wrap_control_points = basis->order - 1;
} else {
basis->num_wrap_control_points = 0;
}
if (basis->order > 1) {
size_t degree = basis->order - 1;
ufbx_real_list knots = basis->knot_vector;
if (knots.count >= 2*degree + 1) {
basis->t_min = knots.data[degree];
basis->t_max = knots.data[knots.count - degree - 1];
size_t max_spans = knots.count - 2*degree;
ufbx_real *spans = ufbxi_push(&uc->result, ufbx_real, max_spans);
ufbxi_check(spans);
ufbx_real prev = -UFBX_INFINITY;
size_t num_spans = 0;
for (size_t i = 0; i < max_spans; i++) {
ufbx_real t = knots.data[degree + i];
if (t != prev) {
spans[num_spans++] = t;
prev = t;
}
}
basis->spans.data = spans;
basis->spans.count = num_spans;
basis->valid = true;
for (size_t i = 1; i < knots.count; i++) {
if (knots.data[i - 1] > knots.data[i]) {
basis->valid = false;
break;
}
}
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_lod_group(ufbxi_context *uc, ufbx_lod_group *lod)
{
size_t num_levels = 0;
for (size_t i = 0; i < lod->instances.count; i++) {
num_levels = ufbxi_max_sz(num_levels, lod->instances.data[0]->children.count);
}
char prop_name[64];
for (size_t i = 0; ; i++) {
int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "Thresholds|Level%zu", i);
ufbx_prop *prop = ufbx_find_prop_len(&lod->props, prop_name, (size_t)len);
if (!prop) break;
num_levels = ufbxi_max_sz(num_levels, i + 1);
}
ufbx_lod_level *levels = ufbxi_push_zero(&uc->result, ufbx_lod_level, num_levels);
ufbxi_check(levels);
lod->relative_distances = ufbx_find_bool(&lod->props, "ThresholdsUsedAsPercentage", false);
lod->ignore_parent_transform = !ufbx_find_bool(&lod->props, "WorldSpace", true);
lod->use_distance_limit = ufbx_find_bool(&lod->props, "MinMaxDistance", false);
lod->distance_limit_min = ufbx_find_real(&lod->props, "MinDistance", (ufbx_real)-100.0);
lod->distance_limit_max = ufbx_find_real(&lod->props, "MaxDistance", (ufbx_real)100.0);
lod->lod_levels.data = levels;
lod->lod_levels.count = num_levels;
for (size_t i = 0; i < num_levels; i++) {
ufbx_lod_level *level = &levels[i];
if (i > 0) {
int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "Thresholds|Level%zu", i - 1);
level->distance = ufbx_find_real_len(&lod->props, prop_name, (size_t)len, 0.0f);
} else if (lod->relative_distances) {
level->distance = (ufbx_real)100.0;
}
{
int len = ufbxi_snprintf(prop_name, sizeof(prop_name), "DisplayLevels|Level%zu", i);
int64_t display = ufbx_find_int_len(&lod->props, prop_name, (size_t)len, 0);
if (display >= 0 && display <= 2) {
level->display = (ufbx_lod_display)display;
}
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_generate_normals(ufbxi_context *uc, ufbx_mesh *mesh)
{
size_t num_indices = mesh->num_indices;
mesh->generated_normals = true;
ufbx_topo_edge *topo = ufbxi_push(&uc->tmp_stack, ufbx_topo_edge, num_indices);
ufbxi_check(topo);
uint32_t *normal_indices = ufbxi_push(&uc->result, uint32_t, num_indices);
ufbxi_check(normal_indices);
ufbx_compute_topology(mesh, topo, num_indices);
size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, num_indices, normal_indices, num_indices, false);
if (num_normals == mesh->num_vertices) {
mesh->vertex_normal.unique_per_vertex = true;
}
ufbx_vec3 *normal_data = ufbxi_push(&uc->result, ufbx_vec3, num_normals + 1);
ufbxi_check(normal_data);
normal_data[0] = ufbx_zero_vec3;
normal_data++;
ufbx_compute_normals(mesh, &mesh->vertex_position, normal_indices, num_indices, normal_data, num_normals);
mesh->vertex_normal.exists = true;
mesh->vertex_normal.values.data = normal_data;
mesh->vertex_normal.values.count = num_normals;
mesh->vertex_normal.indices.data = normal_indices;
mesh->vertex_normal.indices.count = num_indices;
mesh->vertex_normal.value_reals = 3;
mesh->skinned_normal = mesh->vertex_normal;
ufbxi_pop(&uc->tmp_stack, ufbx_topo_edge, num_indices, NULL);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_prop_prefix(ufbxi_context *uc, ufbx_string *dst, ufbx_string prefix)
{
size_t stack_size = 0;
if (prefix.length > 0 && prefix.data[prefix.length - 1] != '|') {
stack_size = prefix.length + 1;
char *copy = ufbxi_push(&uc->tmp_stack, char, stack_size);
ufbxi_check(copy);
memcpy(copy, prefix.data, prefix.length);
copy[prefix.length] = '|';
prefix.data = copy;
prefix.length += 1;
}
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &prefix, false));
*dst = prefix;
if (stack_size > 0) {
ufbxi_pop(&uc->tmp_stack, char, stack_size, NULL);
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_shader_texture_find_prefix(ufbxi_context *uc, ufbx_texture *texture, ufbx_shader_texture *shader)
{
ufbx_string suffixes[3];
size_t num_suffixes = 0;
suffixes[num_suffixes++] = ufbxi_str_c(" Parameters/Connections");
if (shader->shader_name.length > 0) {
suffixes[num_suffixes++] = shader->shader_name;
}
suffixes[num_suffixes++] = ufbxi_str_c("3dsMax|parameters");
ufbx_assert(num_suffixes <= ufbxi_arraycount(suffixes));
ufbxi_for(ufbx_string, p_suffix, suffixes, num_suffixes) {
ufbx_string suffix = *p_suffix;
ufbxi_for_list(ufbx_prop, prop, texture->props.props) {
if (prop->type != UFBX_PROP_COMPOUND) continue;
if (ufbxi_ends_with(prop->name, suffix)) {
ufbxi_check(ufbxi_push_prop_prefix(uc, &shader->prop_prefix, prop->name));
return 1;
}
}
}
// Pre-7000 files don't have explicit Compound properties, so let's look for
// any property that has the suffix before the last `|` ...
ufbxi_for(ufbx_string, p_suffix, suffixes, num_suffixes) {
ufbx_string suffix = *p_suffix;
ufbxi_for_list(ufbx_prop, prop, texture->props.props) {
ufbx_string name = prop->name;
while (name.length > 0) {
if (name.data[name.length - 1] == '|') {
break;
}
name.length--;
}
if (name.length <= 1) continue;
name.length--;
if (ufbxi_ends_with(name, suffix)) {
ufbxi_check(ufbxi_push_prop_prefix(uc, &shader->prop_prefix, name));
return 1;
}
}
}
return 1;
}
typedef struct {
uint64_t shader_id;
const char *shader_name;
const char *input_name;
} ufbxi_file_shader;
// Known shaders that represent sampled images.
static const ufbxi_file_shader ufbxi_file_shaders[] = {
{ UINT64_C(0x7e73161fad53b12a), "ai_image", "filename" },
{ 0, "OSLBitmap", ufbxi_Filename },
{ 0, "OSLBitmap2", ufbxi_Filename },
{ 0, "UberBitmap", ufbxi_Filename },
{ 0, "UberBitmap2", ufbxi_Filename },
};
ufbxi_noinline static void ufbxi_update_shader_texture(ufbx_texture *texture, ufbx_shader_texture *shader)
{
ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) {
ufbx_prop *prop = input->prop;
if (prop) {
input->prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length);
input->value_vec4 = prop->value_vec4;
input->value_int = prop->value_int;
input->value_str = prop->value_str;
input->value_blob = prop->value_blob;
input->texture = (ufbx_texture*)ufbx_get_prop_element(&texture->element, input->prop, UFBX_ELEMENT_TEXTURE);
}
prop = input->texture_prop;
if (prop) {
input->texture_prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length);
ufbx_texture *tex = (ufbx_texture*)ufbx_get_prop_element(&texture->element, prop, UFBX_ELEMENT_TEXTURE);
if (tex) input->texture = tex;
}
input->texture_enabled = input->texture != NULL;
prop = input->texture_enabled_prop;
if (prop) {
input->texture_enabled_prop = prop = ufbx_find_prop_len(&texture->props, prop->name.data, prop->name.length);
input->texture_enabled = prop->value_int != 0;
}
}
if (shader->type == UFBX_SHADER_TEXTURE_SELECT_OUTPUT) {
ufbx_shader_texture_input *map = ufbx_find_shader_texture_input(shader, "sourceMap");
ufbx_shader_texture_input *index = ufbx_find_shader_texture_input(shader, "outputChannelIndex");
if (index) {
shader->main_texture_output_index = index->value_int;
}
if (map) {
shader->main_texture = map->texture;
map->texture_output_index = shader->main_texture_output_index;
}
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_shader_texture(ufbxi_context *uc, ufbx_texture *texture)
{
uint32_t classid_a = (uint32_t)(uint64_t)ufbx_find_int(&texture->props, "3dsMax|ClassIDa", 0);
uint32_t classid_b = (uint32_t)(uint64_t)ufbx_find_int(&texture->props, "3dsMax|ClassIDb", 0);
uint64_t classid = (uint64_t)classid_a << 32u | classid_b;
ufbx_string max_texture = ufbx_find_string(&texture->props, "3dsMax|MaxTexture", ufbx_empty_string);
// Check first if the texture looks like it could be a shader.
ufbx_shader_texture_type type = (ufbx_shader_texture_type)UFBX_SHADER_TEXTURE_TYPE_COUNT;
if (!strcmp(max_texture.data, "MULTIOUTPUT_TO_OSLMap") || classid == UINT64_C(0x896ef2fc44bd743f)) {
type = UFBX_SHADER_TEXTURE_SELECT_OUTPUT;
} else if (!strcmp(max_texture.data, "OSLMap") || classid == UINT64_C(0x7f9a7b9d6fcdf00d)) {
type = UFBX_SHADER_TEXTURE_OSL;
} else if (texture->type == UFBX_TEXTURE_FILE && texture->relative_filename.length == 0 && texture->absolute_filename.length == 0 && !texture->video) {
type = UFBX_SHADER_TEXTURE_UNKNOWN;
}
if ((uint32_t)type == UFBX_SHADER_TEXTURE_TYPE_COUNT) return 1;
ufbx_shader_texture *shader = ufbxi_push_zero(&uc->result, ufbx_shader_texture, 1);
ufbxi_check(shader);
shader->type = type;
static const char *const name_props[] = {
"3dsMax|params|OSLShaderName",
};
static const char *const source_props[] = {
"3dsMax|params|OSLCode",
};
shader->shader_source.data = ufbxi_empty_char;
shader->shader_name.data = ufbxi_empty_char;
ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(name_props); i++) {
ufbx_prop *prop = ufbx_find_prop(&texture->props, name_props[i]);
if (prop) {
shader->shader_name = prop->value_str;
break;
}
}
ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(source_props); i++) {
ufbx_prop *prop = ufbx_find_prop(&texture->props, source_props[i]);
if (prop) {
shader->shader_source = prop->value_str;
shader->raw_shader_source = prop->value_blob;
break;
}
}
ufbxi_check(ufbxi_shader_texture_find_prefix(uc, texture, shader));
if (shader->shader_name.length == 0) {
ufbx_string name = shader->prop_prefix;
if (ufbxi_remove_suffix_c(&name, " Parameters/Connections|")) {
size_t begin = name.length;
while (begin > 0 && name.data[begin - 1] != '|') {
begin--;
}
shader->shader_name.data = name.data + begin;
shader->shader_name.length = name.length - begin;
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, &shader->shader_name, false));
}
}
if (shader->shader_name.length == 0) {
if (max_texture.length > 0) {
shader->shader_name = max_texture;
}
}
if (classid != 0) {
shader->shader_type_id = classid;
}
if (shader->prop_prefix.length == 0) {
// If we not find any shader properties so we might have guessed wrong.
// We "leak" (freed with scene) the shader in this case but it's negligible.
return 1;
}
ufbxi_for_list(ufbx_prop, prop, texture->props.props) {
ufbx_string name = prop->name;
if (!ufbxi_remove_prefix_str(&name, shader->prop_prefix)) continue;
// Check if this property is a modifier to an existing input.
ufbx_string base_name = name;
if (ufbxi_remove_suffix_c(&base_name, "_map") || ufbxi_remove_suffix_c(&base_name, ".shader")) {
ufbx_shader_texture_input *base = ufbx_find_shader_texture_input_len(shader, base_name.data, base_name.length);
if (base) {
base->texture_prop = prop;
continue;
}
} else if (ufbxi_remove_suffix_c(&base_name, ".connected") || ufbxi_remove_suffix_c(&base_name, "Enabled")) {
ufbx_shader_texture_input *base = ufbx_find_shader_texture_input_len(shader, base_name.data, base_name.length);
if (base) {
base->texture_enabled_prop = prop;
continue;
}
}
// Use `uc->tmp_arr` to store the texture inputs so we can search them while we insert new ones.
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size,
(shader->inputs.count + 1) * sizeof(ufbx_shader_texture_input)));
shader->inputs.data = (ufbx_shader_texture_input*)uc->tmp_arr;
// Add a new property
ufbx_shader_texture_input *input = &shader->inputs.data[shader->inputs.count++];
memset(input, 0, sizeof(ufbx_shader_texture_input));
// NOTE: This is a bit hackish, we are using a suffix of an interned string. It won't compare
// pointer equal to the same string but that shouldn't matter..
input->name = name;
// Connect the property only, values and textures etc are fetched in `ufbxi_update_shader_texture()`.
input->prop = prop;
}
// Retain the shader inputs
shader->inputs.data = ufbxi_push_copy(&uc->result, ufbx_shader_texture_input, shader->inputs.count, shader->inputs.data);
ufbxi_check(shader->inputs.data);
texture->shader = shader;
texture->type = UFBX_TEXTURE_SHADER;
uc->scene.metadata.num_shader_textures++;
if (!uc->opts.disable_quirks) {
ufbxi_nounroll for (size_t i = 0; i < ufbxi_arraycount(ufbxi_file_shaders); i++) {
const ufbxi_file_shader *fs = &ufbxi_file_shaders[i];
if ((fs->shader_id && shader->shader_type_id == fs->shader_id) || !strcmp(shader->shader_name.data, fs->shader_name)) {
ufbx_shader_texture_input *input = ufbx_find_shader_texture_input(shader, fs->input_name);
if (input) {
// TODO: Support for specifying relative filename here if ever needed
ufbx_prop *prop = input->prop;
texture->absolute_filename = prop->value_str;
texture->raw_absolute_filename = prop->value_blob;
texture->type = UFBX_TEXTURE_FILE;
break;
}
}
}
}
ufbxi_update_shader_texture(texture, shader);
return 1;
}
ufbxi_noinline static void ufbxi_propagate_main_textures(ufbx_scene *scene)
{
// We need to do at least 2^(N-1) passes for N shader textures
size_t mask = scene->metadata.num_shader_textures;
while (mask) {
mask >>= 1;
ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) {
ufbx_texture *texture = *p_texture;
ufbx_shader_texture *shader = texture->shader;
if (!shader) continue;
ufbx_texture *main_tex = shader->main_texture;
if (!main_tex || shader->main_texture_output_index != 0) continue;
ufbx_shader_texture *main_shader = main_tex->shader;
if (!main_shader || !main_shader->main_texture) continue;
shader->main_texture = main_shader->main_texture;
shader->main_texture_output_index = main_shader->main_texture_output_index;
}
}
// Remove cyclic main textures
ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) {
ufbx_texture *texture = *p_texture;
ufbx_shader_texture *shader = texture->shader;
if (!shader || !shader->main_texture || shader->main_texture_output_index != 0) continue;
ufbx_texture *main_tex = shader->main_texture;
if (main_tex && main_tex->shader && main_tex->shader->main_texture) {
// Should have been propagated to `texture`
shader->main_texture = NULL;
}
}
ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) {
ufbx_texture *texture = *p_texture;
ufbx_shader_texture *shader = texture->shader;
if (!shader) continue;
ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) {
if (!input->texture || !input->texture->shader) continue;
ufbx_shader_texture *input_shader = input->texture->shader;
if (input_shader->main_texture) {
input->texture = input_shader->main_texture;
input->texture_output_index = input_shader->main_texture_output_index;
}
}
}
ufbxi_for_ptr_list(ufbx_material, p_material, scene->materials) {
ufbx_material *material = *p_material;
ufbxi_for_list(ufbx_material_texture, tex, material->textures) {
ufbx_shader_texture *shader = tex->texture->shader;
if (shader && shader->main_texture && shader->main_texture_output_index == 0) {
tex->texture = shader->main_texture;
}
}
}
}
#define ufbxi_patch_empty(m_dst, m_len, m_src) \
do { if (!(m_dst).m_len) m_dst = m_src; } while (0)
ufbxi_nodiscard ufbxi_noinline static int ufbxi_insert_texture_file(ufbxi_context *uc, ufbx_texture *texture)
{
texture->file_index = UFBX_NO_INDEX;
const char *key = NULL;
// HACK: Even the raw entries have a null terminator so we can offset the
// pointer by one for relative filenames. This guarantees that an overlapping
// absolute and relative filenames will get separate textures.
if (texture->raw_absolute_filename.size > 0) {
key = (const char*)texture->raw_absolute_filename.data;
} else if (texture->raw_relative_filename.size > 0) {
key = (const char*)texture->raw_relative_filename.data + 1;
}
if (key == NULL) return 1;
uint32_t hash = ufbxi_hash_ptr(key);
ufbxi_texture_file_entry *entry = ufbxi_map_find(&uc->texture_file_map, ufbxi_texture_file_entry, hash, &key);
if (!entry) {
entry = ufbxi_map_insert(&uc->texture_file_map, ufbxi_texture_file_entry, hash, &key);
ufbxi_check(entry);
ufbx_texture_file *file = ufbxi_push_zero(&uc->tmp, ufbx_texture_file, 1);
ufbxi_check(file);
file->index = uc->texture_file_map.size - 1;
entry->key = key;
entry->file = file;
}
ufbx_texture_file *file = entry->file;
texture->file_index = file->index;
texture->has_file = true;
ufbxi_patch_empty(file->filename, length, texture->filename);
ufbxi_patch_empty(file->relative_filename, length, texture->relative_filename);
ufbxi_patch_empty(file->absolute_filename, length, texture->absolute_filename);
ufbxi_patch_empty(file->raw_filename, size, texture->raw_filename);
ufbxi_patch_empty(file->raw_relative_filename, size, texture->raw_relative_filename);
ufbxi_patch_empty(file->raw_absolute_filename, size, texture->raw_absolute_filename);
ufbxi_patch_empty(file->content, size, texture->content);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_pop_texture_files(ufbxi_context *uc)
{
uint32_t num_files = uc->texture_file_map.size;
ufbx_texture_file *files = ufbxi_push(&uc->result, ufbx_texture_file, num_files);
ufbxi_check(files);
uc->scene.texture_files.data = files;
uc->scene.texture_files.count = num_files;
ufbxi_texture_file_entry *entries = (ufbxi_texture_file_entry*)uc->texture_file_map.items;
for (size_t i = 0; i < num_files; i++) {
memcpy(&files[i], entries[i].file, sizeof(ufbx_texture_file));
}
return 1;
}
typedef struct {
ufbx_texture *texture;
size_t order;
} ufbxi_ordered_texture;
ufbxi_noinline static bool ufbxi_ordered_texture_less_texture(void *user, const void *va, const void *vb)
{
(void)user;
const ufbxi_ordered_texture *a = (const ufbxi_ordered_texture*)va, *b = (const ufbxi_ordered_texture*)vb;
return a->texture < b->texture;
}
ufbxi_noinline static bool ufbxi_ordered_texture_less_order(void *user, const void *va, const void *vb)
{
(void)user;
const ufbxi_ordered_texture *a = (const ufbxi_ordered_texture*)va, *b = (const ufbxi_ordered_texture*)vb;
return a->order < b->order;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_deduplicate_textures(ufbxi_context *uc, ufbxi_buf *dst_buf, ufbxi_ordered_texture **p_dst, size_t *p_dst_count, size_t count)
{
ufbxi_ordered_texture *textures = ufbxi_push_pop(dst_buf, &uc->tmp_stack, ufbxi_ordered_texture, count);
ufbxi_check(textures);
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_ordered_texture)));
ufbxi_stable_sort(sizeof(ufbxi_ordered_texture), 16, textures, uc->tmp_arr, count, &ufbxi_ordered_texture_less_texture, NULL);
// Remove adjacent duplicates
size_t dst_ix = 0;
for (size_t src_ix = 0; src_ix < count; src_ix++) {
if (src_ix > 0 && textures[src_ix - 1].texture == textures[src_ix].texture) {
continue;
} else {
if (src_ix != dst_ix) {
textures[dst_ix] = textures[src_ix];
}
dst_ix++;
}
}
size_t new_count = dst_ix;
ufbxi_stable_sort(sizeof(ufbxi_ordered_texture), 16, textures, uc->tmp_arr, new_count, &ufbxi_ordered_texture_less_order, NULL);
*p_dst_count = new_count;
*p_dst = textures;
return 1;
}
typedef enum {
UFBXI_FILE_TEXTURE_FETCH_INITIAL,
UFBXI_FILE_TEXTURE_FETCH_STARTED,
UFBXI_FILE_TEXTURE_FETCH_FINISHED,
} ufbxi_file_texture_fetch_state;
// Populate `ufbx_texture.file_textures[]` arrays.
ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_file_textures(ufbxi_context *uc)
{
// We keep pointers to `ufbx_texture` in `tmp_stack` as a working set, since we don't know
// how deep the shader graphs might be.
// Start by pushing all the textures into the stack
size_t num_stack_textures = uc->scene.textures.count;
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, num_stack_textures, uc->scene.textures.data));
// Compressed `ufbxi_file_texture_fetch_state`
uint8_t *states = ufbxi_push_zero(&uc->tmp, uint8_t, uc->scene.textures.count);
ufbxi_check(states);
while (num_stack_textures-- > 0) {
ufbx_texture *texture = NULL;
ufbxi_pop(&uc->tmp_stack, ufbx_texture*, 1, &texture);
ufbxi_file_texture_fetch_state state = (ufbxi_file_texture_fetch_state)states[texture->typed_id];
if (state == UFBXI_FILE_TEXTURE_FETCH_FINISHED) continue;
ufbx_shader_texture *shader = texture->shader;
if (state == UFBXI_FILE_TEXTURE_FETCH_STARTED) {
states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_FINISHED;
// HACK: Reuse `tmp_parse` for storing intermediate information as we can clear it.
ufbxi_buf_clear(&uc->tmp_parse);
// Now all non-cyclical dependents should be processed.
size_t num_deps = 0;
if (texture->type == UFBX_TEXTURE_FILE) {
ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1);
ufbxi_check(dst);
dst->texture = texture;
dst->order = num_deps++;
}
ufbxi_for_list(ufbx_texture_layer, layer, texture->layers) {
ufbx_texture *dep_tex = layer->texture;
if (dep_tex->file_textures.count > 0) {
ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1);
ufbxi_check(dst);
dst->texture = dep_tex;
dst->order = num_deps++;
}
}
if (shader) {
ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) {
ufbx_texture *dep_tex = input->texture;
if (dep_tex && dep_tex->file_textures.count > 0) {
ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1);
ufbxi_check(dst);
dst->texture = dep_tex;
dst->order = num_deps++;
}
}
}
// Deduplicate the direct dependencies first
ufbxi_ordered_texture *deps;
ufbxi_check(ufbxi_deduplicate_textures(uc, &uc->tmp_parse, &deps, &num_deps, num_deps));
if (num_deps == 1) {
// If we have only a single dependency (that is not the same one) we can just copy the pointer
texture->file_textures = deps[0].texture->file_textures;
} else {
// Now collect all the file textures and deduplicate them
size_t num_files = 0;
ufbxi_for(ufbxi_ordered_texture, dep, deps, num_deps) {
ufbxi_for_ptr_list(ufbx_texture, p_tex, dep->texture->file_textures) {
ufbxi_ordered_texture *dst = ufbxi_push(&uc->tmp_stack, ufbxi_ordered_texture, 1);
ufbxi_check(dst);
dst->texture = *p_tex;
dst->order = num_files++;
}
}
// Deduplicate the file textures
ufbxi_ordered_texture *files;
ufbxi_check(ufbxi_deduplicate_textures(uc, &uc->tmp_parse, &files, &num_files, num_files));
texture->file_textures.count = num_files;
texture->file_textures.data = ufbxi_push(&uc->result, ufbx_texture*, num_files);
ufbxi_check(texture->file_textures.data);
for (size_t i = 0; i < num_files; i++) {
texture->file_textures.data[i] = files[i].texture;
}
}
} else {
if (texture->type == UFBX_TEXTURE_FILE) {
// Simple case: Just point to self
texture->file_textures.count = 1;
texture->file_textures.data = ufbxi_push(&uc->result, ufbx_texture*, 1);
ufbxi_check(texture->file_textures.data);
texture->file_textures.data[0] = texture;
// In simple cases we can quit here, for more complex file textures queue
// the texture in case there are other file textures as inputs.
if (!texture->shader) {
states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_FINISHED;
continue;
}
}
// Complex: Process all dependencies first
states[texture->typed_id] = UFBXI_FILE_TEXTURE_FETCH_STARTED;
// Push self first so we can return after processing dependencies
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &texture));
num_stack_textures++;
ufbxi_for_list(ufbx_texture_layer, layer, texture->layers) {
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &layer->texture));
num_stack_textures++;
}
if (shader) {
ufbxi_for_list(ufbx_shader_texture_input, input, shader->inputs) {
if (input->texture) {
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_texture*, 1, &input->texture));
num_stack_textures++;
}
}
}
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static ufbx_node *ufbxi_get_geometry_transform_node(ufbx_element *element)
{
if (element->instances.count == 1) {
ufbx_node *node = element->instances.data[0];
if (node->has_geometry_transform) return node;
}
return NULL;
}
ufbxi_noinline static void ufbxi_mirror_vec3_list(const void *v_list, ufbx_mirror_axis axis, size_t stride)
{
const ufbx_void_list *list = (const ufbx_void_list*)v_list;
if (axis == UFBX_MIRROR_AXIS_NONE || !list || list->count == 0) return;
if (!stride) stride = sizeof(ufbx_vec3);
void *ptr = (char*)list->data + (size_t)((int)axis - 1) * sizeof(ufbx_real);
void *end = (char*)ptr + list->count * stride;
while (ptr != end) {
ufbx_real *v = (ufbx_real*)ptr;
*v = -*v;
ptr = (char*)ptr + stride;
}
}
ufbxi_noinline static void ufbxi_scale_vec3_list(const void *v_list, ufbx_real scale, size_t stride)
{
const ufbx_void_list *list = (const ufbx_void_list*)v_list;
if (!list || list->count == 0) return;
if (!stride) stride = sizeof(ufbx_vec3);
void *ptr = list->data, *end = (char*)ptr + list->count * stride;
while (ptr != end) {
ufbx_vec3 *v = (ufbx_vec3*)ptr;
v->x *= scale;
v->y *= scale;
v->z *= scale;
ptr = (char*)ptr + stride;
}
}
ufbxi_noinline static void ufbxi_transform_vec3_list(const void *v_list, const ufbx_matrix *matrix, size_t stride)
{
const ufbx_void_list *list = (const ufbx_void_list*)v_list;
if (!list || list->count == 0) return;
if (!stride) stride = sizeof(ufbx_vec3);
void *ptr = list->data, *end = (char*)ptr + list->count * stride;
while (ptr != end) {
ufbx_vec3 *v = (ufbx_vec3*)ptr;
*v = ufbx_transform_position(matrix, *v);
ptr = (char*)ptr + stride;
}
}
ufbxi_noinline static void ufbxi_normalize_vec3_list(const ufbx_vec3_list *list)
{
ufbxi_nounroll ufbxi_for_list(ufbx_vec3, normal, *list) {
*normal = ufbxi_normalize3(*normal);
}
}
// Forward declare as we're kind of preprocessing ata here that would usually happen later.
ufbxi_noinline static ufbx_transform ufbxi_get_geometry_transform(const ufbx_props *props, ufbx_node *node);
ufbxi_nodiscard ufbxi_noinline static int ufbxi_flip_attrib_winding(ufbxi_context *uc, ufbx_mesh *mesh, ufbx_uint32_list *indices, bool is_position)
{
// All zero, no flipping needed
if (indices->data == uc->zero_indices || indices->count == 0) return 1;
if (indices->data == mesh->vertex_position.indices.data && !is_position) {
// Sharing indices with vertex position, already flipped.
return 1;
} else if (indices->data == uc->consecutive_indices) {
// Need to duplicate consecutive indices, but we can cache the per mesh.
if (uc->tmp_mesh_consecutive_indices) {
indices->data = uc->tmp_mesh_consecutive_indices;
return 1;
}
indices->data = ufbxi_push_copy(&uc->result, uint32_t, indices->count, indices->data);
ufbxi_check(indices->data);
uc->tmp_mesh_consecutive_indices = indices->data;
}
uint32_t *data = indices->data;
ufbxi_for_list(ufbx_face, face, mesh->faces) {
if (face->num_indices == 0) continue;
size_t begin = face->index_begin + 1;
size_t end = face->index_begin + face->num_indices - 1;
while (begin < end) {
uint32_t tmp = data[begin];
data[begin] = data[end];
data[end] = tmp;
begin++;
end--;
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_flip_winding(ufbxi_context *uc, ufbx_mesh *mesh)
{
uc->tmp_mesh_consecutive_indices = NULL;
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_position.indices, true));
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_normal.indices, false));
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->vertex_crease.indices, false));
if (mesh->uv_sets.count > 0) {
ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) {
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_uv.indices, false));
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_tangent.indices, false));
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_bitangent.indices, false));
}
mesh->vertex_uv = mesh->uv_sets.data[0].vertex_uv;
mesh->vertex_bitangent = mesh->uv_sets.data[0].vertex_bitangent;
mesh->vertex_tangent = mesh->uv_sets.data[0].vertex_tangent;
}
if (mesh->color_sets.count > 0) {
ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) {
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &set->vertex_color.indices, false));
}
mesh->vertex_color = mesh->color_sets.data[0].vertex_color;
}
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->skinned_position.indices, false));
if (mesh->skinned_normal.indices.data != mesh->vertex_normal.indices.data) {
ufbxi_check(ufbxi_flip_attrib_winding(uc, mesh, &mesh->skinned_normal.indices, false));
}
ufbxi_update_vertex_first_index(mesh);
// Mapping from old index values to flipped ones, reserve index -1
// (aka `UFBX_NO_INDEX`) for itself.
if (mesh->edges.count > 0) {
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, (mesh->num_indices + 1) * sizeof(uint32_t)));
uint32_t *index_mapping = (uint32_t*)uc->tmp_arr + 1;
index_mapping[-1] = UFBX_NO_INDEX;
ufbxi_for_list(ufbx_face, face, mesh->faces) {
if (face->num_indices == 0) continue;
uint32_t begin = face->index_begin;
uint32_t count = face->num_indices - 1;
index_mapping[begin] = begin;
for (uint32_t i = 0; i < count; i++) {
index_mapping[begin + 1 + i] = begin + count - i;
}
}
ufbxi_for_list(ufbx_edge, p_edge, mesh->edges) {
uint32_t a = index_mapping[(int32_t)p_edge->a];
uint32_t b = index_mapping[(int32_t)p_edge->b];
p_edge->a = b;
p_edge->b = a;
}
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_modify_geometry(ufbxi_context *uc)
{
bool do_mirror = false;
bool do_winding = uc->opts.reverse_winding;
bool do_scale = false;
bool do_geometry_transforms = false;
if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY
|| uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) {
// Prefetch geometry transforms for processing, they will later be overwritten in `ufbxi_update_node()`.
ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) {
ufbx_node *node = *p_node;
if (node->is_root) continue;
node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node);
if (!ufbxi_is_transform_identity(&node->geometry_transform)) {
node->geometry_to_node = ufbx_transform_to_matrix(&node->geometry_transform);
node->has_geometry_transform = true;
} else {
node->geometry_to_node = ufbx_identity_matrix;
node->has_geometry_transform = false;
}
}
do_geometry_transforms = true;
}
if (uc->mirror_axis != 0) {
do_mirror = true;
}
if (uc->scene.metadata.geometry_scale != 1.0f) {
do_scale = true;
}
ufbx_real geometry_scale = uc->scene.metadata.geometry_scale;
ufbx_mirror_axis mirror_axis = uc->mirror_axis;
ufbxi_for_ptr_list(ufbx_blend_shape, p_shape, uc->scene.blend_shapes) {
ufbx_blend_shape *shape = *p_shape;
if (do_scale) {
ufbxi_scale_vec3_list(&shape->position_offsets, geometry_scale, 0);
}
if (do_mirror) {
ufbxi_mirror_vec3_list(&shape->position_offsets, mirror_axis, 0);
ufbxi_mirror_vec3_list(&shape->normal_offsets, mirror_axis, 0);
}
}
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) {
ufbx_mesh *mesh = *p_mesh;
if (do_scale) {
ufbxi_scale_vec3_list(&mesh->vertex_position.values, geometry_scale, 0);
}
bool do_flip_winding = do_winding;
if (do_mirror) {
ufbxi_mirror_vec3_list(&mesh->vertex_position.values, mirror_axis, 0);
ufbxi_mirror_vec3_list(&mesh->vertex_normal.values, mirror_axis, 0);
ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) {
ufbxi_mirror_vec3_list(&set->vertex_tangent.values, mirror_axis, 0);
ufbxi_mirror_vec3_list(&set->vertex_bitangent.values, mirror_axis, 0);
}
if (!uc->opts.handedness_conversion_retain_winding) {
do_flip_winding = !do_flip_winding;
}
}
// Flip face winding retaining the first vertex
if (do_flip_winding) {
mesh->reversed_winding = true;
ufbxi_check(ufbxi_flip_winding(uc, mesh));
}
ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&mesh->element);
if (do_geometry_transforms && geo_node) {
ufbx_matrix tangent_matrix = geo_node->geometry_to_node;
tangent_matrix.m03 = 0.0f;
tangent_matrix.m13 = 0.0f;
tangent_matrix.m23 = 0.0f;
ufbx_matrix normal_matrix = ufbx_matrix_for_normals(&geo_node->geometry_to_node);
ufbxi_transform_vec3_list(&mesh->vertex_position.values, &geo_node->geometry_to_node, 0);
ufbxi_transform_vec3_list(&mesh->vertex_normal.values, &normal_matrix, 0);
ufbxi_normalize_vec3_list(&mesh->vertex_normal.values);
ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) {
ufbxi_transform_vec3_list(&set->vertex_tangent.values, &tangent_matrix, 0);
ufbxi_transform_vec3_list(&set->vertex_bitangent.values, &tangent_matrix, 0);
ufbxi_normalize_vec3_list(&set->vertex_tangent.values);
ufbxi_normalize_vec3_list(&set->vertex_bitangent.values);
}
}
}
ufbxi_for_ptr_list(ufbx_line_curve, p_curve, uc->scene.line_curves) {
ufbx_line_curve *curve = *p_curve;
if (do_scale) {
ufbxi_scale_vec3_list(&curve->control_points, geometry_scale, 0);
}
if (do_mirror) {
ufbxi_mirror_vec3_list(&curve->control_points, mirror_axis, 0);
}
ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&curve->element);
if (do_geometry_transforms && geo_node) {
ufbxi_transform_vec3_list(&curve->control_points, &geo_node->geometry_to_node, 0);
}
}
ufbxi_for_ptr_list(ufbx_nurbs_curve, p_curve, uc->scene.nurbs_curves) {
ufbx_nurbs_curve *curve = *p_curve;
if (do_scale) {
ufbxi_scale_vec3_list(&curve->control_points, geometry_scale, sizeof(ufbx_vec4));
}
if (do_mirror) {
ufbxi_mirror_vec3_list(&curve->control_points, mirror_axis, sizeof(ufbx_vec4));
}
ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&curve->element);
if (do_geometry_transforms && geo_node) {
ufbxi_transform_vec3_list(&curve->control_points, &geo_node->geometry_to_node, sizeof(ufbx_vec4));
}
}
ufbxi_for_ptr_list(ufbx_nurbs_surface, p_surface, uc->scene.nurbs_surfaces) {
ufbx_nurbs_surface *surface = *p_surface;
if (do_scale) {
ufbxi_scale_vec3_list(&surface->control_points, geometry_scale, sizeof(ufbx_vec4));
}
if (do_mirror) {
ufbxi_mirror_vec3_list(&surface->control_points, mirror_axis, sizeof(ufbx_vec4));
}
ufbx_node *geo_node = ufbxi_get_geometry_transform_node(&surface->element);
if (do_geometry_transforms && geo_node) {
ufbxi_transform_vec3_list(&surface->control_points, &geo_node->geometry_to_node, sizeof(ufbx_vec4));
}
}
if (uc->opts.geometry_transform_handling != UFBX_GEOMETRY_TRANSFORM_HANDLING_PRESERVE) {
// Reset all geometry transforms if we're not preserving them
ufbx_props *defaults = NULL;
ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) {
ufbx_node *node = *p_node;
if (!defaults) defaults = node->props.defaults;
if (node->has_geometry_transform) {
ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricTranslation, 0.0f);
ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricRotation, 0.0f);
ufbxi_set_own_prop_vec3_uniform(&node->props, ufbxi_GeometricScaling, 1.0f);
}
}
if (defaults) {
ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricTranslation, 0.0f);
ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricRotation, 0.0f);
ufbxi_set_own_prop_vec3_uniform(defaults, ufbxi_GeometricScaling, 1.0f);
}
}
return 1;
}
ufbxi_noinline static void ufbxi_postprocess_scene(ufbxi_context *uc)
{
if (uc->opts.normalize_normals || uc->opts.normalize_tangents) {
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) {
ufbx_mesh *mesh = *p_mesh;
if (uc->opts.normalize_normals) {
ufbxi_normalize_vec3_list(&mesh->vertex_normal.values);
}
if (uc->opts.normalize_tangents) {
ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) {
ufbxi_normalize_vec3_list(&mesh->vertex_tangent.values);
ufbxi_normalize_vec3_list(&mesh->vertex_bitangent.values);
}
}
}
}
if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) {
uc->scene.metadata.ortho_size_unit = 1.0f / uc->scene.metadata.geometry_scale;
} else {
uc->scene.metadata.ortho_size_unit = 30.0f;
}
}
ufbxi_noinline static size_t ufbxi_next_path_segment(const char *data, size_t begin, size_t length)
{
for (size_t i = begin; i < length; i++) {
if (data[i] == '/' || data[i] == '\\') {
return i;
}
}
return length;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_absolute_to_relative_path(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_rel, const ufbxi_strblob *p_src, bool raw)
{
const char *rel = ufbxi_strblob_data(p_rel, raw);
const char *src = ufbxi_strblob_data(p_src, raw);
size_t rel_length = ufbxi_strblob_length(p_rel, raw);
size_t src_length = ufbxi_strblob_length(p_src, raw);
if (rel_length == 0 || src_length == 0) return 1;
// Absolute paths must start with the same character (either drive or '/')
if (rel[0] != src[0]) return 1;
// Find the last directory of the path we want to be relative to
while (rel_length > 0 && (rel[rel_length - 1] != '/' && rel[rel_length - 1] != '\\')) {
rel_length--;
}
if (rel_length == 0) return 1;
char separator = rel[rel_length - 1];
size_t max_length = rel_length * 2 + src_length;
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, max_length));
char *tmp = uc->tmp_arr;
size_t tmp_length = 0;
size_t rel_begin = 0;
size_t src_begin = 0;
while (rel_begin < rel_length && src_begin < src_length) {
size_t rel_end = ufbxi_next_path_segment(rel, rel_begin, rel_length);
size_t src_end = ufbxi_next_path_segment(src, src_begin, src_length);
if (rel_end != src_end || memcmp(rel + rel_begin, src + src_begin, src_end - src_begin) != 0) break;
rel_begin = rel_end + 1;
src_begin = src_end + 1;
}
while (rel_begin < rel_length) {
size_t rel_end = ufbxi_next_path_segment(rel, rel_begin, rel_length);
tmp[tmp_length++] = '.';
tmp[tmp_length++] = '.';
tmp[tmp_length++] = separator;
rel_begin = rel_end + 1;
}
while (src_begin < src_length) {
size_t src_end = ufbxi_next_path_segment(src, src_begin, src_length);
size_t len = src_end - src_begin;
memcpy(tmp + tmp_length, src + src_begin, len);
tmp_length += len;
if (src_end < src_length) {
tmp[tmp_length++] = separator;
}
src_begin = src_end + 1;
}
ufbx_assert(tmp_length <= max_length);
const char *dst = ufbxi_push_string(&uc->string_pool, tmp, tmp_length, NULL, true);
ufbxi_check(dst);
ufbxi_strblob_set(p_dst, dst, tmp_length, raw);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_filenames(ufbxi_context *uc, ufbxi_strblob *filename, ufbxi_strblob *absolute_filename, ufbxi_strblob *relative_filename, bool raw)
{
if (ufbxi_strblob_length(relative_filename, raw) == 0) {
const ufbxi_strblob *original_file_path = raw
? (const ufbxi_strblob*)&uc->scene.metadata.raw_original_file_path
: (const ufbxi_strblob*)&uc->scene.metadata.original_file_path;
ufbxi_check(ufbxi_absolute_to_relative_path(uc, relative_filename, original_file_path, absolute_filename, raw));
}
ufbxi_check(ufbxi_resolve_relative_filename(uc, filename, relative_filename, raw));
return 1;
}
ufbxi_noinline static bool ufbxi_file_content_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbxi_file_content *a = (const ufbxi_file_content*)va, *b = (const ufbxi_file_content*)vb;
return ufbxi_str_less(a->absolute_filename, b->absolute_filename);
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_file_contents(ufbxi_context *uc, ufbxi_file_content *content, size_t count)
{
ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbxi_file_content)));
ufbxi_stable_sort(sizeof(ufbxi_file_content), 32, content, uc->tmp_arr, count, &ufbxi_file_content_less, NULL);
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_file_content(ufbxi_context *uc, ufbx_string *p_filename, ufbx_blob *p_data)
{
if (p_data->size == 0 || p_filename->length == 0) return 1;
ufbxi_file_content *content = ufbxi_push(&uc->tmp_stack, ufbxi_file_content, 1);
ufbxi_check(content);
content->absolute_filename = *p_filename;
content->content = *p_data;
return 1;
}
ufbxi_noinline static void ufbxi_fetch_file_content(ufbxi_context *uc, ufbx_string *p_filename, ufbx_blob *p_data)
{
if (p_data->size > 0) return;
ufbx_string filename = *p_filename;
size_t index = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbxi_file_content, 8, &index, uc->file_content, 0, uc->num_file_content,
( ufbxi_str_less(a->absolute_filename, filename) ),
( a->absolute_filename.data == filename.data ));
if (index != SIZE_MAX) {
*p_data = uc->file_content[index].content;
}
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_file_content(ufbxi_context *uc)
{
size_t initial_stack = uc->tmp_stack.num_items;
ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) {
ufbx_video *video = *p_video;
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&video->filename, (ufbxi_strblob*)&video->absolute_filename, (ufbxi_strblob*)&video->relative_filename, false));
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&video->raw_filename, (ufbxi_strblob*)&video->raw_absolute_filename, (ufbxi_strblob*)&video->raw_relative_filename, true));
ufbxi_check(ufbxi_push_file_content(uc, &video->absolute_filename, &video->content));
}
ufbxi_for_ptr_list(ufbx_audio_clip, p_clip, uc->scene.audio_clips) {
ufbx_audio_clip *clip = *p_clip;
clip->absolute_filename = ufbx_find_string(&clip->props, "Path", ufbx_empty_string);
clip->relative_filename = ufbx_find_string(&clip->props, "RelPath", ufbx_empty_string);
clip->raw_absolute_filename = ufbx_find_blob(&clip->props, "Path", ufbx_empty_blob);
clip->raw_relative_filename = ufbx_find_blob(&clip->props, "RelPath", ufbx_empty_blob);
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&clip->filename, (ufbxi_strblob*)&clip->absolute_filename, (ufbxi_strblob*)&clip->relative_filename, false));
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&clip->raw_filename, (ufbxi_strblob*)&clip->raw_absolute_filename, (ufbxi_strblob*)&clip->raw_relative_filename, true));
ufbxi_check(ufbxi_push_file_content(uc, &clip->absolute_filename, &clip->content));
}
uc->num_file_content = uc->tmp_stack.num_items - initial_stack;
uc->file_content = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_file_content, uc->num_file_content);
ufbxi_check(uc->file_content);
ufbxi_check(ufbxi_sort_file_contents(uc, uc->file_content, uc->num_file_content));
ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) {
ufbx_video *video = *p_video;
ufbxi_fetch_file_content(uc, &video->absolute_filename, &video->content);
}
ufbxi_for_ptr_list(ufbx_audio_clip, p_clip, uc->scene.audio_clips) {
ufbx_audio_clip *clip = *p_clip;
ufbxi_fetch_file_content(uc, &clip->absolute_filename, &clip->content);
}
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_validate_indices(ufbxi_context *uc, ufbx_uint32_list *indices, size_t max_index)
{
if (max_index == 0 && uc->opts.index_error_handling == UFBX_INDEX_ERROR_HANDLING_CLAMP) {
indices->data = NULL;
indices->count = 0;
return 1;
}
ufbxi_nounroll ufbxi_for_list(uint32_t, p_ix, *indices) {
uint32_t ix = *p_ix;
if (ix >= max_index) {
ufbxi_check(ufbxi_fix_index(uc, p_ix, ix, max_index));
}
}
return 1;
}
static bool ufbxi_material_part_usage_less(void *user, const void *va, const void *vb)
{
ufbx_mesh_part *parts = (ufbx_mesh_part*)user;
uint32_t a = *(const uint32_t*)va, b = *(const uint32_t*)vb;
ufbx_mesh_part *pa = &parts[a];
ufbx_mesh_part *pb = &parts[b];
if (pa->face_indices.count == 0 || pb->face_indices.count == 0) {
if (pa->face_indices.count == pb->face_indices.count) return a < b;
return pa->face_indices.count > pb->face_indices.count;
}
return pa->face_indices.data[0] < pb->face_indices.data[0];
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh_material(ufbxi_buf *buf, ufbx_error *error, ufbx_mesh *mesh)
{
size_t num_materials = mesh->materials.count;
size_t num_parts = mesh->material_parts.count;
size_t num_faces = mesh->faces.count;
ufbx_mesh_part *parts = mesh->material_parts.data;
ufbx_assert(!parts || (mesh->material_parts.count == num_materials) || (mesh->material_parts.count == 1 && num_materials == 0));
uint32_t *face_material = mesh->face_material.data;
// Count the number of faces and triangles per material
ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) {
ufbx_face face = mesh->faces.data[i];
uint32_t mat_ix = 0;
if (face_material) {
mat_ix = face_material[i];
if (mat_ix >= num_materials) {
face_material[i] = 0;
mat_ix = 0;
}
}
if (parts) {
ufbxi_mesh_part_add_face(&parts[mat_ix], face.num_indices);
}
}
if (parts) {
// Allocate per-material buffers (clear `num_faces` to 0 to re-use it as
// an index when fetching the face indices).
uint32_t part_index = 0;
ufbxi_for(ufbx_mesh_part, part, parts, num_parts) {
part->index = part_index++;
part->face_indices.count = part->num_faces;
part->face_indices.data = ufbxi_push(buf, uint32_t, part->num_faces);
ufbxi_check_err(error, part->face_indices.data);
part->num_faces = 0;
}
// Fetch the per-material face indices
ufbxi_nounroll for (size_t i = 0; i < num_faces; i++) {
uint32_t mat_ix = face_material ? face_material[i] : 0;
if (mat_ix < num_parts) {
ufbx_mesh_part *part = &parts[mat_ix];
part->face_indices.data[part->num_faces++] = (uint32_t)i;
}
}
mesh->material_part_usage_order.count = num_parts;
mesh->material_part_usage_order.data = ufbxi_push(buf, uint32_t, num_parts);
ufbxi_check_err(error, mesh->material_part_usage_order.data);
for (size_t i = 0; i < num_parts; i++) {
mesh->material_part_usage_order.data[i] = (uint32_t)i;
}
ufbxi_unstable_sort(mesh->material_part_usage_order.data, num_parts, sizeof(uint32_t), &ufbxi_material_part_usage_less, parts);
}
return 1;
}
typedef struct {
ufbxi_refcount refcount;
ufbx_anim anim;
uint32_t magic;
} ufbxi_anim_imp;
ufbxi_nodiscard ufbxi_noinline static int ufbxi_push_anim(ufbxi_context *uc, ufbx_anim **p_anim, ufbx_anim_layer **layers, size_t num_layers)
{
ufbx_anim *anim = ufbxi_push_zero(&uc->result, ufbx_anim, 1);
ufbxi_check(anim);
anim->layers.data = layers;
anim->layers.count = num_layers;
*p_anim = anim;
return 1;
}
ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc)
{
size_t num_elements = uc->num_elements;
uc->scene.elements.count = num_elements;
uc->scene.elements.data = ufbxi_push(&uc->result, ufbx_element*, num_elements);
ufbxi_check(uc->scene.elements.data);
uc->scene.metadata.element_buffer_size = uc->tmp_element_byte_offset;
char *element_data = (char*)ufbxi_push_pop(&uc->result, &uc->tmp_elements, uint64_t, uc->tmp_element_byte_offset/8);
ufbxi_check(element_data);
size_t *element_offsets = ufbxi_push_pop(&uc->tmp, &uc->tmp_element_offsets, size_t, uc->tmp_element_offsets.num_items);
ufbxi_buf_free(&uc->tmp_element_offsets);
ufbxi_check(element_offsets);
for (size_t i = 0; i < num_elements; i++) {
ufbx_element *element = (ufbx_element*)(element_data + element_offsets[i]);
if (element->type == UFBX_ELEMENT_NODE) {
ufbx_node *node = (ufbx_node*)element;
if (node->scale_helper) {
ufbxi_node_extra *extra = (ufbxi_node_extra*)ufbxi_get_element_extra(uc, node->element_id);
ufbx_assert(extra);
node->scale_helper = (ufbx_node*)(element_data + element_offsets[extra->scale_helper_id]);
}
}
uc->scene.elements.data[i] = element;
}
uc->scene.elements.count = num_elements;
ufbxi_buf_free(&uc->tmp_element_offsets);
ufbxi_buf_free(&uc->tmp_elements);
uc->tmp_element_flag = ufbxi_push_zero(&uc->tmp, uint8_t, num_elements);
ufbxi_check(uc->tmp_element_flag);
uc->scene.metadata.original_file_path = ufbx_find_string(&uc->scene.metadata.scene_props, "DocumentUrl", ufbx_empty_string);
uc->scene.metadata.raw_original_file_path = ufbx_find_blob(&uc->scene.metadata.scene_props, "DocumentUrl", ufbx_empty_blob);
// Resolve and add the connections to elements
ufbxi_check(ufbxi_resolve_connections(uc));
ufbxi_check(ufbxi_add_connections_to_elements(uc));
ufbxi_check(ufbxi_linearize_nodes(uc));
for (size_t type = 0; type < UFBX_ELEMENT_TYPE_COUNT; type++) {
size_t num_typed = uc->tmp_typed_element_offsets[type].num_items;
size_t *typed_offsets = ufbxi_push_pop(&uc->tmp, &uc->tmp_typed_element_offsets[type], size_t, num_typed);
ufbxi_buf_free(&uc->tmp_typed_element_offsets[type]);
ufbxi_check(typed_offsets);
ufbx_element_list *typed_elems = &uc->scene.elements_by_type[type];
typed_elems->count = num_typed;
typed_elems->data = ufbxi_push(&uc->result, ufbx_element*, num_typed);
ufbxi_check(typed_elems->data);
for (size_t i = 0; i < num_typed; i++) {
typed_elems->data[i] = (ufbx_element*)(element_data + typed_offsets[i]);
}
ufbxi_buf_free(&uc->tmp_typed_element_offsets[type]);
}
// Create named elements
uc->scene.elements_by_name.count = num_elements;
uc->scene.elements_by_name.data = ufbxi_push(&uc->result, ufbx_name_element, num_elements);
ufbxi_check(uc->scene.elements_by_name.data);
for (size_t i = 0; i < num_elements; i++) {
ufbx_element *elem = uc->scene.elements.data[i];
ufbx_name_element *name_elem = &uc->scene.elements_by_name.data[i];
name_elem->name = elem->name;
name_elem->type = elem->type;
name_elem->_internal_key = ufbxi_get_name_key(elem->name.data, elem->name.length);
name_elem->element = elem;
}
ufbxi_check(ufbxi_sort_name_elements(uc, uc->scene.elements_by_name.data, num_elements));
// Setup node children arrays and attribute pointers/lists
ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) {
ufbx_node *node = *p_node, *parent = node->parent;
if (parent) {
parent->children.count++;
if (parent->children.data == NULL) {
parent->children.data = p_node;
}
if (node->is_geometry_transform_helper) {
parent->geometry_transform_helper = node;
}
// Force top-level nodes to have `UFBX_INHERIT_MODE_NORMAL` to make unit scaling work.
if (parent->is_root && uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT && uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_PRESERVE) {
node->original_inherit_mode = UFBX_INHERIT_MODE_NORMAL;
node->inherit_mode = UFBX_INHERIT_MODE_NORMAL;
}
// RrSs nodes inherit scale from their parent, Rrs ignore the scale of
// their _immediate_ parent, potentially multiple if chained.
if (node->original_inherit_mode == UFBX_INHERIT_MODE_COMPONENTWISE_SCALE) {
node->inherit_scale_node = parent;
} else if (node->original_inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) {
node->inherit_scale_node = parent->inherit_scale_node;
}
}
ufbx_connection_list conns = ufbxi_find_dst_connections(&node->element, NULL);
ufbxi_for_list(ufbx_connection, conn, conns) {
ufbx_element *elem = conn->src;
ufbx_element_type type = elem->type;
if (!(type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB)) continue;
size_t index = node->all_attribs.count++;
if (index == 0) {
node->attrib = elem;
node->attrib_type = type;
} else {
if (index == 1) {
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &node->attrib));
}
ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &elem));
}
switch (elem->type) {
case UFBX_ELEMENT_MESH: node->mesh = (ufbx_mesh*)elem; break;
case UFBX_ELEMENT_LIGHT: node->light = (ufbx_light*)elem; break;
case UFBX_ELEMENT_CAMERA: node->camera = (ufbx_camera*)elem; break;
case UFBX_ELEMENT_BONE: node->bone = (ufbx_bone*)elem; break;
default: /* No shorthand */ break;
}
}
if (node->all_attribs.count > 1) {
node->all_attribs.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_element*, node->all_attribs.count);
ufbxi_check(node->all_attribs.data);
} else if (node->all_attribs.count == 1) {
node->all_attribs.data = &node->attrib;
}
ufbxi_check(ufbxi_fetch_dst_elements(uc, &node->materials, &node->element, false, false, NULL, UFBX_ELEMENT_MATERIAL));
}
// Resolve bind pose bones that don't use the normal connection system
ufbxi_for_ptr_list(ufbx_pose, p_pose, uc->scene.poses) {
ufbx_pose *pose = *p_pose;
// HACK: Transport `ufbxi_tmp_bone_pose` array through the `ufbx_bone_pose` pointer
size_t num_bones = pose->bone_poses.count;
ufbxi_tmp_bone_pose *tmp_poses = (ufbxi_tmp_bone_pose*)pose->bone_poses.data;
pose->bone_poses.data = ufbxi_push(&uc->result, ufbx_bone_pose, num_bones);
ufbxi_check(pose->bone_poses.data);
// Filter only found bones
pose->bone_poses.count = 0;
for (size_t i = 0; i < num_bones; i++) {
ufbx_element *elem = ufbxi_find_element_by_fbx_id(uc, tmp_poses[i].bone_fbx_id);
if (!elem || elem->type != UFBX_ELEMENT_NODE) continue;
ufbx_node *node = (ufbx_node*)elem;
ufbx_bone_pose *bone = &pose->bone_poses.data[pose->bone_poses.count++];
bone->bone_node = node;
bone->bone_to_world = tmp_poses[i].bone_to_world;
if (pose->is_bind_pose) {
if (node->bind_pose == NULL) {
node->bind_pose = pose;
}
ufbx_connection_list node_conns = ufbxi_find_src_connections(elem, NULL);
ufbxi_for_list(ufbx_connection, conn, node_conns) {
if (conn->dst->type != UFBX_ELEMENT_SKIN_CLUSTER) continue;
ufbx_skin_cluster *cluster = (ufbx_skin_cluster*)conn->dst;
if (ufbxi_matrix_all_zero(&cluster->bind_to_world)) {
cluster->bind_to_world = bone->bone_to_world;
}
}
}
}
ufbxi_check(ufbxi_sort_bone_poses(uc, pose));
}
// Fetch pointers that may break elements
// Setup node attribute instances
for (int type = UFBX_ELEMENT_TYPE_FIRST_ATTRIB; type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB; type++) {
ufbxi_for_ptr_list(ufbx_element, p_elem, uc->scene.elements_by_type[type]) {
ufbx_element *elem = *p_elem;
ufbxi_check(ufbxi_fetch_src_elements(uc, &elem->instances, elem, false, true, NULL, UFBX_ELEMENT_NODE));
}
}
bool search_node = uc->version < 7000;
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, uc->scene.skin_clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
cluster->bone_node = (ufbx_node*)ufbxi_fetch_dst_element(&cluster->element, false, NULL, UFBX_ELEMENT_NODE);
}
ufbxi_for_ptr_list(ufbx_skin_deformer, p_skin, uc->scene.skin_deformers) {
ufbx_skin_deformer *skin = *p_skin;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &skin->clusters, &skin->element, false, true, NULL, UFBX_ELEMENT_SKIN_CLUSTER));
// Remove clusters without a valid `bone`
if (!uc->opts.connect_broken_elements) {
size_t num_broken = 0;
for (size_t i = 0; i < skin->clusters.count; i++) {
if (!skin->clusters.data[i]->bone_node) {
num_broken++;
} else if (num_broken > 0) {
skin->clusters.data[i - num_broken] = skin->clusters.data[i];
}
}
skin->clusters.count -= num_broken;
}
size_t total_weights = 0;
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
ufbxi_check(SIZE_MAX - total_weights > cluster->num_weights);
total_weights += cluster->num_weights;
}
size_t num_vertices = 0;
// Iterate through meshes so we can pad the vertices to the largest one
{
ufbx_connection_list conns = ufbxi_find_src_connections(&skin->element, NULL);
ufbxi_for_list(ufbx_connection, conn, conns) {
ufbx_mesh *mesh = NULL;
if (conn->dst_prop.length > 0) continue;
if (conn->dst->type == UFBX_ELEMENT_MESH) {
mesh = (ufbx_mesh*)conn->dst;
} else if (conn->dst->type == UFBX_ELEMENT_NODE) {
ufbx_node *node = (ufbx_node*)conn->dst;
if (node->geometry_transform_helper) node = node->geometry_transform_helper;
mesh = node->mesh;
}
if (!mesh) continue;
num_vertices = ufbxi_max_sz(num_vertices, mesh->num_vertices);
}
}
if (!uc->opts.skip_skin_vertices) {
skin->vertices.count = num_vertices;
skin->vertices.data = ufbxi_push_zero(&uc->result, ufbx_skin_vertex, num_vertices);
ufbxi_check(skin->vertices.data);
skin->weights.count = total_weights;
skin->weights.data = ufbxi_push_zero(&uc->result, ufbx_skin_weight, total_weights);
ufbxi_check(skin->weights.data);
bool retain_all = !uc->opts.clean_skin_weights;
// Count the number of weights per vertex
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
for (size_t i = 0; i < cluster->num_weights; i++) {
uint32_t vertex = cluster->vertices.data[i];
if (vertex < num_vertices && (retain_all || cluster->weights.data[i] > 0.0f)) {
skin->vertices.data[vertex].num_weights++;
}
}
}
ufbx_real default_dq = skin->skinning_method == UFBX_SKINNING_METHOD_DUAL_QUATERNION ? 1.0f : 0.0f;
// Prefix sum to assign the vertex weight offsets and set up default DQ values
uint32_t offset = 0;
uint32_t max_weights = 0;
for (size_t i = 0; i < num_vertices; i++) {
skin->vertices.data[i].weight_begin = offset;
skin->vertices.data[i].dq_weight = default_dq;
uint32_t num_weights = skin->vertices.data[i].num_weights;
offset += num_weights;
skin->vertices.data[i].num_weights = 0;
if (num_weights > max_weights) max_weights = num_weights;
}
ufbx_assert(offset <= total_weights);
skin->max_weights_per_vertex = max_weights;
// Copy the DQ weights to vertices
for (size_t i = 0; i < skin->num_dq_weights; i++) {
uint32_t vertex = skin->dq_vertices.data[i];
if (vertex < num_vertices) {
skin->vertices.data[vertex].dq_weight = skin->dq_weights.data[i];
}
}
// Copy the weights to vertices
uint32_t cluster_index = 0;
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, skin->clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
for (size_t i = 0; i < cluster->num_weights; i++) {
uint32_t vertex = cluster->vertices.data[i];
if (vertex < num_vertices && (retain_all || cluster->weights.data[i] > 0.0f)) {
uint32_t local_index = skin->vertices.data[vertex].num_weights++;
uint32_t index = skin->vertices.data[vertex].weight_begin + local_index;
skin->weights.data[index].cluster_index = cluster_index;
skin->weights.data[index].weight = cluster->weights.data[i];
}
}
cluster_index++;
}
// Sort the vertex weights by descending weight value
ufbxi_check(ufbxi_sort_skin_weights(uc, skin));
}
}
ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, uc->scene.blend_deformers) {
ufbx_blend_deformer *blend = *p_blend;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &blend->channels, &blend->element, false, true, NULL, UFBX_ELEMENT_BLEND_CHANNEL));
}
ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, uc->scene.cache_deformers) {
ufbx_cache_deformer *deformer = *p_deformer;
deformer->channel = ufbx_find_string(&deformer->props, "ChannelName", ufbx_empty_string);
deformer->file = (ufbx_cache_file*)ufbxi_fetch_dst_element(&deformer->element, false, NULL, UFBX_ELEMENT_CACHE_FILE);
}
ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) {
ufbx_cache_file *cache = *p_cache;
cache->absolute_filename = ufbx_find_string(&cache->props, "CacheAbsoluteFileName", ufbx_empty_string);
cache->relative_filename = ufbx_find_string(&cache->props, "CacheFileName", ufbx_empty_string);
cache->raw_absolute_filename = ufbx_find_blob(&cache->props, "CacheAbsoluteFileName", ufbx_empty_blob);
cache->raw_relative_filename = ufbx_find_blob(&cache->props, "CacheFileName", ufbx_empty_blob);
int64_t type = ufbx_find_int(&cache->props, "CacheFileType", 0);
if (type >= 0 && type <= UFBX_CACHE_FILE_FORMAT_MC) {
cache->format = (ufbx_cache_file_format)type;
}
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&cache->filename, (ufbxi_strblob*)&cache->absolute_filename, (ufbxi_strblob*)&cache->relative_filename, false));
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&cache->raw_filename, (ufbxi_strblob*)&cache->raw_absolute_filename, (ufbxi_strblob*)&cache->raw_relative_filename, true));
}
ufbx_assert(uc->tmp_full_weights.num_items == uc->scene.blend_channels.count);
ufbx_real_list *full_weights = ufbxi_push_pop(&uc->tmp, &uc->tmp_full_weights, ufbx_real_list, uc->tmp_full_weights.num_items);
ufbxi_buf_free(&uc->tmp_full_weights);
ufbxi_check(full_weights);
ufbxi_for_ptr_list(ufbx_blend_channel, p_channel, uc->scene.blend_channels) {
ufbx_blend_channel *channel = *p_channel;
ufbxi_check(ufbxi_fetch_blend_keyframes(uc, &channel->keyframes, &channel->element));
for (size_t i = 0; i < channel->keyframes.count; i++) {
ufbx_blend_keyframe *key = &channel->keyframes.data[i];
if (i < full_weights->count) {
key->target_weight = full_weights->data[i] / (ufbx_real)100.0;
} else {
key->target_weight = 1.0f;
}
}
ufbxi_check(ufbxi_sort_blend_keyframes(uc, channel->keyframes.data, channel->keyframes.count));
full_weights++;
if (channel->keyframes.count > 0) {
channel->target_shape = channel->keyframes.data[channel->keyframes.count - 1].shape;
}
}
ufbxi_buf_free(&uc->tmp_full_weights);
{
// Generate and patch procedural index buffers
uint32_t *zero_indices = ufbxi_push(&uc->result, uint32_t, uc->max_zero_indices);
uint32_t *consecutive_indices = ufbxi_push(&uc->result, uint32_t, uc->max_consecutive_indices);
ufbxi_check(zero_indices && consecutive_indices);
memset(zero_indices, 0, sizeof(uint32_t) * uc->max_zero_indices);
for (size_t i = 0; i < uc->max_consecutive_indices; i++) {
consecutive_indices[i] = (uint32_t)i;
}
uc->zero_indices = zero_indices;
uc->consecutive_indices = consecutive_indices;
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) {
ufbx_mesh *mesh = *p_mesh;
ufbxi_patch_index_pointer(uc, &mesh->vertex_position.indices.data);
ufbxi_patch_index_pointer(uc, &mesh->vertex_normal.indices.data);
ufbxi_patch_index_pointer(uc, &mesh->vertex_color.indices.data);
ufbxi_patch_index_pointer(uc, &mesh->vertex_crease.indices.data);
ufbxi_patch_index_pointer(uc, &mesh->face_material.data);
ufbxi_patch_index_pointer(uc, &mesh->face_group.data);
ufbxi_patch_index_pointer(uc, &mesh->skinned_position.indices.data);
ufbxi_patch_index_pointer(uc, &mesh->skinned_normal.indices.data);
ufbxi_for_list(ufbx_uv_set, set, mesh->uv_sets) {
ufbxi_patch_index_pointer(uc, &set->vertex_uv.indices.data);
ufbxi_patch_index_pointer(uc, &set->vertex_bitangent.indices.data);
ufbxi_patch_index_pointer(uc, &set->vertex_tangent.indices.data);
}
ufbxi_for_list(ufbx_color_set, set, mesh->color_sets) {
ufbxi_patch_index_pointer(uc, &set->vertex_color.indices.data);
}
// Generate normals if necessary
if (!mesh->vertex_normal.exists && uc->opts.generate_missing_normals) {
ufbxi_check(ufbxi_generate_normals(uc, mesh));
}
// Assign first UV and color sets as the "canonical" ones
if (mesh->uv_sets.count > 0) {
mesh->vertex_uv = mesh->uv_sets.data[0].vertex_uv;
mesh->vertex_bitangent = mesh->uv_sets.data[0].vertex_bitangent;
mesh->vertex_tangent = mesh->uv_sets.data[0].vertex_tangent;
}
if (mesh->color_sets.count > 0) {
mesh->vertex_color = mesh->color_sets.data[0].vertex_color;
}
if (mesh->face_group_parts.count == 1) {
ufbxi_patch_index_pointer(uc, &mesh->face_group_parts.data[0].face_indices.data);
}
ufbxi_check(ufbxi_fetch_mesh_materials(uc, &mesh->materials, &mesh->element, true));
// Patch materials to instances if necessary
if (mesh->materials.count > 0) {
ufbxi_for_ptr_list(ufbx_node, p_node, mesh->instances) {
ufbx_node *node = *p_node;
if (node->materials.count < mesh->materials.count && mesh->materials.data[0] != NULL) {
ufbx_material **materials = ufbxi_push(&uc->result, ufbx_material*, mesh->materials.count);
ufbxi_check(materials);
ufbxi_nounroll for (size_t i = 0; i < node->materials.count; i++) {
materials[i] = node->materials.data[i];
}
ufbxi_nounroll for (size_t i = node->materials.count; i < mesh->materials.count; i++) {
materials[i] = mesh->materials.data[i];
}
node->materials.data = materials;
node->materials.count = mesh->materials.count;
}
}
}
if (uc->retain_mesh_parts) {
size_t num_parts = ufbxi_max_sz(mesh->materials.count, 1);
mesh->material_parts.data = ufbxi_push_zero(&uc->result, ufbx_mesh_part, num_parts);
ufbxi_check(mesh->material_parts.data);
mesh->material_parts.count = num_parts;
}
if (mesh->materials.count <= 1) {
// Use the shared consecutive index buffer for mesh faces if there's only one material
// See HACK(consecutive-faces) in `ufbxi_read_mesh()`.
if (mesh->material_parts.count > 0) {
ufbx_mesh_part *part = &mesh->material_parts.data[0];
part->num_faces = mesh->num_faces;
part->num_triangles = mesh->num_triangles;
part->num_empty_faces = mesh->num_empty_faces;
part->num_point_faces = mesh->num_point_faces;
part->num_line_faces = mesh->num_line_faces;
part->face_indices.data = uc->consecutive_indices;
part->face_indices.count = mesh->num_faces;
mesh->material_part_usage_order.data = uc->zero_indices;
mesh->material_part_usage_order.count = 1;
}
if (mesh->materials.count == 1) {
mesh->face_material.data = uc->zero_indices;
mesh->face_material.count = mesh->num_faces;
} else {
mesh->face_material.data = NULL;
mesh->face_material.count = 0;
}
} else if (mesh->materials.count > 0) {
ufbxi_check(ufbxi_finalize_mesh_material(&uc->result, &uc->error, mesh));
}
// Fetch deformers
ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->skin_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_SKIN_DEFORMER));
ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->blend_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_BLEND_DEFORMER));
ufbxi_check(ufbxi_fetch_dst_elements(uc, &mesh->cache_deformers, &mesh->element, search_node, true, NULL, UFBX_ELEMENT_CACHE_DEFORMER));
ufbxi_check(ufbxi_fetch_deformers(uc, &mesh->all_deformers, &mesh->element, search_node));
// Vertex position must always exist if not explicitly allowed to be missing
if (!mesh->vertex_position.exists && !uc->opts.allow_missing_vertex_position) {
ufbxi_check(mesh->num_indices == 0);
mesh->vertex_position.exists = true;
mesh->vertex_position.unique_per_vertex = true;
mesh->skinned_position.exists = true;
mesh->skinned_position.unique_per_vertex = true;
}
// Update metadata
if (mesh->max_face_triangles > uc->scene.metadata.max_face_triangles) {
uc->scene.metadata.max_face_triangles = mesh->max_face_triangles;
}
}
}
ufbxi_for_ptr_list(ufbx_stereo_camera, p_stereo, uc->scene.stereo_cameras) {
ufbx_stereo_camera *stereo = *p_stereo;
stereo->left = (ufbx_camera*)ufbxi_fetch_dst_element(&stereo->element, search_node, ufbxi_LeftCamera, UFBX_ELEMENT_CAMERA);
stereo->right = (ufbx_camera*)ufbxi_fetch_dst_element(&stereo->element, search_node, ufbxi_RightCamera, UFBX_ELEMENT_CAMERA);
}
ufbxi_for_ptr_list(ufbx_nurbs_curve, p_curve, uc->scene.nurbs_curves) {
ufbx_nurbs_curve *curve = *p_curve;
ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &curve->basis));
}
ufbxi_for_ptr_list(ufbx_nurbs_surface, p_surface, uc->scene.nurbs_surfaces) {
ufbx_nurbs_surface *surface = *p_surface;
ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &surface->basis_u));
ufbxi_check(ufbxi_finalize_nurbs_basis(uc, &surface->basis_v));
surface->material = (ufbx_material*)ufbxi_fetch_dst_element(&surface->element, true, NULL, UFBX_ELEMENT_MATERIAL);
}
ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, uc->scene.anim_stacks) {
ufbx_anim_stack *stack = *p_stack;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &stack->layers, &stack->element, false, true, NULL, UFBX_ELEMENT_ANIM_LAYER));
ufbxi_check(ufbxi_push_anim(uc, &stack->anim, stack->layers.data, stack->layers.count));
}
ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, uc->scene.anim_layers) {
ufbx_anim_layer *layer = *p_layer;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->anim_values, &layer->element, false, true, NULL, UFBX_ELEMENT_ANIM_VALUE));
ufbxi_check(ufbxi_push_anim(uc, &layer->anim, p_layer, 1));
uint32_t min_id = UINT32_MAX, max_id = 0;
// Combine the animated properties with elements (potentially duplicates!)
size_t num_anim_props = 0;
ufbxi_for_ptr_list(ufbx_anim_value, p_value, layer->anim_values) {
ufbx_anim_value *value = *p_value;
ufbxi_for_list(ufbx_connection, ac, value->element.connections_src) {
if (ac->src_prop.length == 0 && ac->dst_prop.length > 0) {
ufbx_anim_prop *aprop = ufbxi_push(&uc->tmp_stack, ufbx_anim_prop, 1);
uint32_t id = ac->dst->element_id;
min_id = ufbxi_min32(min_id, id);
max_id = ufbxi_max32(max_id, id);
uint32_t id_mask = ufbxi_arraycount(layer->_element_id_bitmask) - 1;
layer->_element_id_bitmask[(id >> 5) & id_mask] |= 1u << (id & 31);
ufbxi_check(aprop);
aprop->anim_value = value;
aprop->element = ac->dst;
aprop->_internal_key = ufbxi_get_name_key(ac->dst_prop.data, ac->dst_prop.length);
aprop->prop_name = ac->dst_prop;
num_anim_props++;
}
}
}
if (min_id != UINT32_MAX) {
layer->_min_element_id = min_id;
layer->_max_element_id = max_id;
}
switch (ufbxi_find_int(&layer->props, ufbxi_BlendMode, 0)) {
case 0: // Additive
layer->blended = true;
layer->additive = true;
break;
case 1: // Override
layer->blended = false;
layer->additive = false;
break;
case 2: // Override Passthrough
layer->blended = true;
layer->additive = false;
break;
default: // Unknown
layer->blended = false;
layer->additive = false;
break;
}
ufbx_prop *weight_prop = ufbxi_find_prop(&layer->props, ufbxi_Weight);
if (weight_prop) {
layer->weight = weight_prop->value_real / (ufbx_real)100.0;
if (layer->weight < 0.0f) layer->weight = 0.0f;
if (layer->weight > 0.99999f) layer->weight = 1.0f;
layer->weight_is_animated = (weight_prop->flags & UFBX_PROP_FLAG_ANIMATED) != 0;
} else {
layer->weight = 1.0f;
layer->weight_is_animated = false;
}
layer->compose_rotation = ufbxi_find_int(&layer->props, ufbxi_RotationAccumulationMode, 0) == 0;
layer->compose_scale = ufbxi_find_int(&layer->props, ufbxi_ScaleAccumulationMode, 0) == 0;
// Add a dummy NULL element animated prop at the end so we can iterate
// animated props without worrying about boundary conditions..
{
ufbx_anim_prop *aprop = ufbxi_push_zero(&uc->tmp_stack, ufbx_anim_prop, 1);
ufbxi_check(aprop);
}
layer->anim_props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_anim_prop, num_anim_props + 1);
ufbxi_check(layer->anim_props.data);
layer->anim_props.count = num_anim_props;
ufbxi_check(ufbxi_sort_anim_props(uc, layer->anim_props.data, layer->anim_props.count));
}
ufbxi_for_ptr_list(ufbx_anim_value, p_value, uc->scene.anim_values) {
ufbx_anim_value *value = *p_value;
// TODO: Search for things like d|Visibility with a constructed name
value->default_value.x = ufbxi_find_real(&value->props, ufbxi_X, value->default_value.x);
value->default_value.x = ufbxi_find_real(&value->props, ufbxi_d_X, value->default_value.x);
value->default_value.y = ufbxi_find_real(&value->props, ufbxi_Y, value->default_value.y);
value->default_value.y = ufbxi_find_real(&value->props, ufbxi_d_Y, value->default_value.y);
value->default_value.z = ufbxi_find_real(&value->props, ufbxi_Z, value->default_value.z);
value->default_value.z = ufbxi_find_real(&value->props, ufbxi_d_Z, value->default_value.z);
ufbxi_for_list(ufbx_connection, conn, value->element.connections_dst) {
if (conn->src->type == UFBX_ELEMENT_ANIM_CURVE && conn->src_prop.length == 0) {
ufbx_anim_curve *curve = (ufbx_anim_curve*)conn->src;
uint32_t index = 0;
const char *name = conn->dst_prop.data;
if (name == ufbxi_Y || name == ufbxi_d_Y) index = 1;
if (name == ufbxi_Z || name == ufbxi_d_Z) index = 2;
ufbx_prop *prop = ufbx_find_prop_len(&value->props, conn->dst_prop.data, conn->dst_prop.length);
if (prop) {
value->default_value.v[index] = prop->value_real;
}
value->curves[index] = curve;
}
}
}
ufbxi_for_ptr_list(ufbx_shader, p_shader, uc->scene.shaders) {
ufbx_shader *shader = *p_shader;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &shader->bindings, &shader->element, false, false, NULL, UFBX_ELEMENT_SHADER_BINDING));
ufbx_prop *api = ufbx_find_prop(&shader->props, "RenderAPI");
if (api) {
if (!strcmp(api->value_str.data, "ARNOLD_SHADER_ID")) {
shader->type = UFBX_SHADER_ARNOLD_STANDARD_SURFACE;
} else if (!strcmp(api->value_str.data, "OSL")) {
shader->type = UFBX_SHADER_OSL_STANDARD_SURFACE;
} else if (!strcmp(api->value_str.data, "SFX_PBS_SHADER")) {
shader->type = UFBX_SHADER_SHADERFX_GRAPH;
}
}
}
ufbxi_for_ptr_list(ufbx_material, p_material, uc->scene.materials) {
ufbx_material *material = *p_material;
material->shader = (ufbx_shader*)ufbxi_fetch_src_element(&material->element, false, NULL, UFBX_ELEMENT_SHADER);
if (!strcmp(material->shading_model_name.data, "lambert") || !strcmp(material->shading_model_name.data, "Lambert")) {
material->shader_type = UFBX_SHADER_FBX_LAMBERT;
} else if (!strcmp(material->shading_model_name.data, "phong") || !strcmp(material->shading_model_name.data, "Phong")) {
material->shader_type = UFBX_SHADER_FBX_PHONG;
}
if (material->shader) {
material->shader_type = material->shader->type;
} else {
if (uc->opts.use_blender_pbr_material && uc->exporter == UFBX_EXPORTER_BLENDER_BINARY && uc->exporter_version >= ufbx_pack_version(4,12,0)) {
material->shader_type = UFBX_SHADER_BLENDER_PHONG;
}
// TODO: Is this too strict?
if (material->shader_type == UFBX_SHADER_UNKNOWN) {
uint32_t classid_a = (uint32_t)(uint64_t)ufbx_find_int(&material->props, "3dsMax|ClassIDa", 0);
uint32_t classid_b = (uint32_t)(uint64_t)ufbx_find_int(&material->props, "3dsMax|ClassIDb", 0);
if (classid_a == 0x3d6b1cecu && classid_b == 0xdeadc001u) {
material->shader_type = UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL;
material->shader_prop_prefix.data = "3dsMax|Parameters|";
material->shader_prop_prefix.length = strlen("3dsMax|Parameters|");
} else if (classid_a == 0x38420192u && classid_b == 0x45fe4e1bu) {
material->shader_type = UFBX_SHADER_GLTF_MATERIAL;
material->shader_prop_prefix.data = "3dsMax|";
material->shader_prop_prefix.length = strlen("3dsMax|");
} else if (classid_a == 0xd00f1e00u && classid_b == 0xbe77e500u) {
material->shader_type = UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH;
material->shader_prop_prefix.data = "3dsMax|main|";
material->shader_prop_prefix.length = strlen("3dsMax|main|");
} else if (classid_a == 0xd00f1e00u && classid_b == 0x01dbad33u) {
material->shader_type = UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS;
material->shader_prop_prefix.data = "3dsMax|main|";
material->shader_prop_prefix.length = strlen("3dsMax|main|");
}
}
}
ufbxi_check(ufbxi_fetch_textures(uc, &material->textures, &material->element, false));
}
// Ugh.. Patch the textures from meshes for legacy LayerElement-style textures
{
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, uc->scene.meshes) {
ufbx_mesh *mesh = *p_mesh;
size_t num_materials = mesh->materials.count;
ufbxi_mesh_extra *extra = (ufbxi_mesh_extra*)ufbxi_get_element_extra(uc, mesh->element.element_id);
if (!extra) continue;
if (num_materials == 0) continue;
// TODO: This leaks currently to result, probably doesn't matter..
ufbx_texture_list textures;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &textures, &mesh->element, true, false, NULL, UFBX_ELEMENT_TEXTURE));
size_t num_material_textures = 0;
ufbxi_for(ufbxi_tmp_mesh_texture, tex, extra->texture_arr, extra->texture_count) {
if (tex->all_same) {
int32_t texture_id = tex->num_faces > 0 ? (int32_t)tex->face_texture[0] : 0;
if (texture_id >= 0 && (size_t)texture_id < textures.count) {
ufbxi_tmp_material_texture *mat_texs = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, num_materials);
ufbxi_check(mat_texs);
num_material_textures += num_materials;
for (size_t i = 0; i < num_materials; i++) {
mat_texs[i].material_id = (int32_t)i;
mat_texs[i].texture_id = texture_id;
mat_texs[i].prop_name = tex->prop_name;
}
}
} else if (mesh->face_material.count) {
size_t num_faces = ufbxi_min_sz(tex->num_faces, mesh->num_faces);
int32_t prev_material = -1;
int32_t prev_texture = -1;
for (size_t i = 0; i < num_faces; i++) {
int32_t texture_id = (int32_t)tex->face_texture[i];
int32_t material_id = (int32_t)mesh->face_material.data[i];
if (texture_id < 0 || (size_t)texture_id >= textures.count) continue;
if (material_id < 0 || (size_t)material_id >= num_materials) continue;
if (material_id == prev_material && texture_id == prev_texture) continue;
prev_material = material_id;
prev_texture = texture_id;
ufbxi_tmp_material_texture *mat_tex = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, 1);
ufbxi_check(mat_tex);
mat_tex->material_id = material_id;
mat_tex->texture_id = texture_id;
mat_tex->prop_name = tex->prop_name;
num_material_textures++;
}
}
}
// Push a sentinel material texture to the end so we don't need to
// duplicate the material texture flushing code twice.
{
ufbxi_tmp_material_texture *mat_tex = ufbxi_push(&uc->tmp_stack, ufbxi_tmp_material_texture, 1);
ufbxi_check(mat_tex);
mat_tex->material_id = -1;
mat_tex->texture_id = -1;
mat_tex->prop_name = ufbx_empty_string;
}
ufbxi_tmp_material_texture *mat_texs = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_tmp_material_texture, num_material_textures + 1);
ufbxi_check(mat_texs);
ufbxi_check(ufbxi_sort_tmp_material_textures(uc, mat_texs, num_material_textures));
int32_t prev_material = -2;
int32_t prev_texture = -2;
const char *prev_prop = NULL;
size_t num_textures_in_material = 0;
for (size_t i = 0; i < num_material_textures + 1; i++) {
ufbxi_tmp_material_texture mat_tex = mat_texs[i];
if (mat_tex.material_id != prev_material) {
if (prev_material >= 0 && num_textures_in_material > 0) {
ufbx_material *mat = mesh->materials.data[prev_material];
if (mat && mat->textures.count == 0) {
ufbx_material_texture *texs = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_material_texture, num_textures_in_material);
ufbxi_check(texs);
mat->textures.data = texs;
mat->textures.count = num_textures_in_material;
} else {
ufbxi_pop(&uc->tmp_stack, ufbx_material_texture, num_textures_in_material, NULL);
}
}
if (mat_tex.material_id < 0) break;
prev_material = mat_tex.material_id;
prev_texture = -1;
prev_prop = NULL;
num_textures_in_material = 0;
}
if (mat_tex.texture_id == prev_texture && mat_tex.prop_name.data == prev_prop) continue;
prev_texture = mat_tex.texture_id;
prev_prop = mat_tex.prop_name.data;
ufbx_material_texture *tex = ufbxi_push(&uc->tmp_stack, ufbx_material_texture, 1);
ufbxi_check(tex);
ufbx_assert(prev_texture >= 0 && (size_t)prev_texture < textures.count);
tex->texture = textures.data[prev_texture];
tex->shader_prop = tex->material_prop = mat_tex.prop_name;
num_textures_in_material++;
}
}
}
ufbxi_check(ufbxi_resolve_file_content(uc));
ufbxi_for_ptr_list(ufbx_texture, p_texture, uc->scene.textures) {
ufbx_texture *texture = *p_texture;
ufbxi_texture_extra *extra = (ufbxi_texture_extra*)ufbxi_get_element_extra(uc, texture->element.element_id);
ufbx_prop *uv_set = ufbxi_find_prop(&texture->props, ufbxi_UVSet);
if (uv_set) {
texture->uv_set = uv_set->value_str;
} else {
texture->uv_set = ufbx_empty_string;
}
texture->video = (ufbx_video*)ufbxi_fetch_dst_element(&texture->element, false, NULL, UFBX_ELEMENT_VIDEO);
if (texture->video) {
texture->content = texture->video->content;
}
ufbxi_check(ufbxi_finalize_shader_texture(uc, texture));
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&texture->filename, (ufbxi_strblob*)&texture->absolute_filename, (ufbxi_strblob*)&texture->relative_filename, false));
ufbxi_check(ufbxi_resolve_filenames(uc, (ufbxi_strblob*)&texture->raw_filename, (ufbxi_strblob*)&texture->raw_absolute_filename, (ufbxi_strblob*)&texture->raw_relative_filename, true));
// Fetch layered texture layers and patch alphas/blend modes
if (texture->type == UFBX_TEXTURE_LAYERED) {
ufbxi_check(ufbxi_fetch_texture_layers(uc, &texture->layers, &texture->element));
if (extra) {
for (size_t i = 0, num = ufbxi_min_sz(extra->num_alphas, texture->layers.count); i < num; i++) {
texture->layers.data[i].alpha = extra->alphas[i];
}
for (size_t i = 0, num = ufbxi_min_sz(extra->num_blend_modes, texture->layers.count); i < num; i++) {
int32_t mode = extra->blend_modes[i];
if (mode >= 0 && mode < UFBX_BLEND_OVERLAY) {
texture->layers.data[i].blend_mode = (ufbx_blend_mode)mode;
}
}
}
}
ufbxi_check(ufbxi_insert_texture_file(uc, texture));
}
ufbxi_propagate_main_textures(&uc->scene);
ufbxi_check(ufbxi_pop_texture_files(uc));
// Second pass to fetch material maps
ufbxi_for_ptr_list(ufbx_material, p_material, uc->scene.materials) {
ufbx_material *material = *p_material;
ufbxi_check(ufbxi_sort_material_textures(uc, material->textures.data, material->textures.count));
ufbxi_fetch_maps(&uc->scene, material);
// Fetch `ufbx_material_texture.shader_prop` names
if (material->shader) {
ufbxi_for_ptr_list(ufbx_shader_binding, p_binding, material->shader->bindings) {
ufbx_shader_binding *binding = *p_binding;
ufbxi_for_list(ufbx_shader_prop_binding, prop, binding->prop_bindings) {
ufbx_string name = prop->material_prop;
size_t index = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_material_texture, 4, &index, material->textures.data, 0, material->textures.count,
( ufbxi_str_less(a->material_prop, name) ), ( a->material_prop.data == name.data ));
for (; index < material->textures.count && material->textures.data[index].shader_prop.data == name.data; index++) {
material->textures.data[index].shader_prop = prop->shader_prop;
}
}
}
}
}
ufbxi_for_ptr_list(ufbx_display_layer, p_layer, uc->scene.display_layers) {
ufbx_display_layer *layer = *p_layer;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->nodes, &layer->element, false, true, NULL, UFBX_ELEMENT_NODE));
}
ufbxi_for_ptr_list(ufbx_selection_set, p_set, uc->scene.selection_sets) {
ufbx_selection_set *set = *p_set;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &set->nodes, &set->element, false, true, NULL, UFBX_ELEMENT_SELECTION_NODE));
}
ufbxi_for_ptr_list(ufbx_selection_node, p_node, uc->scene.selection_nodes) {
ufbx_selection_node *node = *p_node;
node->target_node = (ufbx_node*)ufbxi_fetch_dst_element(&node->element, false, NULL, UFBX_ELEMENT_NODE);
node->target_mesh = (ufbx_mesh*)ufbxi_fetch_dst_element(&node->element, false, NULL, UFBX_ELEMENT_MESH);
if (!node->target_mesh && node->target_node) {
node->target_mesh = node->target_node->mesh;
} else if (!node->target_node && node->target_mesh && node->target_mesh->instances.count > 0) {
node->target_node = node->target_mesh->instances.data[0];
}
ufbx_mesh *mesh = node->target_mesh;
if (mesh) {
ufbxi_check(ufbxi_validate_indices(uc, &node->vertices, mesh->num_vertices));
ufbxi_check(ufbxi_validate_indices(uc, &node->edges, mesh->num_edges));
ufbxi_check(ufbxi_validate_indices(uc, &node->faces, mesh->num_faces));
}
}
ufbxi_for_ptr_list(ufbx_constraint, p_constraint, uc->scene.constraints) {
ufbx_constraint *constraint = *p_constraint;
size_t tmp_base = uc->tmp_stack.num_items;
// Find property connections in _both_ src and dst connections as they are inconsistent
// in pre-7000 files. For example "Constrained Object" is a "PO" connection in 6100.
ufbxi_for_list(ufbx_connection, conn, constraint->element.connections_src) {
if (conn->src_prop.length == 0 || conn->dst->type != UFBX_ELEMENT_NODE) continue;
ufbxi_check(ufbxi_add_constraint_prop(uc, constraint, (ufbx_node*)conn->dst, conn->src_prop.data));
}
ufbxi_for_list(ufbx_connection, conn, constraint->element.connections_dst) {
if (conn->dst_prop.length == 0 || conn->src->type != UFBX_ELEMENT_NODE) continue;
ufbxi_check(ufbxi_add_constraint_prop(uc, constraint, (ufbx_node*)conn->src, conn->dst_prop.data));
}
size_t num_targets = uc->tmp_stack.num_items - tmp_base;
constraint->targets.count = num_targets;
constraint->targets.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_constraint_target, num_targets);
ufbxi_check(constraint->targets.data);
}
ufbxi_for_ptr_list(ufbx_audio_layer, p_layer, uc->scene.audio_layers) {
ufbx_audio_layer *layer = *p_layer;
ufbxi_check(ufbxi_fetch_dst_elements(uc, &layer->clips, &layer->element, false, true, NULL, UFBX_ELEMENT_AUDIO_CLIP));
}
ufbxi_for_ptr_list(ufbx_lod_group, p_lod, uc->scene.lod_groups) {
ufbxi_check(ufbxi_finalize_lod_group(uc, *p_lod));
}
ufbxi_check(ufbxi_fetch_file_textures(uc));
// NOTE: This will be patched over in `ufbxi_update_scene()` if there are `anim_layers`
if (uc->scene.anim_layers.count == 0) {
ufbxi_check(ufbxi_push_anim(uc, &uc->scene.anim, NULL, 0));
}
uc->scene.metadata.ktime_second = uc->ktime_sec;
// Maya seems to use scale of 100/3, Blender binary uses exactly 33, ASCII has always value of 1.0
if (uc->version < 6000) {
uc->scene.metadata.bone_prop_size_unit = 1.0f;
} else if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY) {
uc->scene.metadata.bone_prop_size_unit = 33.0f;
} else if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) {
uc->scene.metadata.bone_prop_size_unit = 1.0f;
} else {
uc->scene.metadata.bone_prop_size_unit = (ufbx_real)(100.0/3.0);
}
if (uc->exporter == UFBX_EXPORTER_BLENDER_ASCII) {
uc->scene.metadata.bone_prop_limb_length_relative = false;
} else {
uc->scene.metadata.bone_prop_limb_length_relative = true;
}
return 1;
}
// -- Interpret the read scene
static ufbxi_forceinline void ufbxi_add_translate(ufbx_transform *t, ufbx_vec3 v)
{
t->translation.x += v.x;
t->translation.y += v.y;
t->translation.z += v.z;
}
static ufbxi_forceinline void ufbxi_sub_translate(ufbx_transform *t, ufbx_vec3 v)
{
t->translation.x -= v.x;
t->translation.y -= v.y;
t->translation.z -= v.z;
}
static ufbxi_forceinline void ufbxi_mul_scale(ufbx_transform *t, ufbx_vec3 v)
{
t->translation.x *= v.x;
t->translation.y *= v.y;
t->translation.z *= v.z;
t->scale.x *= v.x;
t->scale.y *= v.y;
t->scale.z *= v.z;
}
static ufbxi_forceinline void ufbxi_mul_scale_real(ufbx_transform *t, ufbx_real v)
{
t->translation.x *= v;
t->translation.y *= v;
t->translation.z *= v;
t->scale.x *= v;
t->scale.y *= v;
t->scale.z *= v;
}
static ufbxi_noinline ufbx_quat ufbxi_mul_quat(ufbx_quat a, ufbx_quat b)
{
ufbx_quat r;
r.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y;
r.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x;
r.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w;
r.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z;
return r;
}
static ufbxi_forceinline void ufbxi_add_weighted_vec3(ufbx_vec3 *r, ufbx_vec3 b, ufbx_real w)
{
r->x += b.x * w;
r->y += b.y * w;
r->z += b.z * w;
}
static ufbxi_forceinline void ufbxi_add_weighted_quat(ufbx_quat *r, ufbx_quat b, ufbx_real w)
{
r->x += b.x * w;
r->y += b.y * w;
r->z += b.z * w;
r->w += b.w * w;
}
static ufbxi_noinline void ufbxi_add_weighted_mat(ufbx_matrix *r, const ufbx_matrix *b, ufbx_real w)
{
ufbxi_add_weighted_vec3(&r->cols[0], b->cols[0], w);
ufbxi_add_weighted_vec3(&r->cols[1], b->cols[1], w);
ufbxi_add_weighted_vec3(&r->cols[2], b->cols[2], w);
ufbxi_add_weighted_vec3(&r->cols[3], b->cols[3], w);
}
static void ufbxi_mul_rotate(ufbx_transform *t, ufbx_vec3 v, ufbx_rotation_order order)
{
if (ufbxi_is_vec3_zero(v)) return;
ufbx_quat q = ufbx_euler_to_quat(v, order);
if (t->rotation.w != 1.0) {
t->rotation = ufbxi_mul_quat(q, t->rotation);
} else {
t->rotation = q;
}
if (!ufbxi_is_vec3_zero(t->translation)) {
t->translation = ufbx_quat_rotate_vec3(q, t->translation);
}
}
static void ufbxi_mul_rotate_quat(ufbx_transform *t, ufbx_quat q)
{
if (ufbxi_is_quat_identity(q)) return;
if (t->rotation.w != 1.0) {
t->rotation = ufbxi_mul_quat(q, t->rotation);
} else {
t->rotation = q;
}
if (!ufbxi_is_vec3_zero(t->translation)) {
t->translation = ufbx_quat_rotate_vec3(q, t->translation);
}
}
static void ufbxi_mul_inv_rotate(ufbx_transform *t, ufbx_vec3 v, ufbx_rotation_order order)
{
if (ufbxi_is_vec3_zero(v)) return;
ufbx_quat q = ufbx_euler_to_quat(v, order);
q.x = -q.x; q.y = -q.y; q.z = -q.z;
if (t->rotation.w != 1.0) {
t->rotation = ufbxi_mul_quat(q, t->rotation);
} else {
t->rotation = q;
}
if (!ufbxi_is_vec3_zero(t->translation)) {
t->translation = ufbx_quat_rotate_vec3(q, t->translation);
}
}
// -- Updating state from properties
ufbxi_forceinline static void ufbxi_mirror_translation(ufbx_vec3 *p_vec, ufbx_mirror_axis axis)
{
ufbxi_dev_assert(axis);
p_vec->v[axis - 1] = -p_vec->v[axis - 1];
}
ufbxi_forceinline static void ufbxi_mirror_rotation(ufbx_quat *p_quat, ufbx_mirror_axis axis)
{
ufbxi_dev_assert(axis);
p_quat->v[axis % 3] = -p_quat->v[axis % 3];
p_quat->v[(axis + 1) % 3] = -p_quat->v[(axis + 1) % 3];
}
ufbxi_noinline static ufbx_transform ufbxi_get_geometry_transform(const ufbx_props *props, ufbx_node *node)
{
ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_GeometricRotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_GeometricScaling, 1.0f, 1.0f, 1.0f);
ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }};
// WorldTransform = ParentWorldTransform * T * R * S * (OT * OR * OS)
ufbxi_mul_scale(&t, scaling);
ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxi_add_translate(&t, translation);
if (node->has_adjust_transform) {
t.translation.x *= node->adjust_translation_scale;
t.translation.y *= node->adjust_translation_scale;
t.translation.z *= node->adjust_translation_scale;
}
if (node->adjust_mirror_axis) {
ufbxi_mirror_translation(&t.translation, node->adjust_mirror_axis);
ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis);
}
return t;
}
ufbxi_noinline static ufbx_transform ufbxi_get_transform(const ufbx_props *props, ufbx_rotation_order order, const ufbx_node *node, const ufbx_vec3 *translation_scale)
{
ufbx_vec3 scale_pivot = ufbxi_find_vec3(props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rot_pivot = ufbxi_find_vec3(props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f);
ufbx_vec3 scale_offset = ufbxi_find_vec3(props, ufbxi_ScalingOffset, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rot_offset = ufbxi_find_vec3(props, ufbxi_RotationOffset, 0.0f, 0.0f, 0.0f);
ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Lcl_Translation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f);
ufbx_vec3 pre_rotation = ufbxi_find_vec3(props, ufbxi_PreRotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 post_rotation = ufbxi_find_vec3(props, ufbxi_PostRotation, 0.0f, 0.0f, 0.0f);
ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }};
// WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1
// NOTE: Rpost is inverted (!) after converting from PostRotation Euler angles
if (translation_scale) {
translation.x *= translation_scale->x;
translation.y *= translation_scale->y;
translation.z *= translation_scale->z;
}
if (node->has_adjust_transform) {
ufbxi_mul_rotate_quat(&t, node->adjust_post_rotation);
ufbxi_mul_scale_real(&t, node->adjust_post_scale);
}
ufbxi_sub_translate(&t, scale_pivot);
ufbxi_mul_scale(&t, scaling);
ufbxi_add_translate(&t, scale_pivot);
ufbxi_add_translate(&t, scale_offset);
ufbxi_sub_translate(&t, rot_pivot);
ufbxi_mul_inv_rotate(&t, post_rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxi_mul_rotate(&t, rotation, order);
ufbxi_mul_rotate(&t, pre_rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxi_add_translate(&t, rot_pivot);
ufbxi_add_translate(&t, rot_offset);
ufbxi_add_translate(&t, translation);
if (node->has_adjust_transform) {
ufbxi_add_translate(&t, node->adjust_pre_translation);
ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation);
ufbxi_mul_scale_real(&t, node->adjust_pre_scale);
t.translation.x *= node->adjust_translation_scale;
t.translation.y *= node->adjust_translation_scale;
t.translation.z *= node->adjust_translation_scale;
}
if (node->adjust_mirror_axis) {
ufbxi_mirror_translation(&t.translation, node->adjust_mirror_axis);
ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis);
}
return t;
}
ufbxi_noinline static ufbx_quat ufbxi_get_rotation(const ufbx_props *props, ufbx_rotation_order order, const ufbx_node *node)
{
ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 pre_rotation = ufbxi_find_vec3(props, ufbxi_PreRotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 post_rotation = ufbxi_find_vec3(props, ufbxi_PostRotation, 0.0f, 0.0f, 0.0f);
ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }};
if (node->has_adjust_transform) {
ufbxi_mul_rotate_quat(&t, node->adjust_post_rotation);
}
ufbxi_mul_inv_rotate(&t, post_rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxi_mul_rotate(&t, rotation, order);
ufbxi_mul_rotate(&t, pre_rotation, UFBX_ROTATION_ORDER_XYZ);
if (node->has_adjust_transform) {
ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation);
}
return t.rotation;
}
ufbxi_noinline static ufbx_vec3 ufbxi_get_scale(const ufbx_props *props, const ufbx_node *node)
{
ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f);
ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }};
if (node->has_adjust_transform) {
ufbxi_mul_scale_real(&t, node->adjust_post_scale);
}
ufbxi_mul_scale(&t, scaling);
if (node->has_adjust_transform) {
ufbxi_mul_scale_real(&t, node->adjust_pre_scale);
}
return t.scale;
}
ufbxi_noinline static ufbx_transform ufbxi_get_texture_transform(const ufbx_props *props)
{
ufbx_vec3 scale_pivot = ufbxi_find_vec3(props, ufbxi_TextureScalingPivot, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rot_pivot = ufbxi_find_vec3(props, ufbxi_TextureRotationPivot, 0.0f, 0.0f, 0.0f);
ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Translation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Rotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Scaling, 1.0f, 1.0f, 1.0f);
ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }};
ufbxi_sub_translate(&t, scale_pivot);
ufbxi_mul_scale(&t, scaling);
ufbxi_add_translate(&t, scale_pivot);
ufbxi_sub_translate(&t, rot_pivot);
ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxi_add_translate(&t, rot_pivot);
ufbxi_add_translate(&t, translation);
if (ufbxi_find_int(props, ufbxi_UVSwap, 0) != 0) {
const ufbx_vec3 swap_scale = { -1.0f, 0.0f, 0.0f };
const ufbx_vec3 swap_rotate = { 0.0f, 0.0f, -90.0f };
ufbxi_mul_scale(&t, swap_scale);
ufbxi_mul_rotate(&t, swap_rotate, UFBX_ROTATION_ORDER_XYZ);
}
return t;
}
ufbxi_noinline static ufbx_transform ufbxi_get_constraint_transform(const ufbx_props *props)
{
ufbx_vec3 translation = ufbxi_find_vec3(props, ufbxi_Translation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rotation = ufbxi_find_vec3(props, ufbxi_Rotation, 0.0f, 0.0f, 0.0f);
ufbx_vec3 rotation_offset = ufbxi_find_vec3(props, ufbxi_RotationOffset, 0.0f, 0.0f, 0.0f);
ufbx_vec3 scaling = ufbxi_find_vec3(props, ufbxi_Scaling, 1.0f, 1.0f, 1.0f);
ufbx_transform t = { { 0,0,0 }, { 0,0,0,1 }, { 1,1,1 }};
ufbxi_mul_scale(&t, scaling);
ufbxi_mul_rotate(&t, rotation, UFBX_ROTATION_ORDER_XYZ);
ufbxi_mul_rotate(&t, rotation_offset, UFBX_ROTATION_ORDER_XYZ);
ufbxi_add_translate(&t, translation);
return t;
}
ufbxi_noinline static void ufbxi_update_node(ufbx_node *node, const ufbx_transform_override *overrides, size_t num_overrides)
{
node->rotation_order = (ufbx_rotation_order)ufbxi_find_enum(&node->props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC);
node->euler_rotation = ufbxi_find_vec3(&node->props, ufbxi_Lcl_Rotation, 0.0f, 0.0f, 0.0f);
if (!node->is_root) {
const ufbx_vec3 *transform_scale = NULL;
if (node->parent && node->parent->scale_helper) {
transform_scale = &node->parent->scale_helper->local_transform.scale;
}
node->local_transform = ufbxi_get_transform(&node->props, node->rotation_order, node, transform_scale);
if (node->is_scale_helper && node->parent && node->parent->inherit_scale_node) {
ufbx_node *scale_parent = node->parent->inherit_scale_node;
if (scale_parent->scale_helper) {
ufbx_vec3 inherit_scale = scale_parent->scale_helper->local_transform.scale;
node->local_transform.scale.x *= inherit_scale.x;
node->local_transform.scale.y *= inherit_scale.y;
node->local_transform.scale.z *= inherit_scale.z;
}
}
if (num_overrides > 0) {
uint32_t typed_id = node->typed_id;
size_t override_ix = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_transform_override, 16, &override_ix, overrides, 0, num_overrides,
( a->node_id < typed_id ), ( a->node_id == typed_id ));
if (override_ix != SIZE_MAX) {
node->local_transform = overrides[override_ix].transform;
}
}
node->node_to_parent = ufbx_transform_to_matrix(&node->local_transform);
node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node);
} else {
node->geometry_transform = ufbx_identity_transform;
}
ufbx_matrix unscaled_node_to_parent = ufbxi_unscaled_transform_to_matrix(&node->local_transform);
node->inherit_scale = node->local_transform.scale;
ufbx_node *parent = node->parent;
if (parent) {
if (node->inherit_mode == UFBX_INHERIT_MODE_NORMAL) {
node->node_to_world = ufbx_matrix_mul(&parent->node_to_world, &node->node_to_parent);
node->unscaled_node_to_world = ufbx_matrix_mul(&parent->node_to_world, &unscaled_node_to_parent);
} else {
ufbx_transform transform = node->local_transform;
ufbx_vec3 parent_scale = ufbxi_one_vec3;
if (node->inherit_scale_node) {
parent_scale = node->inherit_scale_node->inherit_scale;
}
transform.scale.x *= parent_scale.x;
transform.scale.y *= parent_scale.y;
transform.scale.z *= parent_scale.z;
transform.translation.x *= parent->inherit_scale.x;
transform.translation.y *= parent->inherit_scale.y;
transform.translation.z *= parent->inherit_scale.z;
ufbx_matrix node_to_unscaled_parent = ufbx_transform_to_matrix(&transform);
ufbx_matrix unscaled_node_to_unscaled_parent = ufbxi_unscaled_transform_to_matrix(&transform);
node->inherit_scale = transform.scale;
node->node_to_world = ufbx_matrix_mul(&parent->unscaled_node_to_world, &node_to_unscaled_parent);
node->unscaled_node_to_world = ufbx_matrix_mul(&parent->unscaled_node_to_world, &unscaled_node_to_unscaled_parent);
}
} else {
node->node_to_world = node->node_to_parent;
node->unscaled_node_to_world = unscaled_node_to_parent;
}
if (!ufbxi_is_transform_identity(&node->geometry_transform)) {
node->geometry_to_node = ufbx_transform_to_matrix(&node->geometry_transform);
node->geometry_to_world = ufbx_matrix_mul(&node->node_to_world, &node->geometry_to_node);
node->has_geometry_transform = true;
} else {
node->geometry_to_node = ufbx_identity_matrix;
node->geometry_to_world = node->node_to_world;
node->has_geometry_transform = false;
}
node->visible = ufbxi_find_int(&node->props, ufbxi_Visibility, 1) != 0;
}
ufbxi_noinline static void ufbxi_update_light(ufbx_light *light)
{
// NOTE: FBX seems to store intensities 100x of what's specified in at least
// Maya and Blender, should there be a quirks mode to not do this for specific
// exporters. Does the FBX SDK do this transparently as well?
light->intensity = ufbxi_find_real(&light->props, ufbxi_Intensity, (ufbx_real)100.0) / (ufbx_real)100.0;
light->color = ufbxi_find_vec3(&light->props, ufbxi_Color, 1.0f, 1.0f, 1.0f);
light->type = (ufbx_light_type)ufbxi_find_enum(&light->props, ufbxi_LightType, 0, UFBX_LIGHT_VOLUME);
light->decay = (ufbx_light_decay)ufbxi_find_enum(&light->props, ufbxi_DecayType, UFBX_LIGHT_DECAY_NONE, UFBX_LIGHT_DECAY_CUBIC);
light->area_shape = (ufbx_light_area_shape)ufbxi_find_enum(&light->props, ufbxi_AreaLightShape, 0, UFBX_LIGHT_AREA_SHAPE_SPHERE);
light->inner_angle = ufbxi_find_real(&light->props, ufbxi_HotSpot, 0.0f);
light->inner_angle = ufbxi_find_real(&light->props, ufbxi_InnerAngle, light->inner_angle);
light->outer_angle = ufbxi_find_real(&light->props, ufbxi_Cone_angle, 0.0f);
light->outer_angle = ufbxi_find_real(&light->props, ufbxi_ConeAngle, light->outer_angle);
light->outer_angle = ufbxi_find_real(&light->props, ufbxi_OuterAngle, light->outer_angle);
light->cast_light = ufbxi_find_int(&light->props, ufbxi_CastLight, 1) != 0;
light->cast_shadows = ufbxi_find_int(&light->props, ufbxi_CastShadows, 0) != 0;
}
typedef struct {
// 1/1000 decimal fixed point for size
uint16_t film_size_x, film_size_y;
} ufbxi_aperture_format;
static const ufbxi_aperture_format ufbxi_aperture_formats[] = {
{ 1000, 1000, }, // UFBX_APERTURE_FORMAT_CUSTOM
{ 404, 295, }, // UFBX_APERTURE_FORMAT_16MM_THEATRICAL
{ 493, 292, }, // UFBX_APERTURE_FORMAT_SUPER_16MM
{ 864, 630, }, // UFBX_APERTURE_FORMAT_35MM_ACADEMY
{ 816, 612, }, // UFBX_APERTURE_FORMAT_35MM_TV_PROJECTION
{ 980, 735, }, // UFBX_APERTURE_FORMAT_35MM_FULL_APERTURE
{ 825, 446, }, // UFBX_APERTURE_FORMAT_35MM_185_PROJECTION
{ 864, 732, }, // UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC
{ 2066, 906, }, // UFBX_APERTURE_FORMAT_70MM_PROJECTION
{ 1485, 991, }, // UFBX_APERTURE_FORMAT_VISTAVISION
{ 2080, 1480, }, // UFBX_APERTURE_FORMAT_DYNAVISION
{ 2772, 2072, }, // UFBX_APERTURE_FORMAT_IMAX
};
ufbxi_noinline static void ufbxi_update_camera(ufbx_scene *scene, ufbx_camera *camera)
{
camera->projection_mode = (ufbx_projection_mode)ufbxi_find_enum(&camera->props, ufbxi_CameraProjectionType, 0, UFBX_PROJECTION_MODE_ORTHOGRAPHIC);
camera->aspect_mode = (ufbx_aspect_mode)ufbxi_find_enum(&camera->props, ufbxi_AspectRatioMode, 0, UFBX_ASPECT_MODE_FIXED_HEIGHT);
camera->aperture_mode = (ufbx_aperture_mode)ufbxi_find_enum(&camera->props, ufbxi_ApertureMode, UFBX_APERTURE_MODE_VERTICAL, UFBX_APERTURE_MODE_FOCAL_LENGTH);
camera->aperture_format = (ufbx_aperture_format)ufbxi_find_enum(&camera->props, ufbxi_ApertureFormat, UFBX_APERTURE_FORMAT_CUSTOM, UFBX_APERTURE_FORMAT_IMAX);
camera->gate_fit = (ufbx_gate_fit)ufbxi_find_enum(&camera->props, ufbxi_GateFit, 0, UFBX_GATE_FIT_STRETCH);
camera->near_plane = ufbxi_find_real(&camera->props, ufbxi_NearPlane, 0.0f);
camera->far_plane = ufbxi_find_real(&camera->props, ufbxi_FarPlane, 0.0f);
// Search both W/H and Width/Height but prefer the latter
ufbx_real aspect_x = ufbxi_find_real(&camera->props, ufbxi_AspectW, 0.0f);
ufbx_real aspect_y = ufbxi_find_real(&camera->props, ufbxi_AspectH, 0.0f);
aspect_x = ufbxi_find_real(&camera->props, ufbxi_AspectWidth, aspect_x);
aspect_y = ufbxi_find_real(&camera->props, ufbxi_AspectHeight, aspect_y);
ufbx_real fov = ufbxi_find_real(&camera->props, ufbxi_FieldOfView, 0.0f);
ufbx_real fov_x = ufbxi_find_real(&camera->props, ufbxi_FieldOfViewX, 0.0f);
ufbx_real fov_y = ufbxi_find_real(&camera->props, ufbxi_FieldOfViewY, 0.0f);
ufbx_real focal_length = ufbxi_find_real(&camera->props, ufbxi_FocalLength, 0.0f);
ufbx_real ortho_extent = scene->metadata.ortho_size_unit * ufbxi_find_real(&camera->props, ufbxi_OrthoZoom, 1.0f);
ufbxi_aperture_format format = ufbxi_aperture_formats[camera->aperture_format];
ufbx_vec2 film_size = { (ufbx_real)format.film_size_x * (ufbx_real)0.001, (ufbx_real)format.film_size_y * (ufbx_real)0.001 };
ufbx_real squeeze_ratio = camera->aperture_format == UFBX_APERTURE_FORMAT_35MM_ANAMORPHIC ? 2.0f : 1.0f;
film_size.x = ufbxi_find_real(&camera->props, ufbxi_FilmWidth, film_size.x);
film_size.y = ufbxi_find_real(&camera->props, ufbxi_FilmHeight, film_size.y);
squeeze_ratio = ufbxi_find_real(&camera->props, ufbxi_FilmSqueezeRatio, squeeze_ratio);
if (aspect_x <= 0.0f && aspect_y <= 0.0f) {
aspect_x = film_size.x > 0.0f ? film_size.x : 1.0f;
aspect_y = film_size.y > 0.0f ? film_size.y : 1.0f;
} else if (aspect_x <= 0.0f) {
if (film_size.x > 0.0f && film_size.y > 0.0f) {
aspect_x = aspect_y / film_size.y * film_size.x;
} else {
aspect_x = aspect_y;
}
} else if (aspect_y <= 0.0f) {
if (film_size.x > 0.0f && film_size.y > 0.0f) {
aspect_y = aspect_x / film_size.x * film_size.y;
} else {
aspect_y = aspect_x;
}
}
film_size.y *= squeeze_ratio;
// TODO: Should this be done always?
ortho_extent *= scene->metadata.geometry_scale;
camera->near_plane *= scene->metadata.geometry_scale;
camera->far_plane *= scene->metadata.geometry_scale;
camera->focal_length_mm = focal_length;
camera->film_size_inch = film_size;
camera->squeeze_ratio = squeeze_ratio;
camera->orthographic_extent = ortho_extent;
switch (camera->aspect_mode) {
case UFBX_ASPECT_MODE_WINDOW_SIZE:
case UFBX_ASPECT_MODE_FIXED_RATIO:
camera->resolution_is_pixels = false;
camera->resolution.x = aspect_x;
camera->resolution.y = aspect_y;
break;
case UFBX_ASPECT_MODE_FIXED_RESOLUTION:
camera->resolution_is_pixels = true;
camera->resolution.x = aspect_x;
camera->resolution.y = aspect_y;
break;
case UFBX_ASPECT_MODE_FIXED_WIDTH:
camera->resolution_is_pixels = true;
camera->resolution.x = aspect_x;
camera->resolution.y = aspect_x * aspect_y;
break;
case UFBX_ASPECT_MODE_FIXED_HEIGHT:
camera->resolution_is_pixels = true;
camera->resolution.x = aspect_y * aspect_x;
camera->resolution.y = aspect_y;
break;
default:
ufbxi_unreachable("Unexpected aspect mode");
}
ufbx_real aspect_ratio = camera->resolution.x / camera->resolution.y;
ufbx_real film_ratio = film_size.x / film_size.y;
camera->aspect_ratio = aspect_ratio;
ufbx_gate_fit effective_fit = camera->gate_fit;
if (effective_fit == UFBX_GATE_FIT_FILL) {
effective_fit = aspect_ratio > film_ratio ? UFBX_GATE_FIT_HORIZONTAL : UFBX_GATE_FIT_VERTICAL;
} else if (effective_fit == UFBX_GATE_FIT_OVERSCAN) {
effective_fit = aspect_ratio < film_ratio ? UFBX_GATE_FIT_HORIZONTAL : UFBX_GATE_FIT_VERTICAL;
}
switch (effective_fit) {
case UFBX_GATE_FIT_NONE:
camera->aperture_size_inch = camera->film_size_inch;
camera->orthographic_size.x = ortho_extent;
camera->orthographic_size.y = ortho_extent;
break;
case UFBX_GATE_FIT_VERTICAL:
camera->aperture_size_inch.x = camera->film_size_inch.y * aspect_ratio;
camera->aperture_size_inch.y = camera->film_size_inch.y;
camera->orthographic_size.x = ortho_extent * aspect_ratio;
camera->orthographic_size.y = ortho_extent;
break;
case UFBX_GATE_FIT_HORIZONTAL:
camera->aperture_size_inch.x = camera->film_size_inch.x;
camera->aperture_size_inch.y = camera->film_size_inch.x / aspect_ratio;
camera->orthographic_size.x = ortho_extent;
camera->orthographic_size.y = ortho_extent / aspect_ratio;
break;
case UFBX_GATE_FIT_FILL:
case UFBX_GATE_FIT_OVERSCAN:
camera->aperture_size_inch = camera->film_size_inch;
camera->orthographic_size.x = ortho_extent;
camera->orthographic_size.y = ortho_extent;
ufbxi_unreachable("Unreachable, set to vertical/horizontal above");
break;
case UFBX_GATE_FIT_STRETCH:
camera->aperture_size_inch = camera->film_size_inch;
camera->orthographic_size.x = ortho_extent;
camera->orthographic_size.y = ortho_extent;
// TODO: Not sure what to do here...
break;
default:
ufbxi_unreachable("Unexpected gate fit");
}
switch (camera->aperture_mode) {
case UFBX_APERTURE_MODE_HORIZONTAL_AND_VERTICAL:
camera->field_of_view_deg.x = fov_x;
camera->field_of_view_deg.y = fov_y;
camera->field_of_view_tan.x = (ufbx_real)ufbx_tan((double)(fov_x * (UFBXI_DEG_TO_RAD * 0.5f)));
camera->field_of_view_tan.y = (ufbx_real)ufbx_tan((double)(fov_y * (UFBXI_DEG_TO_RAD * 0.5f)));
break;
case UFBX_APERTURE_MODE_HORIZONTAL:
camera->field_of_view_deg.x = fov;
camera->field_of_view_tan.x = (ufbx_real)ufbx_tan((double)(fov * (UFBXI_DEG_TO_RAD * 0.5f)));
camera->field_of_view_tan.y = camera->field_of_view_tan.x / aspect_ratio;
camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f;
break;
case UFBX_APERTURE_MODE_VERTICAL:
camera->field_of_view_deg.y = fov;
camera->field_of_view_tan.y = (ufbx_real)ufbx_tan((double)(fov * (UFBXI_DEG_TO_RAD * 0.5f)));
camera->field_of_view_tan.x = camera->field_of_view_tan.y * aspect_ratio;
camera->field_of_view_deg.x = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.x) * UFBXI_RAD_TO_DEG * 2.0f;
break;
case UFBX_APERTURE_MODE_FOCAL_LENGTH:
camera->field_of_view_tan.x = camera->aperture_size_inch.x / (camera->focal_length_mm * UFBXI_MM_TO_INCH) * 0.5f;
camera->field_of_view_tan.y = camera->aperture_size_inch.y / (camera->focal_length_mm * UFBXI_MM_TO_INCH) * 0.5f;
camera->field_of_view_deg.x = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.x) * UFBXI_RAD_TO_DEG * 2.0f;
camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f;
break;
default:
ufbxi_unreachable("Unexpected aperture mode");
}
if (camera->projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE) {
camera->projection_plane = camera->field_of_view_tan;
} else {
camera->projection_plane = camera->orthographic_size;
}
}
ufbxi_noinline static void ufbxi_update_bone(ufbx_scene *scene, ufbx_bone *bone)
{
ufbx_real unit = scene->metadata.bone_prop_size_unit;
bone->radius = ufbxi_find_real(&bone->props, ufbxi_Size, unit) / unit;
if (scene->metadata.bone_prop_limb_length_relative) {
bone->relative_length = ufbxi_find_real(&bone->props, ufbxi_LimbLength, 1.0f);
} else {
bone->relative_length = 1.0f;
}
}
ufbxi_noinline static void ufbxi_update_line_curve(ufbx_line_curve *line)
{
line->color = ufbxi_find_vec3(&line->props, ufbxi_Color, 1.0f, 1.0f, 1.0f);
}
ufbxi_noinline static void ufbxi_update_pose(ufbx_pose *pose)
{
ufbxi_for_list(ufbx_bone_pose, bone, pose->bone_poses) {
ufbx_node *node = bone->bone_node;
const ufbx_matrix *parent_to_world = &ufbx_identity_matrix;
ufbx_bone_pose *bone_pose = ufbx_get_bone_pose(pose, node->parent);
if (bone_pose) {
parent_to_world = &bone_pose->bone_to_world;
} else if (node->parent) {
parent_to_world = &node->parent->node_to_world;
}
ufbx_matrix world_to_parent = ufbx_matrix_invert(parent_to_world);
bone->bone_to_parent = ufbx_matrix_mul(&world_to_parent, &bone->bone_to_world);
}
}
ufbxi_noinline static void ufbxi_update_skin_cluster(ufbx_skin_cluster *cluster)
{
if (cluster->bone_node) {
cluster->geometry_to_world = ufbx_matrix_mul(&cluster->bone_node->node_to_world, &cluster->geometry_to_bone);
} else {
cluster->geometry_to_world = ufbx_matrix_mul(&cluster->bind_to_world, &cluster->geometry_to_bone);
}
cluster->geometry_to_world_transform = ufbx_matrix_to_transform(&cluster->geometry_to_world);
}
ufbxi_noinline static void ufbxi_update_blend_channel(ufbx_blend_channel *channel)
{
ufbx_real weight = ufbxi_find_real(&channel->props, ufbxi_DeformPercent, 0.0f) * (ufbx_real)0.01;
channel->weight = weight;
ptrdiff_t num_keys = (ptrdiff_t)channel->keyframes.count;
if (num_keys > 0) {
ufbx_blend_keyframe *keys = channel->keyframes.data;
// Reset the effective weights to zero and find the split around zero
ptrdiff_t last_negative = -1;
for (ptrdiff_t i = 0; i < num_keys; i++) {
keys[i].effective_weight = (ufbx_real)0.0;
if (keys[i].target_weight < 0.0) last_negative = i;
}
// Find either the next or last keyframe away from zero
ufbx_blend_keyframe zero_key = { NULL };
ufbx_blend_keyframe *prev = &zero_key, *next = &zero_key;
if (weight > 0.0) {
if (last_negative >= 0) prev = &keys[last_negative];
for (ptrdiff_t i = last_negative + 1; i < num_keys; i++) {
prev = next;
next = &keys[i];
if (next->target_weight > weight) break;
}
} else {
if (last_negative + 1 < num_keys) prev = &keys[last_negative + 1];
for (ptrdiff_t i = last_negative; i >= 0; i--) {
prev = next;
next = &keys[i];
if (next->target_weight < weight) break;
}
}
// Linearly interpolate between the endpoints with the weight
ufbx_real delta = next->target_weight - prev->target_weight;
if (delta != 0.0) {
ufbx_real t = (weight - prev->target_weight) / delta;
prev->effective_weight = 1.0f - t;
next->effective_weight = t;
}
}
}
ufbxi_noinline static void ufbxi_update_material(ufbx_scene *scene, ufbx_material *material)
{
if (material->props.num_animated > 0) {
ufbxi_fetch_maps(scene, material);
}
}
ufbxi_noinline static void ufbxi_update_texture(ufbx_texture *texture)
{
texture->uv_transform = ufbxi_get_texture_transform(&texture->props);
if (!ufbxi_is_transform_identity(&texture->uv_transform)) {
texture->has_uv_transform = true;
texture->texture_to_uv = ufbx_transform_to_matrix(&texture->uv_transform);
texture->uv_to_texture = ufbx_matrix_invert(&texture->texture_to_uv);
} else {
texture->has_uv_transform = false;
texture->texture_to_uv = ufbx_identity_matrix;
texture->uv_to_texture = ufbx_identity_matrix;
}
texture->wrap_u = (ufbx_wrap_mode)ufbxi_find_enum(&texture->props, ufbxi_WrapModeU, 0, UFBX_WRAP_CLAMP);
texture->wrap_v = (ufbx_wrap_mode)ufbxi_find_enum(&texture->props, ufbxi_WrapModeV, 0, UFBX_WRAP_CLAMP);
if (texture->shader) {
ufbxi_update_shader_texture(texture, texture->shader);
}
}
ufbxi_noinline static void ufbxi_update_anim_stack(ufbx_scene *scene, ufbx_anim_stack *stack)
{
ufbx_prop *begin, *end;
begin = ufbxi_find_prop(&stack->props, ufbxi_LocalStart);
end = ufbxi_find_prop(&stack->props, ufbxi_LocalStop);
if (!begin || !end) {
begin = ufbxi_find_prop(&stack->props, ufbxi_ReferenceStart);
end = ufbxi_find_prop(&stack->props, ufbxi_ReferenceStop);
}
if (begin && end) {
stack->time_begin = (double)begin->value_int / (double)scene->metadata.ktime_second;
stack->time_end = (double)end->value_int / (double)scene->metadata.ktime_second;
}
stack->anim->time_begin = stack->time_begin;
stack->anim->time_end = stack->time_end;
}
ufbxi_noinline static void ufbxi_update_display_layer(ufbx_display_layer *layer)
{
layer->visible = ufbxi_find_int(&layer->props, ufbxi_Show, 1) != 0;
layer->frozen = ufbxi_find_int(&layer->props, ufbxi_Freeze, 1) != 0;
layer->ui_color = ufbxi_find_vec3(&layer->props, ufbxi_Color, 0.8f, 0.8f, 0.8f);
}
ufbxi_noinline static void ufbxi_find_bool3(bool *dst, ufbx_props *props, const char *name, bool default_value)
{
size_t name_len = strlen(name);
char local[64];
ufbx_assert(name_len < sizeof(local) - 2);
memcpy(local, name, name_len);
size_t local_len = name_len + 1;
local[local_len] = '\0';
int64_t def = default_value ? 1 : 0;
local[name_len] = 'X';
dst[0] = ufbx_find_int_len(props, local, local_len, def) != 0;
local[name_len] = 'Y';
dst[1] = ufbx_find_int_len(props, local, local_len, def) != 0;
local[name_len] = 'Z';
dst[2] = ufbx_find_int_len(props, local, local_len, def) != 0;
}
ufbxi_noinline static void ufbxi_update_constraint(ufbx_constraint *constraint)
{
ufbx_props *props = &constraint->props;
ufbx_constraint_type constraint_type = constraint->type;
constraint->transform_offset = ufbxi_get_constraint_transform(props);
constraint->weight = ufbxi_find_real(props, ufbxi_Weight, (ufbx_real)100.0) / (ufbx_real)100.0;
ufbxi_for_list(ufbx_constraint_target, target, constraint->targets) {
ufbx_node *node = target->node;
ufbx_real weight_scale = (ufbx_real)100.0;
if (constraint_type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) {
// IK weights seem to be not scaled 100x?
weight_scale = (ufbx_real)1.0;
}
ufbx_prop *prop; // ufbxi_uninit
ufbx_string parts[2]; // ufbxi_uninit
parts[0] = node->name;
parts[1] = ufbxi_str_c(".Weight");
prop = ufbx_find_prop_concat(props, parts, 2);
target->weight = (prop ? prop->value_real : weight_scale) / weight_scale;
if (constraint_type == UFBX_CONSTRAINT_PARENT) {
parts[1] = ufbxi_str_c(".Offset T");
prop = ufbx_find_prop_concat(props, parts, 2);
ufbx_vec3 t = prop ? prop->value_vec3 : ufbx_zero_vec3;
parts[1] = ufbxi_str_c(".Offset R");
prop = ufbx_find_prop_concat(props, parts, 2);
ufbx_vec3 r = prop ? prop->value_vec3 : ufbx_zero_vec3;
parts[1] = ufbxi_str_c(".Offset S");
prop = ufbx_find_prop_concat(props, parts, 2);
ufbx_vec3 s = prop ? prop->value_vec3 : ufbxi_one_vec3;
target->transform.translation = t;
target->transform.rotation = ufbx_euler_to_quat(r, UFBX_ROTATION_ORDER_XYZ);
target->transform.scale = s;
}
}
constraint->active = ufbx_find_int(props, "Active", 1) != 0;
if (constraint_type == UFBX_CONSTRAINT_AIM) {
ufbxi_find_bool3(constraint->constrain_rotation, props, "Affect", 1);
const ufbx_vec3 default_aim = { 1.0f, 0.0f, 0.0f };
const ufbx_vec3 default_up = { 0.0f, 1.0f, 0.0f };
int64_t up_type = ufbx_find_int(props, "WorldUpType", 0);
if (up_type >= 0 && up_type < UFBX_CONSTRAINT_AIM_UP_NONE) {
constraint->aim_up_type = (ufbx_constraint_aim_up_type)up_type;
}
constraint->aim_vector = ufbx_find_vec3(props, "AimVector", default_aim);
constraint->aim_up_vector = ufbx_find_vec3(props, "UpVector", default_up);
} else if (constraint_type == UFBX_CONSTRAINT_PARENT) {
ufbxi_find_bool3(constraint->constrain_translation, props, "AffectTranslation", 1);
ufbxi_find_bool3(constraint->constrain_rotation, props, "AffectRotation", 1);
ufbxi_find_bool3(constraint->constrain_scale, props, "AffectScale", 0);
} else if (constraint_type == UFBX_CONSTRAINT_POSITION) {
ufbxi_find_bool3(constraint->constrain_translation, props, "Affect", 1);
} else if (constraint_type == UFBX_CONSTRAINT_ROTATION) {
ufbxi_find_bool3(constraint->constrain_rotation, props, "Affect", 1);
} else if (constraint_type == UFBX_CONSTRAINT_SCALE) {
ufbxi_find_bool3(constraint->constrain_scale, props, "Affect", 1);
} else if (constraint_type == UFBX_CONSTRAINT_SINGLE_CHAIN_IK) {
constraint->constrain_rotation[0] = true;
constraint->constrain_rotation[1] = true;
constraint->constrain_rotation[2] = true;
constraint->ik_pole_vector = ufbx_find_vec3(props, "PoleVectorType", ufbx_zero_vec3);
}
}
ufbxi_noinline static void ufbxi_update_anim(ufbx_scene *scene)
{
if (scene->anim_stacks.count > 0) {
scene->anim = scene->anim_stacks.data[0]->anim;
}
}
static ufbxi_forceinline void ufbxi_mirror_matrix_dst(ufbx_matrix *m, ufbx_mirror_axis axis)
{
if (axis == 0) return;
int32_t ax = (int32_t)axis - 1;
m->cols[0].v[ax] = -m->cols[0].v[ax];
m->cols[1].v[ax] = -m->cols[1].v[ax];
m->cols[2].v[ax] = -m->cols[2].v[ax];
m->cols[3].v[ax] = -m->cols[3].v[ax];
}
static ufbxi_forceinline void ufbxi_mirror_matrix_src(ufbx_matrix *m, ufbx_mirror_axis axis)
{
if (axis == 0) return;
int32_t ax = (int32_t)axis - 1;
m->cols[ax].x = -m->cols[ax].x;
m->cols[ax].y = -m->cols[ax].y;
m->cols[ax].z = -m->cols[ax].z;
}
static ufbxi_noinline void ufbxi_mirror_matrix(ufbx_matrix *m, ufbx_mirror_axis axis)
{
if (axis == 0) return;
ufbxi_mirror_matrix_src(m, axis);
ufbxi_mirror_matrix_dst(m, axis);
}
ufbxi_noinline static void ufbxi_update_initial_clusters(ufbx_scene *scene)
{
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
cluster->geometry_to_bone = cluster->mesh_node_to_bone;
}
ufbx_mirror_axis mirror_axis = scene->metadata.mirror_axis;
ufbx_real geometry_scale = scene->metadata.geometry_scale;
// Space conversion for bind matrices
{
ufbx_matrix world_to_units;
ufbx_real translation_scale = 1.0f;
if (scene->metadata.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT && scene->metadata.mirror_axis == UFBX_MIRROR_AXIS_NONE) {
world_to_units = scene->root_node->node_to_parent;
} else {
ufbx_transform root_transform;
root_transform.translation = ufbx_zero_vec3;
root_transform.rotation = scene->metadata.root_rotation;
root_transform.scale.x = scene->metadata.root_scale;
root_transform.scale.y = scene->metadata.root_scale;
root_transform.scale.z = scene->metadata.root_scale;
world_to_units = ufbx_transform_to_matrix(&root_transform);
translation_scale = scene->metadata.geometry_scale;
}
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
cluster->bind_to_world = ufbx_matrix_mul(&world_to_units, &cluster->bind_to_world);
cluster->bind_to_world.cols[3].x *= translation_scale;
cluster->bind_to_world.cols[3].y *= translation_scale;
cluster->bind_to_world.cols[3].z *= translation_scale;
ufbxi_mirror_matrix(&cluster->bind_to_world, mirror_axis);
}
ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) {
ufbxi_for_list(ufbx_bone_pose, pose, (*p_pose)->bone_poses) {
pose->bone_to_world = ufbx_matrix_mul(&world_to_units, &pose->bone_to_world);
pose->bone_to_world.cols[3].x *= translation_scale;
pose->bone_to_world.cols[3].y *= translation_scale;
pose->bone_to_world.cols[3].z *= translation_scale;
ufbxi_mirror_matrix(&pose->bone_to_world, mirror_axis);
}
}
}
// Patch initial `mesh_node_to_bone`
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
ufbx_skin_deformer *skin = (ufbx_skin_deformer*)ufbxi_fetch_src_element(&cluster->element, false, NULL, UFBX_ELEMENT_SKIN_DEFORMER);
if (!skin) continue;
ufbx_node *node = (ufbx_node*)ufbxi_fetch_src_element(&skin->element, false, NULL, UFBX_ELEMENT_NODE);
if (!node) {
ufbx_mesh *mesh = (ufbx_mesh*)ufbxi_fetch_src_element(&skin->element, false, NULL, UFBX_ELEMENT_MESH);
if (mesh && mesh->instances.count > 0) {
node = mesh->instances.data[0];
}
}
if (!node) continue;
// Normalize to the non-helper node
if (node->is_geometry_transform_helper) {
node = node->parent;
}
if (ufbxi_matrix_all_zero(&cluster->mesh_node_to_bone)) {
// If `mesh_node_to_bone` is not explicitly specified compute it from bind pose.
ufbx_matrix world_to_bind = ufbx_matrix_invert(&cluster->bind_to_world);
cluster->mesh_node_to_bone = ufbx_matrix_mul(&world_to_bind, &node->node_to_world);
} else {
// If `mesh_node_to_bone` is explicit, we may need to modify it for space conversion.
ufbxi_mirror_matrix(&cluster->mesh_node_to_bone, mirror_axis);
if (geometry_scale != 1.0f) {
cluster->mesh_node_to_bone.cols[3].x *= geometry_scale;
cluster->mesh_node_to_bone.cols[3].y *= geometry_scale;
cluster->mesh_node_to_bone.cols[3].z *= geometry_scale;
}
}
// HACK: Account for geometry transforms by looking at the transform of the
// helper node if one is present. I don't think this is exactly how the skinning
// matrices are formed.
// TODO: Add a test with moving the skinned mesh root around.
if (node->geometry_transform_helper) {
ufbx_node *geo_node = node->geometry_transform_helper;
cluster->geometry_to_bone = ufbx_matrix_mul(&cluster->mesh_node_to_bone, &geo_node->node_to_parent);
} else if (node->has_geometry_transform) {
cluster->geometry_to_bone = ufbx_matrix_mul(&cluster->mesh_node_to_bone, &node->geometry_to_node);
} else {
cluster->geometry_to_bone = cluster->mesh_node_to_bone;
}
}
}
ufbxi_noinline static ufbx_coordinate_axis ufbxi_find_axis(const ufbx_props *props, const char *axis_name, const char *sign_name)
{
int64_t axis = ufbxi_find_int(props, axis_name, 3);
int64_t sign = ufbxi_find_int(props, sign_name, 2);
switch (axis) {
case 0: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_X : UFBX_COORDINATE_AXIS_NEGATIVE_X;
case 1: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_Y : UFBX_COORDINATE_AXIS_NEGATIVE_Y;
case 2: return sign > 0 ? UFBX_COORDINATE_AXIS_POSITIVE_Z : UFBX_COORDINATE_AXIS_NEGATIVE_Z;
default: return UFBX_COORDINATE_AXIS_UNKNOWN;
}
}
static const ufbx_real ufbxi_time_mode_fps[] = {
30.0f, // UFBX_TIME_MODE_DEFAULT
120.0f, // UFBX_TIME_MODE_120_FPS
100.0f, // UFBX_TIME_MODE_100_FPS
60.0f, // UFBX_TIME_MODE_60_FPS
50.0f, // UFBX_TIME_MODE_50_FPS
48.0f, // UFBX_TIME_MODE_48_FPS
30.0f, // UFBX_TIME_MODE_30_FPS
30.0f, // UFBX_TIME_MODE_30_FPS_DROP
29.97f, // UFBX_TIME_MODE_NTSC_DROP_FRAME
29.97f, // UFBX_TIME_MODE_NTSC_FULL_FRAME
25.0f, // UFBX_TIME_MODE_PAL
24.0f, // UFBX_TIME_MODE_24_FPS
1000.0f, // UFBX_TIME_MODE_1000_FPS
23.976f, // UFBX_TIME_MODE_FILM_FULL_FRAME
24.0f, // UFBX_TIME_MODE_CUSTOM
96.0f, // UFBX_TIME_MODE_96_FPS
72.0f, // UFBX_TIME_MODE_72_FPS
59.94f, // UFBX_TIME_MODE_59_94_FPS
};
// Returns whether a non-identity matrix was needed
static ufbxi_noinline bool ufbxi_axis_matrix(ufbx_matrix *mat, ufbx_coordinate_axes src, ufbx_coordinate_axes dst)
{
uint32_t src_x = (uint32_t)src.right;
uint32_t dst_x = (uint32_t)dst.right;
uint32_t src_y = (uint32_t)src.up;
uint32_t dst_y = (uint32_t)dst.up;
uint32_t src_z = (uint32_t)src.front;
uint32_t dst_z = (uint32_t)dst.front;
if (src_x == dst_x && src_y == dst_y && src_z == dst_z) return false;
// Remap axes (axis enum divided by 2) potentially flipping if the signs (enum parity) doesn't match
memset(mat, 0, sizeof(ufbx_matrix));
mat->cols[src_x >> 1].v[dst_x >> 1] = ((src_x ^ dst_x) & 1) == 0 ? 1.0f : -1.0f;
mat->cols[src_y >> 1].v[dst_y >> 1] = ((src_y ^ dst_y) & 1) == 0 ? 1.0f : -1.0f;
mat->cols[src_z >> 1].v[dst_z >> 1] = ((src_z ^ dst_z) & 1) == 0 ? 1.0f : -1.0f;
return true;
}
ufbxi_noinline static void ufbxi_update_adjust_transforms(ufbxi_context *uc, ufbx_scene *scene)
{
ufbx_transform root_transform = ufbx_identity_transform;
if (!ufbxi_matrix_all_zero(&uc->axis_matrix)) {
root_transform = ufbx_matrix_to_transform(&uc->axis_matrix);
}
root_transform.scale.x *= uc->unit_scale;
root_transform.scale.y *= uc->unit_scale;
root_transform.scale.z *= uc->unit_scale;
ufbx_space_conversion conversion = uc->opts.space_conversion;
ufbx_quat light_post_rotation = ufbx_identity_quat;
ufbx_quat camera_post_rotation = ufbx_identity_quat;
ufbx_vec3 light_direction = { 0.0f, -1.0f, 0.0f };
bool has_light_transform = false;
bool has_camera_transform = false;
if (ufbx_coordinate_axes_valid(uc->opts.target_light_axes)) {
ufbx_matrix mat; // ufbxi_uninit
ufbx_coordinate_axes light_axes = {
UFBX_COORDINATE_AXIS_POSITIVE_X,
UFBX_COORDINATE_AXIS_NEGATIVE_Z,
UFBX_COORDINATE_AXIS_POSITIVE_Y,
};
if (ufbxi_axis_matrix(&mat, uc->opts.target_light_axes, light_axes)) {
light_post_rotation = ufbx_matrix_to_transform(&mat).rotation;
ufbx_matrix inv = ufbx_matrix_invert(&mat);
light_direction = ufbx_transform_direction(&inv, light_direction);
has_light_transform = true;
}
}
if (ufbx_coordinate_axes_valid(uc->opts.target_camera_axes)) {
ufbx_matrix mat; // ufbxi_uninit
ufbx_coordinate_axes camera_axes = {
UFBX_COORDINATE_AXIS_POSITIVE_Z,
UFBX_COORDINATE_AXIS_POSITIVE_Y,
UFBX_COORDINATE_AXIS_NEGATIVE_X,
};
if (ufbxi_axis_matrix(&mat, uc->opts.target_camera_axes, camera_axes)) {
camera_post_rotation = ufbx_matrix_to_transform(&mat).rotation;
has_camera_transform = true;
}
}
ufbxi_for_ptr_list(ufbx_light, p_light, scene->lights) {
ufbx_light *light = *p_light;
light->local_direction.x = 0.0f;
light->local_direction.y = -1.0f;
light->local_direction.z = 0.0f;
}
ufbx_real root_scale = ufbxi_min3(root_transform.scale);
scene->metadata.space_conversion = conversion;
if (conversion == UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY) {
scene->metadata.geometry_scale = root_scale;
scene->metadata.root_scale = 1.0f;
} else {
scene->metadata.geometry_scale = 1.0f;
scene->metadata.root_scale = root_scale;
}
scene->metadata.root_rotation = root_transform.rotation;
ufbxi_for_ptr_list(ufbx_node, p_node, scene->nodes) {
ufbx_node *node = *p_node;
node->adjust_post_rotation = ufbx_identity_quat;
node->adjust_pre_rotation = ufbx_identity_quat;
node->adjust_pre_scale = 1.0f;
node->adjust_post_scale = 1.0f;
node->adjust_translation_scale = 1.0f;
if (conversion == UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS) {
if (node->node_depth <= 1 && !node->is_root) {
node->adjust_pre_rotation = root_transform.rotation;
node->adjust_pre_scale = root_scale;
node->has_adjust_transform = true;
node->has_root_adjust_transform = true;
}
} else if (conversion == UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY) {
if (!node->is_root) {
if (node->node_depth <= 1) {
node->adjust_pre_rotation = root_transform.rotation;
}
node->adjust_translation_scale = root_scale;
node->has_adjust_transform = true;
}
}
if (node->parent) {
// We are not inheriting local scale, so propagate root scale manually and
// apply scale compensation if necessary.
ufbx_node *parent = node->parent;
if (parent->has_root_adjust_transform && node->inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) {
node->adjust_post_scale *= root_scale;
node->has_adjust_transform = true;
node->has_root_adjust_transform = true;
}
if (parent->is_scale_compensate_parent && node->original_inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) {
ufbx_vec3 scale = ufbxi_find_vec3(&parent->props, ufbxi_Lcl_Scaling, 1.0f, 1.0f, 1.0f);
ufbx_real size = scale.x;
if (ufbx_fabs(scale.y - 1.0f) < ufbx_fabs(size - 1.0f)) size = scale.y;
if (ufbx_fabs(scale.z - 1.0f) < ufbx_fabs(size - 1.0f)) size = scale.z;
node->adjust_post_scale *= 1.0f / size;
node->has_adjust_transform = true;
}
}
if (node->all_attribs.count == 1) {
if (has_light_transform && node->light) {
node->adjust_post_rotation = light_post_rotation;
node->light->local_direction = light_direction;
node->has_adjust_transform = true;
}
if (has_camera_transform && node->camera) {
node->adjust_post_rotation = camera_post_rotation;
node->camera->projection_axes = uc->opts.target_camera_axes;
node->has_adjust_transform = true;
}
}
}
}
ufbxi_noinline static void ufbxi_update_scene(ufbx_scene *scene, bool initial, const ufbx_transform_override *transform_overrides, size_t num_transform_overrides)
{
ufbxi_for_ptr_list(ufbx_node, p_node, scene->nodes) {
ufbxi_update_node(*p_node, transform_overrides, num_transform_overrides);
}
ufbxi_for_ptr_list(ufbx_light, p_light, scene->lights) {
ufbxi_update_light(*p_light);
}
ufbxi_for_ptr_list(ufbx_camera, p_camera, scene->cameras) {
ufbxi_update_camera(scene, *p_camera);
}
ufbxi_for_ptr_list(ufbx_bone, p_bone, scene->bones) {
ufbxi_update_bone(scene, *p_bone);
}
ufbxi_for_ptr_list(ufbx_line_curve, p_line, scene->line_curves) {
ufbxi_update_line_curve(*p_line);
}
if (initial) {
ufbxi_update_initial_clusters(scene);
ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) {
ufbxi_update_pose(*p_pose);
}
}
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) {
ufbxi_update_skin_cluster(*p_cluster);
}
ufbxi_for_ptr_list(ufbx_blend_channel, p_channel, scene->blend_channels) {
ufbxi_update_blend_channel(*p_channel);
}
ufbxi_for_ptr_list(ufbx_texture, p_texture, scene->textures) {
ufbxi_update_texture(*p_texture);
}
ufbxi_propagate_main_textures(scene);
ufbxi_for_ptr_list(ufbx_material, p_material, scene->materials) {
ufbxi_update_material(scene, *p_material);
}
ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, scene->anim_stacks) {
ufbxi_update_anim_stack(scene, *p_stack);
}
ufbxi_for_ptr_list(ufbx_display_layer, p_layer, scene->display_layers) {
ufbxi_update_display_layer(*p_layer);
}
ufbxi_for_ptr_list(ufbx_constraint, p_constraint, scene->constraints) {
ufbxi_update_constraint(*p_constraint);
}
ufbxi_update_anim(scene);
}
static ufbxi_noinline void ufbxi_update_scene_metadata(ufbx_metadata *metadata)
{
ufbx_props *props = &metadata->scene_props;
metadata->original_application.vendor = ufbx_find_string(props, "Original|ApplicationVendor", ufbx_empty_string);
metadata->original_application.name = ufbx_find_string(props, "Original|ApplicationName", ufbx_empty_string);
metadata->original_application.version = ufbx_find_string(props, "Original|ApplicationVersion", ufbx_empty_string);
metadata->latest_application.vendor = ufbx_find_string(props, "LastSaved|ApplicationVendor", ufbx_empty_string);
metadata->latest_application.name = ufbx_find_string(props, "LastSaved|ApplicationName", ufbx_empty_string);
metadata->latest_application.version = ufbx_find_string(props, "LastSaved|ApplicationVersion", ufbx_empty_string);
}
static const ufbx_real ufbxi_pow10_targets[] = {
0.0f,
(ufbx_real)1e-8, (ufbx_real)1e-7, (ufbx_real)1e-6, (ufbx_real)1e-5,
(ufbx_real)1e-4, (ufbx_real)1e-3, (ufbx_real)1e-2, (ufbx_real)1e-1,
(ufbx_real)1e+0, (ufbx_real)1e+1, (ufbx_real)1e+2, (ufbx_real)1e+3,
(ufbx_real)1e+4, (ufbx_real)1e+5, (ufbx_real)1e+6, (ufbx_real)1e+7,
(ufbx_real)1e+8, (ufbx_real)1e+9,
};
static ufbxi_noinline ufbx_real ufbxi_round_if_near(const ufbx_real *targets, size_t num_targets, ufbx_real value)
{
for (size_t i = 0; i < num_targets; i++) {
double target = targets[i];
double error = target * 9.5367431640625e-7;
if (error < 0.0) error = -error;
if (error < 7.52316384526264005e-37) error = 7.52316384526264005e-37;
if (value >= target - error && value <= target + error) {
return (ufbx_real)target;
}
}
return value;
}
static ufbxi_noinline void ufbxi_update_scene_settings(ufbx_scene_settings *settings)
{
ufbx_real unit_scale_factor = ufbxi_find_real(&settings->props, ufbxi_UnitScaleFactor, 1.0f);
ufbx_real original_unit_scale_factor = ufbxi_find_real(&settings->props, ufbxi_OriginalUnitScaleFactor, unit_scale_factor);
settings->axes.up = ufbxi_find_axis(&settings->props, ufbxi_UpAxis, ufbxi_UpAxisSign);
settings->axes.front = ufbxi_find_axis(&settings->props, ufbxi_FrontAxis, ufbxi_FrontAxisSign);
settings->axes.right = ufbxi_find_axis(&settings->props, ufbxi_CoordAxis, ufbxi_CoordAxisSign);
settings->unit_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), unit_scale_factor * (ufbx_real)0.01);
settings->original_unit_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), original_unit_scale_factor * (ufbx_real)0.01);
settings->frames_per_second = ufbxi_find_real(&settings->props, ufbxi_CustomFrameRate, 24.0f);
settings->ambient_color = ufbxi_find_vec3(&settings->props, ufbxi_AmbientColor, 0.0f, 0.0f, 0.0f);
settings->original_axis_up = ufbxi_find_axis(&settings->props, ufbxi_OriginalUpAxis, ufbxi_OriginalUpAxisSign);
ufbx_prop *default_camera = ufbxi_find_prop(&settings->props, ufbxi_DefaultCamera);
if (default_camera) {
settings->default_camera = default_camera->value_str;
} else {
settings->default_camera = ufbx_empty_string;
}
settings->time_mode = (ufbx_time_mode)ufbxi_find_enum(&settings->props, ufbxi_TimeMode, UFBX_TIME_MODE_24_FPS, UFBX_TIME_MODE_59_94_FPS);
settings->time_protocol = (ufbx_time_protocol)ufbxi_find_enum(&settings->props, ufbxi_TimeProtocol, UFBX_TIME_PROTOCOL_DEFAULT, UFBX_TIME_PROTOCOL_DEFAULT);
settings->snap_mode = (ufbx_snap_mode)ufbxi_find_enum(&settings->props, ufbxi_SnapOnFrameMode, UFBX_SNAP_MODE_NONE, UFBX_SNAP_MODE_SNAP_AND_PLAY);
if (settings->time_mode != UFBX_TIME_MODE_CUSTOM) {
settings->frames_per_second = ufbxi_time_mode_fps[settings->time_mode];
}
}
static ufbxi_noinline void ufbxi_update_scene_settings_obj(ufbxi_context *uc)
{
ufbx_scene_settings *settings = &uc->scene.settings;
settings->original_unit_meters = settings->unit_meters = uc->opts.obj_unit_meters;
if (ufbx_coordinate_axes_valid(uc->opts.obj_axes)) {
settings->axes = uc->opts.obj_axes;
} else {
settings->axes.right = UFBX_COORDINATE_AXIS_UNKNOWN;
settings->axes.up = UFBX_COORDINATE_AXIS_UNKNOWN;
settings->axes.front = UFBX_COORDINATE_AXIS_UNKNOWN;
}
}
// -- Geometry caches
#if UFBXI_FEATURE_GEOMETRY_CACHE
typedef struct {
ufbxi_refcount refcount;
ufbx_geometry_cache cache;
uint32_t magic;
bool owned_by_scene;
ufbxi_buf string_buf;
} ufbxi_geometry_cache_imp;
ufbx_static_assert(geometry_cache_imp_offset, offsetof(ufbxi_geometry_cache_imp, cache) == sizeof(ufbxi_refcount));
typedef struct {
ufbx_string name;
ufbx_string interpretation;
uint32_t sample_rate;
uint32_t start_time;
uint32_t end_time;
uint32_t current_time;
uint32_t consecutive_fails;
bool try_load;
} ufbxi_cache_tmp_channel;
typedef enum {
UFBXI_CACHE_XML_TYPE_NONE,
UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME,
UFBXI_CACHE_XML_TYPE_SINGLE_FILE,
} ufbxi_cache_xml_type;
typedef enum {
UFBXI_CACHE_XML_FORMAT_NONE,
UFBXI_CACHE_XML_FORMAT_MCC,
UFBXI_CACHE_XML_FORMAT_MCX,
} ufbxi_cache_xml_format;
typedef struct {
ufbx_error error;
ufbx_string filename;
bool owned_by_scene;
bool ignore_if_not_found;
ufbx_geometry_cache_opts opts;
ufbxi_allocator *ator_tmp;
ufbxi_allocator ator_result;
ufbxi_buf result;
ufbxi_buf tmp;
ufbxi_buf tmp_stack;
ufbxi_cache_tmp_channel *channels;
size_t num_channels;
// Temporary array
char *tmp_arr;
size_t tmp_arr_size;
ufbxi_string_pool string_pool;
ufbx_open_file_cb open_file_cb;
double frames_per_second;
ufbx_string stream_filename;
ufbx_stream stream;
bool mc_for8;
ufbx_string xml_filename;
uint32_t xml_ticks_per_frame;
ufbxi_cache_xml_type xml_type;
ufbxi_cache_xml_format xml_format;
ufbx_string channel_name;
char *name_buf;
size_t name_cap;
uint64_t file_offset;
const char *pos, *pos_end;
ufbx_geometry_cache cache;
ufbxi_geometry_cache_imp *imp;
char buffer[128];
} ufbxi_cache_context;
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_read(ufbxi_cache_context *cc, void *dst, size_t size, bool allow_eof)
{
size_t buffered = ufbxi_min_sz(ufbxi_to_size(cc->pos_end - cc->pos), size);
memcpy(dst, cc->pos, buffered);
cc->pos += buffered;
size -= buffered;
cc->file_offset += buffered;
if (size == 0) return 1;
dst = (char*)dst + buffered;
if (size >= sizeof(cc->buffer)) {
size_t num_read = cc->stream.read_fn(cc->stream.user, dst, size);
ufbxi_check_err_msg(&cc->error, num_read <= size, "IO error");
if (!allow_eof) {
ufbxi_check_err_msg(&cc->error, num_read == size, "Truncated file");
}
cc->file_offset += num_read;
size -= num_read;
dst = (char*)dst + num_read;
} else {
size_t num_read = cc->stream.read_fn(cc->stream.user, cc->buffer, sizeof(cc->buffer));
ufbxi_check_err_msg(&cc->error, num_read <= sizeof(cc->buffer), "IO error");
if (!allow_eof) {
ufbxi_check_err_msg(&cc->error, num_read >= size, "Truncated file");
}
cc->pos = cc->buffer;
cc->pos_end = cc->buffer + sizeof(cc->buffer);
memcpy(dst, cc->pos, size);
cc->pos += size;
cc->file_offset += size;
size_t num_written = ufbxi_min_sz(size, num_read);
size -= num_written;
dst = (char*)dst + num_written;
}
if (size > 0) {
memset(dst, 0, size);
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_skip(ufbxi_cache_context *cc, uint64_t size)
{
cc->file_offset += size;
uint64_t buffered = ufbxi_min64((uint64_t)(cc->pos_end - cc->pos), size);
cc->pos += buffered;
size -= buffered;
if (cc->stream.skip_fn) {
while (size >= UFBXI_MAX_SKIP_SIZE) {
size -= UFBXI_MAX_SKIP_SIZE;
ufbxi_check_err_msg(&cc->error, cc->stream.skip_fn(cc->stream.user, UFBXI_MAX_SKIP_SIZE - 1), "Truncated file");
// Check that we can read at least one byte in case the file is broken
// and causes us to seek indefinitely forwards as `fseek()` does not
// report if we hit EOF...
char single_byte[1]; // ufbxi_uninit
size_t num_read = cc->stream.read_fn(cc->stream.user, single_byte, 1);
ufbxi_check_err_msg(&cc->error, num_read <= 1, "IO error");
ufbxi_check_err_msg(&cc->error, num_read == 1, "Truncated file");
}
if (size > 0) {
ufbxi_check_err_msg(&cc->error, cc->stream.skip_fn(cc->stream.user, (size_t)size), "Truncated file");
}
} else {
char skip_buf[2048]; // ufbxi_uninit
while (size > 0) {
size_t to_skip = (size_t)ufbxi_min64(size, sizeof(skip_buf));
size -= to_skip;
ufbxi_check_err_msg(&cc->error, cc->stream.read_fn(cc->stream.user, skip_buf, to_skip), "Truncated file");
}
}
return 1;
}
#define ufbxi_cache_mc_tag(a,b,c,d) ((uint32_t)(a)<<24u | (uint32_t)(b)<<16 | (uint32_t)(c)<<8u | (uint32_t)(d))
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_tag(ufbxi_cache_context *cc, uint32_t *p_tag)
{
char buf[4]; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, true));
*p_tag = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3];
if (*p_tag == ufbxi_cache_mc_tag('F','O','R','8')) {
cc->mc_for8 = true;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u32(ufbxi_cache_context *cc, uint32_t *p_value)
{
char buf[4]; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false));
*p_value = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3];
if (cc->mc_for8) {
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false));
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u64(ufbxi_cache_context *cc, uint64_t *p_value)
{
if (!cc->mc_for8) {
uint32_t v32; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &v32));
*p_value = v32;
} else {
char buf[8]; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 8, false));
uint32_t hi = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3];
uint32_t lo = (uint32_t)(uint8_t)buf[4]<<24u | (uint32_t)(uint8_t)buf[5]<<16 | (uint32_t)(uint8_t)buf[6]<<8u | (uint32_t)(uint8_t)buf[7];
*p_value = (uint64_t)hi << 32u | (uint64_t)lo;
}
return 1;
}
static const uint8_t ufbxi_cache_data_format_size[] = {
0, 4, 12, 8, 24,
};
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_mc(ufbxi_cache_context *cc)
{
uint32_t version = 0, time_start = 0, time_end = 0;
uint32_t count = 0, time = 0;
char skip_buf[8]; // ufbxi_uninit
for (;;) {
uint32_t tag; // ufbxi_uninit
uint64_t size; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_tag(cc, &tag));
if (tag == 0) break;
if (tag == ufbxi_cache_mc_tag('C','A','C','H') || tag == ufbxi_cache_mc_tag('M','Y','C','H')) {
continue;
}
if (cc->mc_for8) {
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, skip_buf, 4, false));
}
ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u64(cc, &size));
uint64_t begin = cc->file_offset;
size_t alignment = cc->mc_for8 ? 8 : 4;
ufbx_cache_data_format format = UFBX_CACHE_DATA_FORMAT_UNKNOWN;
switch (tag) {
case ufbxi_cache_mc_tag('F','O','R','4'): cc->mc_for8 = false; break;
case ufbxi_cache_mc_tag('F','O','R','8'): cc->mc_for8 = true; break;
case ufbxi_cache_mc_tag('V','R','S','N'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &version)); break;
case ufbxi_cache_mc_tag('S','T','I','M'):
ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time_start));
time = time_start;
break;
case ufbxi_cache_mc_tag('E','T','I','M'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time_end)); break;
case ufbxi_cache_mc_tag('T','I','M','E'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &time)); break;
case ufbxi_cache_mc_tag('C','H','N','M'): {
ufbxi_check_err(&cc->error, size > 0 && size < SIZE_MAX);
size_t length = (size_t)size - 1;
size_t padded_length = ((size_t)size + alignment - 1) & ~(alignment - 1);
ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->name_buf, &cc->name_cap, padded_length));
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, cc->name_buf, padded_length, false));
cc->channel_name.data = cc->name_buf;
cc->channel_name.length = length;
ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &cc->channel_name, false));
} break;
case ufbxi_cache_mc_tag('S','I','Z','E'): ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &count)); break;
case ufbxi_cache_mc_tag('F','V','C','A'): format = UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT; break;
case ufbxi_cache_mc_tag('D','V','C','A'): format = UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE; break;
case ufbxi_cache_mc_tag('F','B','C','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_FLOAT; break;
case ufbxi_cache_mc_tag('D','B','C','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE; break;
case ufbxi_cache_mc_tag('D','B','L','A'): format = UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE; break;
default: ufbxi_fail_err(&cc->error, "Unknown tag");
}
if (format != UFBX_CACHE_DATA_FORMAT_UNKNOWN) {
ufbx_cache_frame *frame = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_frame, 1);
ufbxi_check_err(&cc->error, frame);
uint32_t elem_size = ufbxi_cache_data_format_size[format];
uint64_t total_size = (uint64_t)elem_size * (uint64_t)count;
ufbxi_check_err(&cc->error, size >= elem_size * count);
frame->channel = cc->channel_name;
frame->time = (double)time * (1.0/6000.0);
frame->filename = cc->stream_filename;
frame->data_format = format;
frame->data_encoding = UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN;
frame->data_offset = cc->file_offset;
frame->data_count = count;
frame->data_element_bytes = elem_size;
frame->data_total_bytes = total_size;
frame->file_format = UFBX_CACHE_FILE_FORMAT_MC;
uint64_t end = begin + ((size + alignment - 1) & ~(uint64_t)(alignment - 1));
ufbxi_check_err(&cc->error, end >= cc->file_offset);
uint64_t left = end - cc->file_offset;
ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, left));
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_pc2(ufbxi_cache_context *cc)
{
char header[32]; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, header, sizeof(header), false));
uint32_t version = ufbxi_read_u32(header + 12);
uint32_t num_points = ufbxi_read_u32(header + 16);
double start_frame = ufbxi_read_f32(header + 20);
double frames_per_sample = ufbxi_read_f32(header + 24);
uint32_t num_samples = ufbxi_read_u32(header + 28);
(void)version;
ufbx_cache_frame *frames = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_frame, num_samples);
ufbxi_check_err(&cc->error, frames);
uint64_t total_points = (uint64_t)num_points * (uint64_t)num_samples;
ufbxi_check_err(&cc->error, total_points < UINT64_MAX / 12);
uint64_t offset = cc->file_offset;
// Skip almost to the end of the data and try to read one byte as there's
// nothing after the data so we can't detect EOF..
if (total_points > 0) {
char last_byte[1]; // ufbxi_uninit
ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, total_points * 12 - 1));
ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, last_byte, 1, false));
}
for (uint32_t i = 0; i < num_samples; i++) {
ufbx_cache_frame *frame = &frames[i];
double sample_frame = start_frame + (double)i * frames_per_sample;
frame->channel = cc->channel_name;
frame->time = sample_frame / cc->frames_per_second;
frame->filename = cc->stream_filename;
frame->data_format = UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT;
frame->data_encoding = UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN;
frame->data_offset = offset;
frame->data_count = num_points;
frame->data_element_bytes = 12;
frame->data_total_bytes = num_points * 12;
frame->file_format = UFBX_CACHE_FILE_FORMAT_PC2;
offset += num_points * 12;
}
return 1;
}
static ufbxi_noinline bool ufbxi_tmp_channel_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbxi_cache_tmp_channel *a = (const ufbxi_cache_tmp_channel *)va, *b = (const ufbxi_cache_tmp_channel *)vb;
return ufbxi_str_less(a->name, b->name);
}
static ufbxi_noinline int ufbxi_cache_sort_tmp_channels(ufbxi_cache_context *cc, ufbxi_cache_tmp_channel *channels, size_t count)
{
ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->tmp_arr, &cc->tmp_arr_size, count * sizeof(ufbxi_cache_tmp_channel)));
ufbxi_stable_sort(sizeof(ufbxi_cache_tmp_channel), 16, channels, cc->tmp_arr, count, &ufbxi_tmp_channel_less, NULL);
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml_imp(ufbxi_cache_context *cc, ufbxi_xml_document *doc)
{
cc->xml_ticks_per_frame = 250;
cc->xml_filename = cc->stream_filename;
ufbxi_xml_tag *tag_root = ufbxi_xml_find_child(doc->root, "Autodesk_Cache_File");
if (tag_root) {
ufbxi_xml_tag *tag_type = ufbxi_xml_find_child(tag_root, "cacheType");
ufbxi_xml_tag *tag_fps = ufbxi_xml_find_child(tag_root, "cacheTimePerFrame");
ufbxi_xml_tag *tag_channels = ufbxi_xml_find_child(tag_root, "Channels");
size_t num_extra = 0;
ufbxi_for(ufbxi_xml_tag, tag, tag_root->children, tag_root->num_children) {
if (tag->num_children != 1) continue;
if (strcmp(tag->name.data, "extra") != 0) continue;
ufbx_string *extra = ufbxi_push(&cc->tmp_stack, ufbx_string, 1);
ufbxi_check_err(&cc->error, extra);
*extra = tag->children[0].text;
ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, extra, false));
num_extra++;
}
cc->cache.extra_info.count = num_extra;
cc->cache.extra_info.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_string, num_extra);
ufbxi_check_err(&cc->error, cc->cache.extra_info.data);
if (tag_type) {
ufbxi_xml_attrib *type = ufbxi_xml_find_attrib(tag_type, "Type");
ufbxi_xml_attrib *format = ufbxi_xml_find_attrib(tag_type, "Format");
if (type) {
if (!strcmp(type->value.data, "OneFilePerFrame")) {
cc->xml_type = UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME;
} else if (!strcmp(type->value.data, "OneFile")) {
cc->xml_type = UFBXI_CACHE_XML_TYPE_SINGLE_FILE;
}
}
if (format) {
if (!strcmp(format->value.data, "mcc")) {
cc->xml_format = UFBXI_CACHE_XML_FORMAT_MCC;
} else if (!strcmp(format->value.data, "mcx")) {
cc->xml_format = UFBXI_CACHE_XML_FORMAT_MCX;
}
}
}
if (tag_fps) {
ufbxi_xml_attrib *fps = ufbxi_xml_find_attrib(tag_fps, "TimePerFrame");
if (fps) {
int value = atoi(fps->value.data);
if (value > 0) {
cc->xml_ticks_per_frame = (uint32_t)value;
}
}
}
if (tag_channels) {
cc->channels = ufbxi_push_zero(&cc->tmp, ufbxi_cache_tmp_channel, tag_channels->num_children);
ufbxi_check_err(&cc->error, cc->channels);
ufbxi_for(ufbxi_xml_tag, tag, tag_channels->children, tag_channels->num_children) {
ufbxi_xml_attrib *name = ufbxi_xml_find_attrib(tag, "ChannelName");
ufbxi_xml_attrib *type = ufbxi_xml_find_attrib(tag, "ChannelType");
ufbxi_xml_attrib *interpretation = ufbxi_xml_find_attrib(tag, "ChannelInterpretation");
if (!(name && type && interpretation)) continue;
ufbxi_cache_tmp_channel *channel = &cc->channels[cc->num_channels++];
channel->name = name->value;
channel->interpretation = interpretation->value;
ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &channel->name, false));
ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &channel->interpretation, false));
ufbxi_xml_attrib *sampling_rate = ufbxi_xml_find_attrib(tag, "SamplingRate");
ufbxi_xml_attrib *start_time = ufbxi_xml_find_attrib(tag, "StartTime");
ufbxi_xml_attrib *end_time = ufbxi_xml_find_attrib(tag, "EndTime");
if (sampling_rate && start_time && end_time) {
channel->sample_rate = (uint32_t)atoi(sampling_rate->value.data);
channel->start_time = (uint32_t)atoi(start_time->value.data);
channel->end_time = (uint32_t)atoi(end_time->value.data);
channel->current_time = channel->start_time;
channel->try_load = true;
}
}
}
}
ufbxi_check_err(&cc->error, ufbxi_cache_sort_tmp_channels(cc, cc->channels, cc->num_channels));
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_xml(ufbxi_cache_context *cc)
{
ufbxi_xml_load_opts opts = { 0 };
opts.ator = cc->ator_tmp;
opts.read_fn = cc->stream.read_fn;
opts.read_user = cc->stream.user;
opts.prefix = cc->pos;
opts.prefix_length = ufbxi_to_size(cc->pos_end - cc->pos);
ufbxi_xml_document *doc = ufbxi_load_xml(&opts, &cc->error);
ufbxi_check_err(&cc->error, doc);
int xml_ok = ufbxi_cache_load_xml_imp(cc, doc);
ufbxi_free_xml(doc);
ufbxi_check_err(&cc->error, xml_ok);
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_file(ufbxi_cache_context *cc, ufbx_string filename)
{
cc->stream_filename = filename;
ufbxi_check_err(&cc->error, ufbxi_push_string_place_str(&cc->string_pool, &cc->stream_filename, false));
// Assume all files have at least 16 bytes of header
size_t magic_len = cc->stream.read_fn(cc->stream.user, cc->buffer, 16);
ufbxi_check_err_msg(&cc->error, magic_len <= 16, "IO error");
ufbxi_check_err_msg(&cc->error, magic_len == 16, "Truncated file");
cc->pos = cc->buffer;
cc->pos_end = cc->buffer + 16;
cc->file_offset = 0;
if (!memcmp(cc->buffer, "POINTCACHE2", 11)) {
ufbxi_check_err(&cc->error, ufbxi_cache_load_pc2(cc));
} else if (!memcmp(cc->buffer, "FOR4", 4) || !memcmp(cc->buffer, "FOR8", 4)) {
ufbxi_check_err(&cc->error, ufbxi_cache_load_mc(cc));
} else {
ufbxi_check_err(&cc->error, ufbxi_cache_load_xml(cc));
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_try_open_file(ufbxi_cache_context *cc, ufbx_string filename, const ufbx_blob *original_filename, bool *p_found)
{
memset(&cc->stream, 0, sizeof(cc->stream));
ufbxi_regression_assert(strlen(filename.data) == filename.length);
if (!ufbxi_open_file(&cc->open_file_cb, &cc->stream, filename.data, filename.length, original_filename, cc->ator_tmp, UFBX_OPEN_FILE_GEOMETRY_CACHE)) {
return 1;
}
int ok = ufbxi_cache_load_file(cc, filename);
*p_found = true;
if (cc->stream.close_fn) {
cc->stream.close_fn(cc->stream.user);
}
return ok;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_frame_files(ufbxi_cache_context *cc)
{
if (cc->xml_filename.length == 0) return 1;
const char *extension = NULL;
switch (cc->xml_format) {
case UFBXI_CACHE_XML_FORMAT_MCC: extension = "mc"; break;
case UFBXI_CACHE_XML_FORMAT_MCX: extension = "mcx"; break;
default: return 1;
}
// Ensure worst case space for `path/filenameFrame123Tick456.mcx`
size_t name_buf_len = cc->xml_filename.length + 64;
char *name_buf = ufbxi_push(&cc->tmp, char, name_buf_len);
ufbxi_check_err(&cc->error, name_buf);
// Find the prefix before `.xml`
size_t prefix_len = cc->xml_filename.length;
for (size_t i = prefix_len; i > 0; --i) {
if (cc->xml_filename.data[i - 1] == '.') {
prefix_len = i - 1;
break;
}
}
memcpy(name_buf, cc->xml_filename.data, prefix_len);
char *suffix_data = name_buf + prefix_len;
size_t suffix_len = name_buf_len - prefix_len;
ufbx_string filename;
filename.data = name_buf;
if (cc->xml_type == UFBXI_CACHE_XML_TYPE_SINGLE_FILE) {
filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, ".%s", extension);
bool found = false;
ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename, NULL, &found));
} else if (cc->xml_type == UFBXI_CACHE_XML_TYPE_FILE_PER_FRAME) {
uint32_t lowest_time = 0;
for (;;) {
// Find the first `time >= lowest_time` value that has data in some channel
uint32_t time = UINT32_MAX;
ufbxi_for(ufbxi_cache_tmp_channel, chan, cc->channels, cc->num_channels) {
if (!chan->try_load || chan->consecutive_fails > 10) continue;
uint32_t sample_rate = chan->sample_rate ? chan->sample_rate : cc->xml_ticks_per_frame;
if (chan->current_time < lowest_time) {
uint32_t delta = (lowest_time - chan->current_time - 1) / sample_rate;
chan->current_time += delta * sample_rate;
if (UINT32_MAX - chan->current_time >= sample_rate) {
chan->current_time += sample_rate;
} else {
chan->try_load = false;
continue;
}
}
if (chan->current_time <= chan->end_time) {
time = ufbxi_min32(time, chan->current_time);
}
}
if (time == UINT32_MAX) break;
// Try to load a file at the specified frame/tick
uint32_t frame = time / cc->xml_ticks_per_frame;
uint32_t tick = time % cc->xml_ticks_per_frame;
if (tick == 0) {
filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, "Frame%u.%s", frame, extension);
} else {
filename.length = prefix_len + (size_t)ufbxi_snprintf(suffix_data, suffix_len, "Frame%uTick%u.%s", frame, tick, extension);
}
bool found = false;
ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename, NULL, &found));
// Update channel status
ufbxi_for(ufbxi_cache_tmp_channel, chan, cc->channels, cc->num_channels) {
if (chan->current_time == time) {
chan->consecutive_fails = found ? 0 : chan->consecutive_fails + 1;
}
}
lowest_time = time + 1;
}
}
return 1;
}
static ufbxi_noinline bool ufbxi_cmp_cache_frame_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_cache_frame *a = (const ufbx_cache_frame *)va, *b = (const ufbx_cache_frame *)vb;
if (a->channel.data != b->channel.data) {
// Channel names should be interned
ufbxi_regression_assert(!ufbxi_str_equal(a->channel, b->channel));
return ufbxi_str_less(a->channel, b->channel);
}
return a->time < b->time;
}
static ufbxi_noinline int ufbxi_cache_sort_frames(ufbxi_cache_context *cc, ufbx_cache_frame *frames, size_t count)
{
ufbxi_check_err(&cc->error, ufbxi_grow_array(cc->ator_tmp, &cc->tmp_arr, &cc->tmp_arr_size, count * sizeof(ufbx_cache_frame)));
ufbxi_stable_sort(sizeof(ufbx_cache_frame), 16, frames, cc->tmp_arr, count, &ufbxi_cmp_cache_frame_less, NULL);
return 1;
}
typedef struct {
ufbx_cache_interpretation interpretation;
const char *pattern;
} ufbxi_cache_interpretation_name;
static const ufbxi_cache_interpretation_name ufbxi_cache_interpretation_names[] = {
{ UFBX_CACHE_INTERPRETATION_POINTS, "\\cpoints?" },
{ UFBX_CACHE_INTERPRETATION_VERTEX_POSITION, "\\cpositions?" },
{ UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL, "\\cnormals?" },
};
static ufbxi_noinline int ufbxi_cache_setup_channels(ufbxi_cache_context *cc)
{
ufbxi_cache_tmp_channel *tmp_chan = cc->channels, *tmp_end = ufbxi_add_ptr(tmp_chan, cc->num_channels);
size_t begin = 0, num_channels = 0;
while (begin < cc->cache.frames.count) {
ufbx_cache_frame *frame = &cc->cache.frames.data[begin];
size_t end = begin + 1;
while (end < cc->cache.frames.count && cc->cache.frames.data[end].channel.data == frame->channel.data) {
end++;
}
ufbx_cache_channel *chan = ufbxi_push_zero(&cc->tmp_stack, ufbx_cache_channel, 1);
ufbxi_check_err(&cc->error, chan);
chan->name = frame->channel;
chan->interpretation_name = ufbx_empty_string;
chan->frames.data = frame;
chan->frames.count = end - begin;
while (tmp_chan < tmp_end && ufbxi_str_less(tmp_chan->name, chan->name)) {
tmp_chan++;
}
if (tmp_chan < tmp_end && ufbxi_str_equal(tmp_chan->name, chan->name)) {
chan->interpretation_name = tmp_chan->interpretation;
}
if (frame->file_format == UFBX_CACHE_FILE_FORMAT_PC2) {
chan->interpretation = UFBX_CACHE_INTERPRETATION_VERTEX_POSITION;
} else {
ufbxi_for(const ufbxi_cache_interpretation_name, name, ufbxi_cache_interpretation_names, ufbxi_arraycount(ufbxi_cache_interpretation_names)) {
if (ufbxi_match(&chan->interpretation_name, name->pattern)) {
chan->interpretation = name->interpretation;
break;
}
}
}
ufbx_mirror_axis mirror_axis = UFBX_MIRROR_AXIS_NONE;
ufbx_real scale_factor = 1.0f;
if (chan->interpretation != UFBX_CACHE_INTERPRETATION_UNKNOWN) {
mirror_axis = cc->opts.mirror_axis;
if (cc->opts.use_scale_factor) {
scale_factor = cc->opts.scale_factor;
}
}
chan->mirror_axis = mirror_axis;
chan->scale_factor = scale_factor;
ufbxi_for_list(ufbx_cache_frame, f, chan->frames) {
f->mirror_axis = mirror_axis;
f->scale_factor = scale_factor;
}
num_channels++;
begin = end;
}
cc->cache.channels.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_cache_channel, num_channels);
ufbxi_check_err(&cc->error, cc->cache.channels.data);
cc->cache.channels.count = num_channels;
return 1;
}
static ufbxi_noinline int ufbxi_cache_load_imp(ufbxi_cache_context *cc, ufbx_string filename)
{
cc->tmp.ator = cc->ator_tmp;
cc->tmp_stack.ator = cc->ator_tmp;
cc->channel_name.data = ufbxi_empty_char;
if (!cc->open_file_cb.fn) {
cc->open_file_cb.fn = ufbx_default_open_file;
}
// Make sure the filename we pass to `open_file_fn()` is NULL-terminated
char *filename_data = ufbxi_push(&cc->tmp, char, filename.length + 1);
ufbxi_check_err(&cc->error, filename_data);
memcpy(filename_data, filename.data, filename.length);
filename_data[filename.length] = '\0';
ufbx_string filename_copy = { filename_data, filename.length };
// TODO: NULL termination!
bool found = false;
ufbxi_check_err(&cc->error, ufbxi_cache_try_open_file(cc, filename_copy, NULL, &found));
if (!found) {
ufbxi_set_err_info(&cc->error, filename.data, filename.length);
ufbxi_fail_err_msg(&cc->error, "open_file_fn()", "File not found");
}
cc->cache.root_filename = cc->stream_filename;
ufbxi_check_err(&cc->error, ufbxi_cache_load_frame_files(cc));
size_t num_frames = cc->tmp_stack.num_items;
cc->cache.frames.count = num_frames;
cc->cache.frames.data = ufbxi_push_pop(&cc->result, &cc->tmp_stack, ufbx_cache_frame, num_frames);
ufbxi_check_err(&cc->error, cc->cache.frames.data);
ufbxi_check_err(&cc->error, ufbxi_cache_sort_frames(cc, cc->cache.frames.data, cc->cache.frames.count));
ufbxi_check_err(&cc->error, ufbxi_cache_setup_channels(cc));
// Must be last allocation!
cc->imp = ufbxi_push(&cc->result, ufbxi_geometry_cache_imp, 1);
ufbxi_check_err(&cc->error, cc->imp);
ufbxi_init_ref(&cc->imp->refcount, UFBXI_CACHE_IMP_MAGIC, NULL);
cc->imp->cache = cc->cache;
cc->imp->magic = UFBXI_CACHE_IMP_MAGIC;
cc->imp->owned_by_scene = cc->owned_by_scene;
cc->imp->refcount.ator = cc->ator_result;
cc->imp->refcount.buf = cc->result;
cc->imp->refcount.buf.ator = &cc->imp->refcount.ator;
cc->imp->string_buf = cc->string_pool.buf;
cc->imp->string_buf.ator = &cc->imp->refcount.ator;
return 1;
}
ufbxi_noinline static ufbx_geometry_cache *ufbxi_cache_load(ufbxi_cache_context *cc, ufbx_string filename)
{
int ok = ufbxi_cache_load_imp(cc, filename);
ufbxi_buf_free(&cc->tmp);
ufbxi_buf_free(&cc->tmp_stack);
ufbxi_free(cc->ator_tmp, char, cc->name_buf, cc->name_cap);
ufbxi_free(cc->ator_tmp, char, cc->tmp_arr, cc->tmp_arr_size);
if (!cc->owned_by_scene) {
ufbxi_string_pool_temp_free(&cc->string_pool);
ufbxi_free_ator(cc->ator_tmp);
}
if (ok) {
return &cc->imp->cache;
} else {
ufbxi_fix_error_type(&cc->error, "Failed to load geometry cache");
if (!cc->owned_by_scene) {
ufbxi_buf_free(&cc->string_pool.buf);
ufbxi_free_ator(&cc->ator_result);
}
return NULL;
}
}
ufbxi_noinline static ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error)
{
ufbx_geometry_cache_opts opts; // ufbxi_uninit
if (user_opts) {
opts = *user_opts;
} else {
memset(&opts, 0, sizeof(opts));
}
ufbxi_cache_context cc = { UFBX_ERROR_NONE };
ufbxi_allocator ator_tmp = { 0 };
ufbxi_init_ator(&cc.error, &ator_tmp, &opts.temp_allocator, "temp");
ufbxi_init_ator(&cc.error, &cc.ator_result, &opts.result_allocator, "result");
cc.ator_tmp = &ator_tmp;
cc.opts = opts;
cc.open_file_cb = opts.open_file_cb;
cc.string_pool.error = &cc.error;
ufbxi_map_init(&cc.string_pool.map, cc.ator_tmp, &ufbxi_map_cmp_string, NULL);
cc.string_pool.buf.ator = &cc.ator_result;
cc.string_pool.buf.unordered = true;
cc.string_pool.initial_size = 64;
cc.result.ator = &cc.ator_result;
cc.frames_per_second = opts.frames_per_second > 0.0 ? opts.frames_per_second : 30.0;
ufbx_geometry_cache *cache = ufbxi_cache_load(&cc, filename);
if (p_error) {
if (cache) {
ufbxi_clear_error(p_error);
} else {
*p_error = cc.error;
}
}
return cache;
}
static ufbxi_noinline void ufbxi_free_geometry_cache_imp(ufbxi_geometry_cache_imp *imp)
{
ufbx_assert(imp->magic == UFBXI_CACHE_IMP_MAGIC);
ufbxi_buf_free(&imp->string_buf);
}
#else
typedef struct {
ufbxi_refcount refcount;
uint32_t magic;
bool owned_by_scene;
} ufbxi_geometry_cache_imp;
static ufbxi_noinline ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error)
{
if (p_error) {
memset(p_error, 0, sizeof(ufbx_error));
ufbxi_fmt_err_info(p_error, "UFBX_ENABLE_GEOMETRY_CACHE");
ufbxi_report_err_msg(p_error, "UFBXI_FEATURE_GEOMETRY_CACHE", "Feature disabled");
}
return NULL;
}
static ufbxi_forceinline void ufbxi_free_geometry_cache_imp(ufbxi_geometry_cache_imp *imp)
{
}
#endif
// -- External files
typedef enum {
UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE,
} ufbxi_external_file_type;
typedef struct {
ufbxi_external_file_type type;
ufbx_string filename;
ufbx_string absolute_filename;
size_t index;
void *data;
size_t data_size;
} ufbxi_external_file;
static bool ufbxi_less_external_file(void *user, const void *va, const void *vb)
{
(void)user;
const ufbxi_external_file *a = (const ufbxi_external_file*)va, *b = (const ufbxi_external_file*)vb;
if (a->type != b->type) return a->type < b->type;
int cmp = ufbxi_str_cmp(a->filename, b->filename);
if (cmp != 0) return cmp < 0;
if (a->index != b->index) return a->index < b->index;
return false;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_cache(ufbxi_context *uc, ufbxi_external_file *file)
{
#if UFBXI_FEATURE_GEOMETRY_CACHE
ufbxi_cache_context cc = { UFBX_ERROR_NONE };
cc.owned_by_scene = true;
cc.open_file_cb = uc->opts.open_file_cb;
cc.frames_per_second = uc->scene.settings.frames_per_second;
// Temporarily "borrow" allocators for the geometry cache
cc.ator_tmp = &uc->ator_tmp;
cc.string_pool = uc->string_pool;
cc.result = uc->result;
cc.opts.mirror_axis = uc->mirror_axis;
cc.opts.use_scale_factor = true;
cc.opts.scale_factor = uc->scene.metadata.geometry_scale;
ufbx_geometry_cache *cache = ufbxi_cache_load(&cc, file->filename);
if (!cache) {
if (cc.error.type == UFBX_ERROR_FILE_NOT_FOUND) {
memset(&cc.error, 0, sizeof(cc.error));
cache = ufbxi_cache_load(&cc, file->absolute_filename);
}
}
// Return the "borrowed" allocators
uc->string_pool = cc.string_pool;
uc->result = cc.result;
if (!cache) {
if (cc.error.type == UFBX_ERROR_FILE_NOT_FOUND) {
if (uc->opts.ignore_missing_external_files) {
ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_EXTERNAL_FILE, "Failed to open geometry cache: %s", file->filename.data));
return 1;
} else {
cc.error.type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND;
cc.error.description.data = "External file not found";
cc.error.description.length = strlen("External file not found");
}
}
uc->error = cc.error;
return 0;
}
file->data = cache;
return 1;
#else
if (uc->opts.ignore_missing_external_files) return 1;
ufbxi_fmt_err_info(&uc->error, "UFBX_ENABLE_GEOMETRY_CACHE");
ufbxi_fail_msg("UFBXI_FEATURE_GEOMETRY_CACHE", "Feature disabled");
#endif
}
static ufbxi_noinline ufbxi_external_file *ufbxi_find_external_file(ufbxi_external_file *files, size_t num_files, ufbxi_external_file_type type, const char *name)
{
size_t ix = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbxi_external_file, 32, &ix, files, 0, num_files,
( type != a->type ? type < a->type : strcmp(a->filename.data, name) < 0 ),
( a->type == type && a->filename.data == name ));
return ix != SIZE_MAX ? &files[ix] : NULL;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_files(ufbxi_context *uc)
{
size_t num_files = 0;
// Gather external files to deduplicate them
ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) {
ufbx_cache_file *cache = *p_cache;
if (cache->filename.length > 0) {
ufbxi_external_file *file = ufbxi_push_zero(&uc->tmp_stack, ufbxi_external_file, 1);
ufbxi_check(file);
file->index = num_files++;
file->type = UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE;
file->filename = cache->filename;
file->absolute_filename = cache->absolute_filename;
}
}
// Sort and load the external files
ufbxi_external_file *files = ufbxi_push_pop(&uc->tmp, &uc->tmp_stack, ufbxi_external_file, num_files);
ufbxi_check(files);
ufbxi_unstable_sort(files, num_files, sizeof(ufbxi_external_file), &ufbxi_less_external_file, NULL);
ufbxi_external_file_type prev_type = UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE;
const char *prev_name = NULL;
ufbxi_for(ufbxi_external_file, file, files, num_files) {
if (file->filename.data == prev_name && file->type == prev_type) continue;
if (file->type == UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE) {
ufbxi_check(ufbxi_load_external_cache(uc, file));
}
prev_name = file->filename.data;
prev_type = file->type;
}
// Patch the loaded files
ufbxi_for_ptr_list(ufbx_cache_file, p_cache, uc->scene.cache_files) {
ufbx_cache_file *cache = *p_cache;
ufbxi_external_file *file = ufbxi_find_external_file(files, num_files,
UFBXI_EXTERNAL_FILE_GEOMETRY_CACHE, cache->filename.data);
if (file && file->data) {
cache->external_cache = (ufbx_geometry_cache*)file->data;
}
}
// Patch the geometry deformers
ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, uc->scene.cache_deformers) {
ufbx_cache_deformer *deformer = *p_deformer;
if (!deformer->file || !deformer->file->external_cache) continue;
ufbx_geometry_cache *cache = deformer->file->external_cache;
deformer->external_cache = cache;
// HACK: It seems like channels may be connected even if the name is wrong
// and they work when exporting from Marvelous to Maya...
if (cache->channels.count == 1) {
deformer->external_channel = &cache->channels.data[0];
} else {
ufbx_string channel = deformer->channel;
size_t ix = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_cache_channel, 16, &ix, cache->channels.data, 0, cache->channels.count,
( ufbxi_str_less(a->name, channel) ), ( a->name.data == channel.data ));
if (ix != SIZE_MAX) {
deformer->external_channel = &cache->channels.data[ix];
}
}
}
return 1;
}
static ufbxi_noinline void ufbxi_transform_to_axes(ufbxi_context *uc, ufbx_coordinate_axes dst_axes)
{
if (!ufbx_coordinate_axes_valid(uc->scene.settings.axes)) return;
if (!ufbxi_axis_matrix(&uc->axis_matrix, uc->scene.settings.axes, dst_axes)) return;
if (ufbx_matrix_determinant(&uc->axis_matrix) < 0.0f) {
if (uc->opts.handedness_conversion_axis != UFBX_MIRROR_AXIS_NONE) {
ufbx_mirror_axis mirror_axis = uc->opts.handedness_conversion_axis;
uc->mirror_axis = mirror_axis;
uc->scene.metadata.mirror_axis = uc->mirror_axis;
ufbxi_mirror_matrix_dst(&uc->axis_matrix, uc->mirror_axis);
ufbxi_dev_assert(ufbx_matrix_determinant(&uc->axis_matrix) >= 0.0f);
ufbxi_for_ptr_list(ufbx_node, p_node, uc->scene.nodes) {
ufbx_node *node = *p_node;
if (!node->is_root) {
node->adjust_mirror_axis = mirror_axis;
}
}
}
}
if (uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT) {
ufbx_matrix axis_mat = uc->axis_matrix;
if (!ufbxi_is_transform_identity(&uc->scene.root_node->local_transform)) {
ufbx_matrix root_mat = ufbx_transform_to_matrix(&uc->scene.root_node->local_transform);
axis_mat = ufbx_matrix_mul(&root_mat, &axis_mat);
}
ufbxi_mirror_matrix(&axis_mat, uc->mirror_axis);
uc->scene.root_node->local_transform = ufbx_matrix_to_transform(&axis_mat);
uc->scene.root_node->node_to_parent = axis_mat;
}
}
static ufbxi_noinline int ufbxi_scale_units(ufbxi_context *uc, ufbx_real target_meters)
{
if (uc->scene.settings.unit_meters <= 0.0f) return 1;
target_meters = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), target_meters);
ufbx_real ratio = uc->scene.settings.unit_meters / target_meters;
ratio = ufbxi_round_if_near(ufbxi_pow10_targets, ufbxi_arraycount(ufbxi_pow10_targets), ratio);
if (ratio == 1.0f) return 1;
uc->unit_scale = ratio;
if (uc->opts.space_conversion == UFBX_SPACE_CONVERSION_TRANSFORM_ROOT) {
uc->scene.root_node->local_transform.scale.x *= ratio;
uc->scene.root_node->local_transform.scale.y *= ratio;
uc->scene.root_node->local_transform.scale.z *= ratio;
uc->scene.root_node->node_to_parent.m00 *= ratio;
uc->scene.root_node->node_to_parent.m01 *= ratio;
uc->scene.root_node->node_to_parent.m02 *= ratio;
uc->scene.root_node->node_to_parent.m10 *= ratio;
uc->scene.root_node->node_to_parent.m11 *= ratio;
uc->scene.root_node->node_to_parent.m12 *= ratio;
uc->scene.root_node->node_to_parent.m20 *= ratio;
uc->scene.root_node->node_to_parent.m21 *= ratio;
uc->scene.root_node->node_to_parent.m22 *= ratio;
}
return 1;
}
// -- Curve evaluation
static ufbxi_forceinline double ufbxi_find_cubic_bezier_t(double p1, double p2, double x0)
{
double p1_3 = p1 * 3.0, p2_3 = p2 * 3.0;
double a = p1_3 - p2_3 + 1.0;
double b = p2_3 - p1_3 - p1_3;
double c = p1_3;
double a_3 = 3.0*a, b_2 = 2.0*b;
double t = x0;
double x1, t2, t3;
// Manually unroll three iterations of Newton-Rhapson, this is enough
// for most tangents
t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0;
t -= x1 / (a_3*t2 + b_2*t + c);
t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0;
t -= x1 / (a_3*t2 + b_2*t + c);
t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0;
t -= x1 / (a_3*t2 + b_2*t + c);
// 4 ULP from 1.0
const double eps = 8.881784197001252e-16;
if (ufbx_fabs(x1) <= eps) return t;
// Perform more iterations until we reach desired accuracy
for (size_t i = 0; i < 4; i++) {
t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0;
t -= x1 / (a_3*t2 + b_2*t + c);
t2 = t*t; t3 = t2*t; x1 = a*t3 + b*t2 + c*t - x0;
t -= x1 / (a_3*t2 + b_2*t + c);
if (ufbx_fabs(x1) <= eps) return t;
}
return t;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_skinning(ufbx_scene *scene, ufbx_error *error, ufbxi_buf *buf_result, ufbxi_buf *buf_tmp,
double time, bool load_caches, ufbx_geometry_cache_data_opts *cache_opts)
{
#if UFBXI_FEATURE_SKINNING_EVALUATION
size_t max_skinned_indices = 0;
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, scene->meshes) {
ufbx_mesh *mesh = *p_mesh;
if (mesh->blend_deformers.count == 0 && mesh->skin_deformers.count == 0 && (mesh->cache_deformers.count == 0 || !load_caches)) continue;
max_skinned_indices = ufbxi_max_sz(max_skinned_indices, mesh->num_indices);
}
ufbx_topo_edge *topo = ufbxi_push(buf_tmp, ufbx_topo_edge, max_skinned_indices);
ufbxi_check_err(error, topo);
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, scene->meshes) {
ufbx_mesh *mesh = *p_mesh;
if (mesh->blend_deformers.count == 0 && mesh->skin_deformers.count == 0 && (mesh->cache_deformers.count == 0 || !load_caches)) continue;
if (mesh->num_vertices == 0) continue;
size_t num_vertices = mesh->num_vertices;
ufbx_vec3 *result_pos = ufbxi_push(buf_result, ufbx_vec3, num_vertices + 1);
ufbxi_check_err(error, result_pos);
result_pos[0] = ufbx_zero_vec3;
result_pos++;
bool cached_position = false, cached_normals = false;
if (load_caches && mesh->cache_deformers.count > 0) {
ufbxi_for_ptr_list(ufbx_cache_deformer, p_cache, mesh->cache_deformers) {
ufbx_cache_channel *channel = (*p_cache)->external_channel;
if (!channel) continue;
if ((channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_POSITION || channel->interpretation == UFBX_CACHE_INTERPRETATION_POINTS) && !cached_position) {
size_t num_read = ufbx_sample_geometry_cache_vec3(channel, time, result_pos, num_vertices, cache_opts);
if (num_read == num_vertices) {
mesh->skinned_is_local = true;
cached_position = true;
}
} else if (channel->interpretation == UFBX_CACHE_INTERPRETATION_VERTEX_NORMAL && !cached_normals) {
// TODO: Is this right at all?
size_t num_normals = mesh->skinned_normal.values.count;
ufbx_vec3 *normal_data = ufbxi_push(buf_result, ufbx_vec3, num_normals + 1);
ufbxi_check_err(error, normal_data);
normal_data[0] = ufbx_zero_vec3;
normal_data++;
size_t num_read = ufbx_sample_geometry_cache_vec3(channel, time, normal_data, num_normals, cache_opts);
if (num_read == num_normals) {
cached_normals = true;
mesh->skinned_normal.values.data = normal_data;
}
}
}
}
if (!cached_position) {
memcpy(result_pos, mesh->vertices.data, num_vertices * sizeof(ufbx_vec3));
ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, mesh->blend_deformers) {
ufbx_add_blend_vertex_offsets(*p_blend, result_pos, num_vertices, 1.0f);
}
// TODO: What should we do about multiple skins??
if (mesh->skin_deformers.count > 0) {
ufbx_matrix *fallback = mesh->instances.count > 0 ? &mesh->instances.data[0]->geometry_to_world : NULL;
ufbx_skin_deformer *skin = mesh->skin_deformers.data[0];
for (size_t i = 0; i < num_vertices; i++) {
ufbx_matrix mat = ufbx_get_skin_vertex_matrix(skin, i, fallback);
result_pos[i] = ufbx_transform_position(&mat, result_pos[i]);
}
mesh->skinned_is_local = false;
}
}
mesh->skinned_position.values.data = result_pos;
if (!cached_normals) {
size_t num_indices = mesh->num_indices;
uint32_t *normal_indices = ufbxi_push(buf_result, uint32_t, num_indices);
ufbxi_check_err(error, normal_indices);
ufbx_compute_topology(mesh, topo, num_indices);
size_t num_normals = ufbx_generate_normal_mapping(mesh, topo, num_indices, normal_indices, num_indices, false);
if (num_normals == mesh->num_vertices) {
mesh->skinned_normal.unique_per_vertex = true;
}
ufbx_vec3 *normal_data = ufbxi_push(buf_result, ufbx_vec3, num_normals + 1);
ufbxi_check_err(error, normal_data);
normal_data[0] = ufbx_zero_vec3;
normal_data++;
ufbx_compute_normals(mesh, &mesh->skinned_position, normal_indices, num_indices, normal_data, num_normals);
mesh->generated_normals = true;
mesh->skinned_normal.exists = true;
mesh->skinned_normal.values.data = normal_data;
mesh->skinned_normal.values.count = num_normals;
mesh->skinned_normal.indices.data = normal_indices;
mesh->skinned_normal.indices.count = num_indices;
mesh->skinned_normal.value_reals = 3;
}
}
return 1;
#else
ufbxi_fmt_err_info(error, "UFBX_ENABLE_SKINNING_EVALUATION");
ufbxi_report_err_msg(error, "UFBXI_FEATURE_SKINNING_EVALUATION", "Feature disabled");
return 0;
#endif
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_fixup_opts_string(ufbxi_context *uc, ufbx_string *str, bool push)
{
if (str->length > 0) {
if (str->length == SIZE_MAX) {
str->length = str->data ? strlen(str->data) : 0;
}
if (push) {
ufbxi_check(ufbxi_push_string_place_str(&uc->string_pool, str, false));
}
} else {
str->data = ufbxi_empty_char;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_resolve_warning_elements(ufbxi_context *uc)
{
size_t num_elements = uc->tmp_element_id.num_items;
uint32_t *element_ids = ufbxi_push_pop(&uc->tmp, &uc->tmp_element_id, uint32_t, num_elements);
ufbxi_check(element_ids);
ufbxi_for_list(ufbx_warning, warning, uc->scene.metadata.warnings) {
uint32_t element_id = warning->element_id;
// Decode `element_id`, see HACK(warning-element) in `ufbxi_vwarnf_imp()` for the encoding.
if ((element_id & 0x80000000u) != 0 && element_id != ~0u) {
warning->element_id = element_ids[element_id & ~0x80000000u];
}
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc)
{
// Check for deferred failure
if (uc->deferred_failure) return 0;
ufbxi_check(uc->opts.path_separator >= 0x20 && uc->opts.path_separator <= 0x7e);
ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.filename, false));
ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.obj_mtl_path, true));
ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.geometry_transform_helper_name, true));
ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.scale_helper_name, true));
ufbxi_check(ufbxi_thread_pool_init(&uc->thread_pool, &uc->error, &uc->ator_tmp, &uc->opts.thread_opts));
if (!uc->opts.allow_unsafe) {
ufbxi_check_msg(uc->opts.index_error_handling != UFBX_INDEX_ERROR_HANDLING_UNSAFE_IGNORE, "Unsafe options");
ufbxi_check_msg(uc->opts.unicode_error_handling != UFBX_UNICODE_ERROR_HANDLING_UNSAFE_IGNORE, "Unsafe options");
} else {
uc->scene.metadata.is_unsafe = true;
}
if (uc->opts.index_error_handling == UFBX_INDEX_ERROR_HANDLING_NO_INDEX) {
uc->scene.metadata.may_contain_no_index = true;
}
uc->retain_mesh_parts = !uc->opts.ignore_geometry && !uc->opts.skip_mesh_parts;
uc->scene.metadata.may_contain_missing_vertex_position = uc->opts.allow_missing_vertex_position;
uc->scene.metadata.may_contain_broken_elements = uc->opts.connect_broken_elements;
uc->scene.metadata.creator.data = ufbxi_empty_char;
uc->unit_scale = 1.0f;
if (uc->data == NULL) {
ufbxi_dev_assert(uc->data_begin == NULL);
uc->data_begin = uc->data = ufbxi_zero_size_buffer;
}
uc->retain_vertex_w = (uc->opts.retain_dom || uc->opts.retain_vertex_attrib_w) && !uc->opts.ignore_geometry;
ufbxi_check(ufbxi_load_strings(uc));
ufbxi_check(ufbxi_load_maps(uc));
ufbxi_check(ufbxi_determine_format(uc));
ufbx_file_format format = uc->scene.metadata.file_format;
if (format == UFBX_FILE_FORMAT_FBX) {
ufbxi_check(ufbxi_begin_parse(uc));
if (uc->version < 6000) {
ufbxi_check(ufbxi_read_legacy_root(uc));
} else {
ufbxi_check(ufbxi_read_root(uc));
}
ufbxi_update_scene_metadata(&uc->scene.metadata);
ufbxi_check(ufbxi_init_file_paths(uc));
} else if (format == UFBX_FILE_FORMAT_OBJ) {
ufbxi_check(ufbxi_obj_load(uc));
ufbxi_update_scene_metadata(&uc->scene.metadata);
} else if (format == UFBX_FILE_FORMAT_MTL) {
ufbxi_check(ufbxi_mtl_load(uc));
ufbxi_update_scene_metadata(&uc->scene.metadata);
}
// Fake DOM root if necessary
if (uc->opts.retain_dom && !uc->scene.dom_root) {
ufbx_dom_node *dom_root = ufbxi_push_zero(&uc->result, ufbx_dom_node, 1);
ufbxi_check(dom_root);
dom_root->name.data = ufbxi_empty_char;
uc->scene.dom_root = dom_root;
}
ufbxi_check(ufbxi_pre_finalize_scene(uc));
// We can free `tmp_parse` already here as all parsing is done by now.
ufbxi_buf_free(&uc->tmp_parse);
ufbxi_check(ufbxi_finalize_scene(uc));
ufbxi_update_scene_settings(&uc->scene.settings);
if (uc->scene.metadata.file_format == UFBX_FILE_FORMAT_OBJ) {
ufbxi_update_scene_settings_obj(uc);
}
// Axis conversion
if (ufbx_coordinate_axes_valid(uc->opts.target_axes)) {
ufbxi_transform_to_axes(uc, uc->opts.target_axes);
}
// Unit conversion
if (uc->opts.target_unit_meters > 0.0f) {
ufbxi_check(ufbxi_scale_units(uc, uc->opts.target_unit_meters));
}
// TODO: This could be done in evaluate as well with refactoring
ufbxi_update_adjust_transforms(uc, &uc->scene);
ufbxi_check(ufbxi_modify_geometry(uc));
ufbxi_postprocess_scene(uc);
ufbxi_update_scene(&uc->scene, true, NULL, 0);
// Force a non-NULL anim pointer
if (!uc->scene.anim) {
uc->scene.anim = ufbxi_push_zero(&uc->result, ufbx_anim, 1);
}
if (uc->opts.load_external_files) {
ufbxi_check(ufbxi_load_external_files(uc));
}
// Evaluate skinning if requested
if (uc->opts.evaluate_skinning) {
ufbx_geometry_cache_data_opts cache_opts = { 0 };
cache_opts.open_file_cb = uc->opts.open_file_cb;
ufbxi_check(ufbxi_evaluate_skinning(&uc->scene, &uc->error, &uc->result, &uc->tmp,
0.0, uc->opts.load_external_files && uc->opts.evaluate_caches, &cache_opts));
}
// Pop warnings to metadata
ufbxi_check(ufbxi_pop_warnings(&uc->warnings, &uc->scene.metadata.warnings, uc->scene.metadata.has_warning));
ufbxi_check(ufbxi_resolve_warning_elements(uc));
// Copy local data to the scene
uc->scene.metadata.version = uc->version;
uc->scene.metadata.ascii = uc->from_ascii;
uc->scene.metadata.big_endian = uc->file_big_endian;
uc->scene.metadata.geometry_ignored = uc->opts.ignore_geometry;
uc->scene.metadata.animation_ignored = uc->opts.ignore_animation;
uc->scene.metadata.embedded_ignored = uc->opts.ignore_embedded;
// Retain the scene, this must be the final allocation as we copy
// `ator_result` to `ufbx_scene_imp`.
ufbxi_scene_imp *imp = ufbxi_push(&uc->result, ufbxi_scene_imp, 1);
ufbxi_check(imp);
ufbxi_init_ref(&imp->refcount, UFBXI_SCENE_IMP_MAGIC, NULL);
imp->magic = UFBXI_SCENE_IMP_MAGIC;
imp->scene = uc->scene;
imp->refcount.ator = uc->ator_result;
imp->refcount.ator.error = NULL;
// Copy retained buffers and translate the allocator struct to the one
// contained within `ufbxi_scene_imp`
imp->refcount.buf = uc->result;
imp->refcount.buf.ator = &imp->refcount.ator;
imp->string_buf = uc->string_pool.buf;
imp->string_buf.ator = &imp->refcount.ator;
imp->scene.metadata.result_memory_used = imp->refcount.ator.current_size;
imp->scene.metadata.temp_memory_used = uc->ator_tmp.current_size;
imp->scene.metadata.result_allocs = imp->refcount.ator.num_allocs;
imp->scene.metadata.temp_allocs = uc->ator_tmp.num_allocs;
ufbxi_for_ptr_list(ufbx_element, p_elem, imp->scene.elements) {
(*p_elem)->scene = &imp->scene;
}
uc->scene_imp = imp;
return 1;
}
static ufbxi_noinline void ufbxi_free_temp(ufbxi_context *uc)
{
ufbxi_thread_pool_free(&uc->thread_pool);
ufbxi_string_pool_temp_free(&uc->string_pool);
ufbxi_buf_free(&uc->warnings.tmp_stack);
ufbxi_map_free(&uc->prop_type_map);
ufbxi_map_free(&uc->fbx_id_map);
ufbxi_map_free(&uc->texture_file_map);
ufbxi_map_free(&uc->anim_stack_map);
ufbxi_map_free(&uc->fbx_attr_map);
ufbxi_map_free(&uc->node_prop_set);
ufbxi_map_free(&uc->dom_node_map);
ufbxi_buf_free(&uc->tmp);
ufbxi_buf_free(&uc->tmp_parse);
for (size_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) {
ufbxi_buf_free(&uc->tmp_thread_parse[i]);
}
ufbxi_buf_free(&uc->tmp_stack);
ufbxi_buf_free(&uc->tmp_connections);
ufbxi_buf_free(&uc->tmp_node_ids);
ufbxi_buf_free(&uc->tmp_elements);
ufbxi_buf_free(&uc->tmp_element_offsets);
ufbxi_buf_free(&uc->tmp_element_fbx_ids);
ufbxi_buf_free(&uc->tmp_element_ptrs);
for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) {
ufbxi_buf_free(&uc->tmp_typed_element_offsets[i]);
}
ufbxi_buf_free(&uc->tmp_mesh_textures);
ufbxi_buf_free(&uc->tmp_full_weights);
ufbxi_buf_free(&uc->tmp_dom_nodes);
ufbxi_buf_free(&uc->tmp_element_id);
ufbxi_buf_free(&uc->tmp_ascii_spans);
ufbxi_free(&uc->ator_tmp, ufbxi_node, uc->top_nodes, uc->top_nodes_cap);
ufbxi_free(&uc->ator_tmp, void*, uc->element_extra_arr, uc->element_extra_cap);
ufbxi_free(&uc->ator_tmp, char, uc->ascii.token.str_data, uc->ascii.token.str_cap);
ufbxi_free(&uc->ator_tmp, char, uc->ascii.prev_token.str_data, uc->ascii.prev_token.str_cap);
ufbxi_free(&uc->ator_tmp, char, uc->read_buffer, uc->read_buffer_size);
ufbxi_free(&uc->ator_tmp, char, uc->tmp_arr, uc->tmp_arr_size);
ufbxi_free(&uc->ator_tmp, char, uc->swap_arr, uc->swap_arr_size);
ufbxi_obj_free(uc);
ufbxi_free_ator(&uc->ator_tmp);
}
static ufbxi_noinline void ufbxi_free_result(ufbxi_context *uc)
{
ufbxi_buf_free(&uc->result);
ufbxi_buf_free(&uc->string_pool.buf);
ufbxi_free_ator(&uc->ator_result);
}
static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_opts *user_opts, ufbx_error *p_error)
{
// Test endianness
{
uint8_t buf[2];
uint16_t val = 0xbbaa;
memcpy(buf, &val, 2);
uc->local_big_endian = buf[0] == 0xbb;
}
uc->double_parse_flags = ufbxi_parse_double_init_flags();
if (user_opts) {
uc->opts = *user_opts;
} else {
memset(&uc->opts, 0, sizeof(uc->opts));
}
if (uc->opts.file_size_estimate) {
uc->progress_bytes_total = uc->opts.file_size_estimate;
}
if (uc->opts.ignore_all_content) {
uc->opts.ignore_geometry = true;
uc->opts.ignore_animation = true;
uc->opts.ignore_embedded = true;
}
ufbx_inflate_retain inflate_retain;
inflate_retain.initialized = false;
ufbxi_init_ator(&uc->error, &uc->ator_tmp, &uc->opts.temp_allocator, "temp");
ufbxi_init_ator(&uc->error, &uc->ator_result, &uc->opts.result_allocator, "result");
if (uc->opts.read_buffer_size == 0) {
uc->opts.read_buffer_size = 0x4000;
}
if (uc->opts.file_format_lookahead == 0) {
uc->opts.file_format_lookahead = 0x4000;
} else if (uc->opts.file_format_lookahead < UFBXI_MIN_FILE_FORMAT_LOOKAHEAD) {
uc->opts.file_format_lookahead = UFBXI_MIN_FILE_FORMAT_LOOKAHEAD;
}
if (!uc->opts.path_separator) {
uc->opts.path_separator = UFBX_PATH_SEPARATOR;
}
if (!uc->opts.progress_cb.fn || uc->opts.progress_interval_hint >= SIZE_MAX) {
uc->progress_interval = SIZE_MAX;
} else if (uc->opts.progress_interval_hint > 0) {
uc->progress_interval = (size_t)uc->opts.progress_interval_hint;
} else {
uc->progress_interval = 0x4000;
}
if (!uc->opts.open_file_cb.fn) {
uc->opts.open_file_cb.fn = &ufbx_default_open_file;
}
if (!uc->opts.thread_opts.memory_limit) {
uc->opts.thread_opts.memory_limit = 32*1024*1024;
}
uc->string_pool.error = &uc->error;
ufbxi_map_init(&uc->string_pool.map, &uc->ator_tmp, &ufbxi_map_cmp_string, NULL);
uc->string_pool.buf.ator = &uc->ator_result;
uc->string_pool.buf.unordered = true;
uc->string_pool.initial_size = 1024;
uc->string_pool.error_handling = uc->opts.unicode_error_handling;
ufbxi_map_init(&uc->prop_type_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL);
ufbxi_map_init(&uc->fbx_id_map, &uc->ator_tmp, &ufbxi_map_cmp_uint64, NULL);
ufbxi_map_init(&uc->texture_file_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL);
ufbxi_map_init(&uc->anim_stack_map, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL);
ufbxi_map_init(&uc->fbx_attr_map, &uc->ator_tmp, &ufbxi_map_cmp_uint64, NULL);
ufbxi_map_init(&uc->node_prop_set, &uc->ator_tmp, &ufbxi_map_cmp_const_char_ptr, NULL);
ufbxi_map_init(&uc->dom_node_map, &uc->ator_tmp, &ufbxi_map_cmp_uintptr, NULL);
uc->tmp.ator = &uc->ator_tmp;
uc->tmp_parse.ator = &uc->ator_tmp;
uc->tmp_stack.ator = &uc->ator_tmp;
uc->tmp_connections.ator = &uc->ator_tmp;
uc->tmp_node_ids.ator = &uc->ator_tmp;
uc->tmp_elements.ator = &uc->ator_tmp;
uc->tmp_element_offsets.ator = &uc->ator_tmp;
uc->tmp_element_fbx_ids.ator = &uc->ator_tmp;
uc->tmp_element_ptrs.ator = &uc->ator_tmp;
for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) {
uc->tmp_typed_element_offsets[i].ator = &uc->ator_tmp;
}
uc->tmp_mesh_textures.ator = &uc->ator_tmp;
uc->tmp_full_weights.ator = &uc->ator_tmp;
uc->tmp_dom_nodes.ator = &uc->ator_tmp;
uc->tmp_element_id.ator = &uc->ator_tmp;
uc->tmp_ascii_spans.ator = &uc->ator_tmp;
for (size_t i = 0; i < UFBX_THREAD_GROUP_COUNT; i++) {
uc->tmp_thread_parse[i].ator = &uc->ator_tmp;
uc->tmp_thread_parse[i].unordered = true;
uc->tmp_thread_parse[i].clearable = true;
}
uc->result.ator = &uc->ator_result;
uc->tmp.unordered = true;
uc->tmp_parse.unordered = true;
uc->tmp_parse.clearable = true;
uc->result.unordered = true;
uc->warnings.error = &uc->error;
uc->warnings.result = &uc->result;
uc->warnings.tmp_stack.ator = &uc->ator_tmp;
uc->string_pool.warnings = &uc->warnings;
// Set zero size `swap_arr` to a non-NULL buffer so we can tell the difference between empty
// array and an allocation failure.
uc->swap_arr = (char*)ufbxi_zero_size_buffer;
// NOTE: Though `inflate_retain` leaks out of the scope we don't use it outside this function.
// cppcheck-suppress autoVariables
uc->inflate_retain = &inflate_retain;
int ok = ufbxi_load_imp(uc);
ufbxi_free_temp(uc);
if (uc->close_fn) {
uc->close_fn(uc->read_user);
}
if (ok) {
if (p_error) {
ufbxi_clear_error(p_error);
}
return &uc->scene_imp->scene;
} else {
ufbxi_fix_error_type(&uc->error, "Failed to load");
if (p_error) *p_error = uc->error;
ufbxi_free_result(uc);
return NULL;
}
}
static ufbxi_noinline ufbx_scene *ufbxi_load_not_found(const char *filename, size_t filename_len, ufbx_error *p_error)
{
ufbxi_context uc = { UFBX_ERROR_NONE };
ufbxi_set_err_info(&uc.error, filename, filename_len);
ufbxi_report_err_msg(&uc.error, "File not found", "File not found");
uc.deferred_failure = true;
return ufbxi_load(&uc, NULL, p_error);
}
// -- Animation evaluation
static ufbxi_forceinline bool ufbxi_override_less_than_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop)
{
if (over->element_id != element_id) return over->element_id < element_id;
if (over->_internal_key != prop->_internal_key) return over->_internal_key < prop->_internal_key;
return strcmp(over->prop_name.data, prop->name.data);
}
static ufbxi_forceinline bool ufbxi_override_equals_to_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop)
{
if (over->element_id != element_id) return false;
if (over->_internal_key != prop->_internal_key) return false;
return !strcmp(over->prop_name.data, prop->name.data);
}
static ufbxi_noinline bool ufbxi_find_prop_override(const ufbx_prop_override_list *overrides, uint32_t element_id, ufbx_prop *prop)
{
size_t ix = SIZE_MAX;
ufbxi_macro_lower_bound_eq(ufbx_prop_override, 16, &ix, overrides->data, 0, overrides->count,
( ufbxi_override_less_than_prop(a, element_id, prop) ),
( ufbxi_override_equals_to_prop(a, element_id, prop) ));
if (ix != SIZE_MAX) {
const ufbx_prop_override *over = &overrides->data[ix];
const uint32_t clear_flags = UFBX_PROP_FLAG_NO_VALUE | UFBX_PROP_FLAG_NOT_FOUND;
prop->flags = (ufbx_prop_flags)(((uint32_t)prop->flags & ~clear_flags) | UFBX_PROP_FLAG_OVERRIDDEN);
prop->value_vec4 = over->value;
prop->value_real_arr[3] = 0.0f;
prop->value_int = over->value_int;
prop->value_str = over->value_str;
prop->value_blob.data = prop->value_str.data;
prop->value_blob.size = prop->value_str.length;
return true;
} else {
return false;
}
}
static ufbxi_noinline ufbx_prop_override_list ufbxi_find_element_prop_overrides(const ufbx_prop_override_list *overrides, uint32_t element_id)
{
size_t begin = overrides->count, end = begin;
ufbxi_macro_lower_bound_eq(ufbx_prop_override, 32, &begin, overrides->data, 0, overrides->count,
(a->element_id < element_id),
(a->element_id == element_id));
ufbxi_macro_upper_bound_eq(ufbx_prop_override, 32, &end, overrides->data, begin, overrides->count,
(a->element_id == element_id));
ufbx_prop_override_list result = { overrides->data + begin, end - begin };
return result;
}
typedef struct ufbxi_anim_layer_combine_ctx {
const ufbx_anim *anim;
const ufbx_element *element;
double time;
ufbx_rotation_order rotation_order;
bool has_rotation_order;
} ufbxi_anim_layer_combine_ctx;
static ufbxi_noinline double ufbxi_pow_abs(double v, double e)
{
if (e <= 0.0) return 1.0;
if (e >= 1.0) return v;
double sign = v < 0.0 ? -1.0 : 1.0;
return sign * ufbx_pow(v * sign, e);
}
// Recursion is limited by the fact that we recurse only when the property name is "Lcl Rotation"
// and when recursing we always evaluate the property "RotationOrder"
static ufbxi_noinline void ufbxi_combine_anim_layer(ufbxi_anim_layer_combine_ctx *ctx, ufbx_anim_layer *layer, ufbx_real weight, const char *prop_name, ufbx_vec3 *result, const ufbx_vec3 *value)
ufbxi_recursive_function_void(ufbxi_combine_anim_layer, (ctx, layer, weight, prop_name, result, value), 2,
(ufbxi_anim_layer_combine_ctx *ctx, ufbx_anim_layer *layer, ufbx_real weight, const char *prop_name, ufbx_vec3 *result, const ufbx_vec3 *value))
{
if (layer->compose_rotation && layer->blended && prop_name == ufbxi_Lcl_Rotation && !ctx->has_rotation_order) {
ufbx_prop rp = ufbx_evaluate_prop_len(ctx->anim, ctx->element, ufbxi_RotationOrder, sizeof(ufbxi_RotationOrder) - 1, ctx->time);
// NOTE: Defaults to 0 (UFBX_ROTATION_XYZ) gracefully if property is not found
if (rp.value_int >= 0 && rp.value_int <= UFBX_ROTATION_ORDER_SPHERIC) {
ctx->rotation_order = (ufbx_rotation_order)rp.value_int;
} else {
ctx->rotation_order = UFBX_ROTATION_ORDER_XYZ;
}
ctx->has_rotation_order = true;
}
if (layer->additive) {
if (layer->compose_scale && prop_name == ufbxi_Lcl_Scaling) {
result->x *= (ufbx_real)ufbxi_pow_abs(value->x, weight);
result->y *= (ufbx_real)ufbxi_pow_abs(value->y, weight);
result->z *= (ufbx_real)ufbxi_pow_abs(value->z, weight);
} else if (layer->compose_rotation && prop_name == ufbxi_Lcl_Rotation) {
ufbx_quat a = ufbx_euler_to_quat(*result, ctx->rotation_order);
ufbx_quat b = ufbx_euler_to_quat(*value, ctx->rotation_order);
b = ufbx_quat_slerp(ufbx_identity_quat, b, weight);
ufbx_quat res = ufbxi_mul_quat(a, b);
*result = ufbx_quat_to_euler(res, ctx->rotation_order);
} else {
result->x += value->x * weight;
result->y += value->y * weight;
result->z += value->z * weight;
}
} else if (layer->blended) {
ufbx_real res_weight = 1.0f - weight;
if (layer->compose_scale && prop_name == ufbxi_Lcl_Scaling) {
result->x = (ufbx_real)(ufbxi_pow_abs(result->x, res_weight) * ufbxi_pow_abs(value->x, weight));
result->y = (ufbx_real)(ufbxi_pow_abs(result->y, res_weight) * ufbxi_pow_abs(value->y, weight));
result->z = (ufbx_real)(ufbxi_pow_abs(result->z, res_weight) * ufbxi_pow_abs(value->z, weight));
} else if (layer->compose_rotation && prop_name == ufbxi_Lcl_Rotation) {
ufbx_quat a = ufbx_euler_to_quat(*result, ctx->rotation_order);
ufbx_quat b = ufbx_euler_to_quat(*value, ctx->rotation_order);
ufbx_quat res = ufbx_quat_slerp(a, b, weight);
*result = ufbx_quat_to_euler(res, ctx->rotation_order);
} else {
result->x = result->x * res_weight + value->x * weight;
result->y = result->y * res_weight + value->y * weight;
result->z = result->z * res_weight + value->z * weight;
}
} else {
*result = *value;
}
}
static ufbxi_forceinline bool ufbxi_anim_layer_might_contain_id(const ufbx_anim_layer *layer, uint32_t id)
{
uint32_t id_mask = ufbxi_arraycount(layer->_element_id_bitmask) - 1;
bool ok = id - layer->_min_element_id <= (layer->_max_element_id - layer->_min_element_id);
ok &= (layer->_element_id_bitmask[(id >> 5) & id_mask] & (1u << (id & 31))) != 0;
return ok;
}
static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, size_t num_props)
{
ufbxi_anim_layer_combine_ctx combine_ctx = { anim, element, time };
uint32_t element_id = element->element_id;
size_t num_layers = anim->layers.count;
for (size_t layer_ix = 0; layer_ix < num_layers; layer_ix++) {
ufbx_anim_layer *layer = anim->layers.data[layer_ix];
if (!ufbxi_anim_layer_might_contain_id(layer, element_id)) continue;
// Find the weight for the current layer
// TODO: Should this be searched from multiple layers?
ufbx_real weight = layer_ix < anim->override_layer_weights.count ? anim->override_layer_weights.data[layer_ix] : layer->weight;
if (layer->weight_is_animated && layer->blended) {
ufbx_anim_prop *weight_aprop = ufbxi_find_anim_prop_start(layer, &layer->element);
if (weight_aprop) {
weight = ufbx_evaluate_anim_value_real(weight_aprop->anim_value, time) / (ufbx_real)100.0;
if (weight < 0.0f) weight = 0.0f;
if (weight > 0.99999f) weight = 1.0f;
}
}
ufbx_anim_prop *aprop = ufbxi_find_anim_prop_start(layer, element);
if (!aprop) continue;
for (size_t i = 0; i < num_props; i++) {
ufbx_prop *prop = &props[i];
// Don't evaluate on top of overridden properties
if ((prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) != 0) continue;
// Connections override animation by default
if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) continue;
// Skip until we reach `aprop >= prop`
// NOTE: No need to check for end as `anim_props` is terminated with a NULL sentinel.
while (aprop->element == element && aprop->_internal_key < prop->_internal_key) aprop++;
if (aprop->prop_name.data != prop->name.data) {
while (aprop->element == element && strcmp(aprop->prop_name.data, prop->name.data) < 0) aprop++;
}
// TODO: Should we skip the blending for the first layer _per property_
// This could be done by having `UFBX_PROP_FLAG_ANIMATION_EVALUATED`
// that gets set for the first layer of animation that is applied.
if (aprop->prop_name.data == prop->name.data) {
ufbx_vec3 v = ufbx_evaluate_anim_value_vec3(aprop->anim_value, time);
if (layer_ix == 0) {
prop->value_vec3 = v;
} else {
ufbxi_combine_anim_layer(&combine_ctx, layer, weight, prop->name.data, &prop->value_vec3, &v);
}
}
}
}
ufbxi_for(ufbx_prop, prop, props, num_props) {
if (prop->flags & UFBX_PROP_FLAG_OVERRIDDEN) continue;
prop->value_int = ufbxi_f64_to_i64(prop->value_real);
}
}
// Recursion limited by not calling `ufbx_evaluate_prop_len()` with a connected property,
// meaning it will never call `ufbxi_evaluate_connected_prop()` again indirectly.
static ufbxi_noinline void ufbxi_evaluate_connected_prop(ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time)
ufbxi_recursive_function_void(ufbxi_evaluate_connected_prop, (prop, anim, element, name, time), 3,
(ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time))
{
ufbx_connection *conn = ufbxi_find_prop_connection(element, name);
for (size_t i = 0; i < 1000 && conn; i++) {
ufbx_connection *next_conn = ufbxi_find_prop_connection(conn->src, conn->src_prop.data);
if (!next_conn) break;
conn = next_conn;
}
// Found a non-cyclic connection
if (conn && !ufbxi_find_prop_connection(conn->src, conn->src_prop.data)) {
ufbx_prop ep = ufbx_evaluate_prop_len(anim, conn->src, conn->src_prop.data, conn->src_prop.length, time);
prop->value_vec4 = ep.value_vec4;
prop->value_int = ep.value_int;
prop->value_str = ep.value_str;
prop->value_blob = ep.value_blob;
} else {
// Connection not found, maybe it's animated?
prop->flags = (ufbx_prop_flags)((uint32_t)prop->flags & ~(uint32_t)UFBX_PROP_FLAG_CONNECTED);
}
}
typedef struct {
const ufbx_prop *prop, *prop_end;
const ufbx_prop_override *over, *over_end;
ufbx_prop tmp;
} ufbxi_prop_iter;
static ufbxi_noinline void ufbxi_init_prop_iter_slow(ufbxi_prop_iter *iter, const ufbx_anim *anim, const ufbx_element *element)
{
iter->prop = element->props.props.data;
iter->prop_end = element->props.props.data + element->props.props.count;
ufbx_prop_override_list over = ufbxi_find_element_prop_overrides(&anim->prop_overrides, element->element_id);
iter->over = over.data;
iter->over_end = over.data + over.count;
if (over.count > 0) {
memset(&iter->tmp, 0, sizeof(ufbx_prop));
}
}
static ufbxi_forceinline void ufbxi_init_prop_iter(ufbxi_prop_iter *iter, const ufbx_anim *anim, const ufbx_element *element)
{
iter->prop = element->props.props.data;
iter->prop_end = ufbxi_add_ptr(element->props.props.data, element->props.props.count);
iter->over = iter->over_end = NULL;
if (anim->prop_overrides.count > 0) {
ufbxi_init_prop_iter_slow(iter, anim, element);
}
}
static ufbxi_noinline const ufbx_prop *ufbxi_next_prop_slow(ufbxi_prop_iter *iter)
{
const ufbx_prop *prop = iter->prop;
const ufbx_prop_override *over = iter->over;
if (prop == iter->prop_end && over == iter->over_end) return NULL;
// We can use `UINT32_MAX` as a termianting key (aka prefix) as prop names must
// be valid UTF-8 and the byte sequence "\xff\xff\xff\xff" is not valid.
uint32_t prop_key = prop != iter->prop_end ? prop->_internal_key : UINT32_MAX;
uint32_t over_key = over != iter->over_end ? over->_internal_key : UINT32_MAX;
int cmp = 0;
if (prop_key != over_key) {
cmp = prop_key < over_key ? -1 : 1;
} else {
cmp = strcmp(prop->name.data, over->prop_name.data);
}
if (cmp >= 0) {
ufbx_prop *dst = &iter->tmp;
dst->name = over->prop_name;
dst->_internal_key = over->_internal_key;
dst->type = UFBX_PROP_UNKNOWN;
dst->flags = UFBX_PROP_FLAG_OVERRIDDEN;
dst->value_str = over->value_str;
dst->value_blob.data = dst->value_str.data;
dst->value_blob.size = dst->value_str.length;
dst->value_int = over->value_int;
dst->value_vec4 = over->value;
iter->over = over + 1;
if (cmp == 0) {
iter->prop = prop + 1;
}
return dst;
} else {
iter->prop = prop + 1;
return prop;
}
}
static ufbxi_forceinline const ufbx_prop *ufbxi_next_prop(ufbxi_prop_iter *iter)
{
if (iter->over == iter->over_end) {
if (iter->prop == iter->prop_end) return NULL;
return iter->prop++;
} else {
return ufbxi_next_prop_slow(iter);
}
}
static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, const char *const *prop_names, size_t max_props)
{
const char *name = prop_names[0];
uint32_t key = ufbxi_get_name_key_c(name);
size_t num_props = 0;
#if defined(UFBX_REGRESSION)
for (size_t i = 1; i < max_props; i++) {
ufbx_assert(strcmp(prop_names[i - 1], prop_names[i]) < 0);
}
#endif
size_t name_ix = 0;
ufbxi_prop_iter iter; // ufbxi_uninit
ufbxi_init_prop_iter(&iter, anim, element);
const ufbx_prop *prop = NULL;
while ((prop = ufbxi_next_prop(&iter)) != NULL) {
while (name_ix < max_props) {
if (key > prop->_internal_key) break;
if (name == prop->name.data) {
if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) {
ufbx_prop *dst = &props[num_props++];
*dst = *prop;
ufbxi_evaluate_connected_prop(dst, anim, element, name, time);
} else if ((prop->flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_OVERRIDDEN)) != 0) {
props[num_props++] = *prop;
}
break;
} else if (strcmp(name, prop->name.data) < 0) {
name_ix++;
if (name_ix < max_props) {
name = prop_names[name_ix];
key = ufbxi_get_name_key_c(name);
}
} else {
break;
}
}
}
ufbxi_evaluate_props(anim, element, time, props, num_props);
ufbx_props prop_list;
prop_list.props.data = props;
prop_list.props.count = prop_list.num_animated = num_props;
prop_list.defaults = (ufbx_props*)&element->props;
return prop_list;
}
#if UFBXI_FEATURE_SCENE_EVALUATION
typedef struct {
char *src_element;
char *dst_element;
ufbxi_scene_imp *src_imp;
ufbx_scene src_scene;
ufbx_evaluate_opts opts;
ufbx_anim *anim;
double time;
ufbx_error error;
// Allocators
ufbxi_allocator ator_result;
ufbxi_allocator ator_tmp;
ufbxi_buf result;
ufbxi_buf tmp;
ufbx_scene scene;
ufbxi_scene_imp *scene_imp;
} ufbxi_eval_context;
static ufbxi_forceinline ufbx_element *ufbxi_translate_element(ufbxi_eval_context *ec, void *elem)
{
return elem ? (ufbx_element*)(ec->dst_element + ((char*)elem - ec->src_element)) : NULL;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_element_list(ufbxi_eval_context *ec, void *p_list)
{
ufbx_element_list *list = (ufbx_element_list*)p_list;
size_t count = list->count;
ufbx_element **src = list->data;
ufbx_element **dst = ufbxi_push(&ec->result, ufbx_element*, count);
ufbxi_check_err(&ec->error, dst);
list->data = dst;
for (size_t i = 0; i < count; i++) {
dst[i] = ufbxi_translate_element(ec, src[i]);
}
return 1;
}
static ufbxi_noinline void ufbxi_translate_maps(ufbxi_eval_context *ec, ufbx_material_map *maps, size_t count)
{
ufbxi_nounroll ufbxi_for(ufbx_material_map, map, maps, count) {
map->texture = (ufbx_texture*)ufbxi_translate_element(ec, map->texture);
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_anim(ufbxi_eval_context *ec, ufbx_anim **p_anim)
{
ufbx_anim *anim = ufbxi_push_copy(&ec->result, ufbx_anim, 1, *p_anim);
ufbxi_check_err(&ec->error, anim);
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &anim->layers));
*p_anim = anim;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context *ec)
{
ec->scene = ec->src_scene;
size_t num_elements = ec->scene.elements.count;
char *element_data = (char*)ufbxi_push(&ec->result, uint64_t, ec->scene.metadata.element_buffer_size/8);
ufbxi_check_err(&ec->error, element_data);
ec->scene.elements.data = ufbxi_push(&ec->result, ufbx_element*, num_elements);
ufbxi_check_err(&ec->error, ec->scene.elements.data);
ec->src_element = (char*)ec->src_scene.elements.data[0];
ec->dst_element = element_data;
for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) {
ec->scene.elements_by_type[i].data = ufbxi_push(&ec->result, ufbx_element*, ec->scene.elements_by_type[i].count);
ufbxi_check_err(&ec->error, ec->scene.elements_by_type[i].data);
}
size_t num_connections = ec->scene.connections_dst.count;
ec->scene.connections_src.data = ufbxi_push(&ec->result, ufbx_connection, num_connections);
ec->scene.connections_dst.data = ufbxi_push(&ec->result, ufbx_connection, num_connections);
ufbxi_check_err(&ec->error, ec->scene.connections_src.data);
ufbxi_check_err(&ec->error, ec->scene.connections_dst.data);
for (size_t i = 0; i < num_connections; i++) {
ufbx_connection *src = &ec->scene.connections_src.data[i];
ufbx_connection *dst = &ec->scene.connections_dst.data[i];
*src = ec->src_scene.connections_src.data[i];
*dst = ec->src_scene.connections_dst.data[i];
src->src = ufbxi_translate_element(ec, src->src);
src->dst = ufbxi_translate_element(ec, src->dst);
dst->src = ufbxi_translate_element(ec, dst->src);
dst->dst = ufbxi_translate_element(ec, dst->dst);
}
ec->scene.elements_by_name.data = ufbxi_push(&ec->result, ufbx_name_element, num_elements);
ufbxi_check_err(&ec->error, ec->scene.elements_by_name.data);
ec->scene.root_node = (ufbx_node*)ufbxi_translate_element(ec, ec->scene.root_node);
ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &ec->scene.anim));
for (size_t i = 0; i < num_elements; i++) {
ufbx_element *src = ec->src_scene.elements.data[i];
ufbx_element *dst = ufbxi_translate_element(ec, src);
size_t size = ufbx_element_type_size[src->type];
ufbx_assert(size > 0);
memcpy(dst, src, size);
ec->scene.elements.data[i] = dst;
ec->scene.elements_by_type[src->type].data[src->typed_id] = dst;
dst->connections_src.data = ec->scene.connections_src.data + (dst->connections_src.data - ec->src_scene.connections_src.data);
dst->connections_dst.data = ec->scene.connections_dst.data + (dst->connections_dst.data - ec->src_scene.connections_dst.data);
if (dst->instances.count > 0) {
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &dst->instances));
}
ufbx_name_element named = ec->src_scene.elements_by_name.data[i];
named.element = ufbxi_translate_element(ec, named.element);
ec->scene.elements_by_name.data[i] = named;
}
ufbxi_for_ptr_list(ufbx_node, p_node, ec->scene.nodes) {
ufbx_node *node = *p_node;
node->parent = (ufbx_node*)ufbxi_translate_element(ec, node->parent);
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->children));
node->attrib = ufbxi_translate_element(ec, node->attrib);
node->mesh = (ufbx_mesh*)ufbxi_translate_element(ec, node->mesh);
node->light = (ufbx_light*)ufbxi_translate_element(ec, node->light);
node->camera = (ufbx_camera*)ufbxi_translate_element(ec, node->camera);
node->bone = (ufbx_bone*)ufbxi_translate_element(ec, node->bone);
node->inherit_scale_node = (ufbx_node*)ufbxi_translate_element(ec, node->inherit_scale_node);
node->scale_helper = (ufbx_node*)ufbxi_translate_element(ec, node->scale_helper);
node->bind_pose = (ufbx_pose*)ufbxi_translate_element(ec, node->bind_pose);
if (node->all_attribs.count > 1) {
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->all_attribs));
} else if (node->all_attribs.count == 1) {
node->all_attribs.data = &node->attrib;
}
node->geometry_transform_helper = (ufbx_node*)ufbxi_translate_element(ec, node->geometry_transform_helper);
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->materials));
}
ufbxi_for_ptr_list(ufbx_mesh, p_mesh, ec->scene.meshes) {
ufbx_mesh *mesh = *p_mesh;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->materials));
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->skin_deformers));
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->blend_deformers));
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->cache_deformers));
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &mesh->all_deformers));
}
ufbxi_for_ptr_list(ufbx_stereo_camera, p_stereo, ec->scene.stereo_cameras) {
ufbx_stereo_camera *stereo = *p_stereo;
stereo->left = (ufbx_camera*)ufbxi_translate_element(ec, stereo->left);
stereo->right = (ufbx_camera*)ufbxi_translate_element(ec, stereo->right);
}
ufbxi_for_ptr_list(ufbx_skin_deformer, p_skin, ec->scene.skin_deformers) {
ufbx_skin_deformer *skin = *p_skin;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &skin->clusters));
}
ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, ec->scene.skin_clusters) {
ufbx_skin_cluster *cluster = *p_cluster;
cluster->bone_node = (ufbx_node*)ufbxi_translate_element(ec, cluster->bone_node);
}
ufbxi_for_ptr_list(ufbx_blend_deformer, p_blend, ec->scene.blend_deformers) {
ufbx_blend_deformer *blend = *p_blend;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &blend->channels));
}
ufbxi_for_ptr_list(ufbx_blend_channel, p_chan, ec->scene.blend_channels) {
ufbx_blend_channel *chan = *p_chan;
ufbx_blend_keyframe *keys = ufbxi_push(&ec->result, ufbx_blend_keyframe, chan->keyframes.count);
ufbxi_check_err(&ec->error, keys);
for (size_t i = 0; i < chan->keyframes.count; i++) {
keys[i] = chan->keyframes.data[i];
keys[i].shape = (ufbx_blend_shape*)ufbxi_translate_element(ec, keys[i].shape);
}
chan->keyframes.data = keys;
chan->target_shape = (ufbx_blend_shape*)ufbxi_translate_element(ec, chan->target_shape);
}
ufbxi_for_ptr_list(ufbx_cache_deformer, p_deformer, ec->scene.cache_deformers) {
ufbx_cache_deformer *deformer = *p_deformer;
deformer->file = (ufbx_cache_file*)ufbxi_translate_element(ec, deformer->file);
}
ufbxi_for_ptr_list(ufbx_material, p_material, ec->scene.materials) {
ufbx_material *material = *p_material;
material->shader = (ufbx_shader*)ufbxi_translate_element(ec, material->shader);
ufbxi_translate_maps(ec, material->fbx.maps, UFBX_MATERIAL_FBX_MAP_COUNT);
ufbxi_translate_maps(ec, material->pbr.maps, UFBX_MATERIAL_PBR_MAP_COUNT);
ufbx_material_texture *textures = ufbxi_push(&ec->result, ufbx_material_texture, material->textures.count);
ufbxi_check_err(&ec->error, textures);
for (size_t i = 0; i < material->textures.count; i++) {
textures[i] = material->textures.data[i];
textures[i].texture = (ufbx_texture*)ufbxi_translate_element(ec, textures[i].texture);
}
material->textures.data = textures;
}
ufbxi_for_ptr_list(ufbx_texture, p_texture, ec->scene.textures) {
ufbx_texture *texture = *p_texture;
texture->video = (ufbx_video*)ufbxi_translate_element(ec, texture->video);
ufbx_texture_layer *layers = ufbxi_push(&ec->result, ufbx_texture_layer, texture->layers.count);
ufbxi_check_err(&ec->error, layers);
for (size_t i = 0; i < texture->layers.count; i++) {
layers[i] = texture->layers.data[i];
layers[i].texture = (ufbx_texture*)ufbxi_translate_element(ec, layers[i].texture);
}
texture->layers.data = layers;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &texture->file_textures));
if (texture->shader) {
ufbx_shader_texture *shader = texture->shader;
shader = ufbxi_push_copy(&ec->result, ufbx_shader_texture, 1, shader);
ufbxi_check_err(&ec->error, shader);
texture->shader = shader;
ufbx_shader_texture_input *inputs = ufbxi_push_copy(&ec->result, ufbx_shader_texture_input, shader->inputs.count, shader->inputs.data);
ufbxi_check_err(&ec->error, inputs);
shader->inputs.data = inputs;
}
}
ufbxi_for_ptr_list(ufbx_shader, p_shader, ec->scene.shaders) {
ufbx_shader *shader = *p_shader;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &shader->bindings));
}
ufbxi_for_ptr_list(ufbx_display_layer, p_layer, ec->scene.display_layers) {
ufbx_display_layer *layer = *p_layer;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->nodes));
}
ufbxi_for_ptr_list(ufbx_selection_set, p_set, ec->scene.selection_sets) {
ufbx_selection_set *set = *p_set;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &set->nodes));
}
ufbxi_for_ptr_list(ufbx_selection_node, p_node, ec->scene.selection_nodes) {
ufbx_selection_node *node = *p_node;
node->target_node = (ufbx_node*)ufbxi_translate_element(ec, node->target_node);
node->target_mesh = (ufbx_mesh*)ufbxi_translate_element(ec, node->target_mesh);
}
ufbxi_for_ptr_list(ufbx_constraint, p_constraint, ec->scene.constraints) {
ufbx_constraint *constraint = *p_constraint;
constraint->node = (ufbx_node*)ufbxi_translate_element(ec, constraint->node);
constraint->aim_up_node = (ufbx_node*)ufbxi_translate_element(ec, constraint->aim_up_node);
constraint->ik_effector = (ufbx_node*)ufbxi_translate_element(ec, constraint->ik_effector);
constraint->ik_end_node = (ufbx_node*)ufbxi_translate_element(ec, constraint->ik_end_node);
ufbx_constraint_target *targets = ufbxi_push(&ec->result, ufbx_constraint_target, constraint->targets.count);
ufbxi_check_err(&ec->error, targets);
for (size_t i = 0; i < constraint->targets.count; i++) {
targets[i] = constraint->targets.data[i];
targets[i].node = (ufbx_node*)ufbxi_translate_element(ec, targets[i].node);
}
constraint->targets.data = targets;
}
ufbxi_for_ptr_list(ufbx_audio_layer, p_layer, ec->scene.audio_layers) {
ufbx_audio_layer *layer = *p_layer;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->clips));
}
ufbxi_for_ptr_list(ufbx_anim_stack, p_stack, ec->scene.anim_stacks) {
ufbx_anim_stack *stack = *p_stack;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &stack->layers));
ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &stack->anim));
}
ufbxi_for_ptr_list(ufbx_anim_layer, p_layer, ec->scene.anim_layers) {
ufbx_anim_layer *layer = *p_layer;
ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &layer->anim_values));
ufbx_anim_prop *props = ufbxi_push(&ec->result, ufbx_anim_prop, layer->anim_props.count + 1);
ufbxi_check_err(&ec->error, props);
for (size_t i = 0; i < layer->anim_props.count; i++) {
props[i] = layer->anim_props.data[i];
props[i].element = ufbxi_translate_element(ec, props[i].element);
props[i].anim_value = (ufbx_anim_value*)ufbxi_translate_element(ec, props[i].anim_value);
}
// Maintain NULL sentinel
memset(props + layer->anim_props.count, 0, sizeof(ufbx_anim_prop));
layer->anim_props.data = props;
}
ufbxi_for_ptr_list(ufbx_pose, p_pose, ec->scene.poses) {
ufbx_pose *pose = *p_pose;
ufbx_bone_pose *bones = ufbxi_push(&ec->result, ufbx_bone_pose, pose->bone_poses.count);
ufbxi_check_err(&ec->error, bones);
for (size_t i = 0; i < pose->bone_poses.count; i++) {
bones[i] = pose->bone_poses.data[i];
bones[i].bone_node = (ufbx_node*)ufbxi_translate_element(ec, bones[i].bone_node);
}
pose->bone_poses.data = bones;
}
ufbxi_check_err(&ec->error, ufbxi_translate_anim(ec, &ec->anim));
ufbxi_for_ptr_list(ufbx_anim_value, p_value, ec->scene.anim_values) {
ufbx_anim_value *value = *p_value;
value->curves[0] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[0]);
value->curves[1] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[1]);
value->curves[2] = (ufbx_anim_curve*)ufbxi_translate_element(ec, value->curves[2]);
}
ufbx_anim anim = *ec->anim;
ufbx_prop_override *over = anim.prop_overrides.data, *over_end = ufbxi_add_ptr(over, anim.prop_overrides.count);
// Evaluate the properties
ufbxi_for_ptr_list(ufbx_element, p_elem, ec->scene.elements) {
ufbx_element *elem = *p_elem;
size_t num_animated = elem->props.num_animated;
size_t num_override = 0;
// Setup the overrides for this element if found
while (over != over_end && over->element_id == elem->element_id) {
num_override++;
over++;
}
num_animated += num_override;
if (num_animated == 0) continue;
anim.prop_overrides.data = ufbxi_sub_ptr(over, num_override);
anim.prop_overrides.count = num_override;
ufbx_prop *props = ufbxi_push(&ec->result, ufbx_prop, num_animated);
ufbxi_check_err(&ec->error, props);
elem->props = ufbx_evaluate_props(&anim, elem, ec->time, props, num_animated);
elem->props.defaults = &ec->src_scene.elements.data[elem->element_id]->props;
}
// Update all derived values
ufbxi_update_scene(&ec->scene, false, anim.transform_overrides.data, anim.transform_overrides.count);
// Evaluate skinning if requested
if (ec->opts.evaluate_skinning) {
ufbx_geometry_cache_data_opts cache_opts = { 0 };
cache_opts.open_file_cb = ec->opts.open_file_cb;
ufbxi_check_err(&ec->error, ufbxi_evaluate_skinning(&ec->scene, &ec->error, &ec->result, &ec->tmp,
ec->time, ec->opts.load_external_files && ec->opts.evaluate_caches, &cache_opts));
}
// Retain the scene, this must be the final allocation as we copy
// `ator_result` to `ufbx_scene_imp`.
ufbxi_scene_imp *imp = ufbxi_push_zero(&ec->result, ufbxi_scene_imp, 1);
ufbxi_check_err(&ec->error, imp);
ufbx_assert(ec->src_imp->magic == UFBXI_SCENE_IMP_MAGIC);
ufbxi_init_ref(&imp->refcount, UFBXI_SCENE_IMP_MAGIC, &ec->src_imp->refcount);
imp->magic = UFBXI_SCENE_IMP_MAGIC;
imp->scene = ec->scene;
imp->refcount.ator = ec->ator_result;
imp->refcount.ator.error = NULL;
// Copy retained buffers and translate the allocator struct to the one
// contained within `ufbxi_scene_imp`
imp->refcount.buf = ec->result;
imp->refcount.buf.ator = &imp->refcount.ator;
imp->scene.metadata.result_memory_used = imp->refcount.ator.current_size;
imp->scene.metadata.temp_memory_used = ec->ator_tmp.current_size;
imp->scene.metadata.result_allocs = imp->refcount.ator.num_allocs;
imp->scene.metadata.temp_allocs = ec->ator_tmp.num_allocs;
ufbxi_for_ptr_list(ufbx_element, p_elem, imp->scene.elements) {
(*p_elem)->scene = &imp->scene;
}
ec->scene_imp = imp;
ec->result.ator = &ec->ator_result;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline ufbx_scene *ufbxi_evaluate_scene(ufbxi_eval_context *ec, ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *user_opts, ufbx_error *p_error)
{
if (user_opts) {
ec->opts = *user_opts;
} else {
memset(&ec->opts, 0, sizeof(ec->opts));
}
ec->src_imp = ufbxi_get_imp(ufbxi_scene_imp, scene);
ec->src_scene = *scene;
ec->anim = anim ? (ufbx_anim*)anim : scene->anim;
ec->time = time;
ufbxi_init_ator(&ec->error, &ec->ator_tmp, &ec->opts.temp_allocator, "temp");
ufbxi_init_ator(&ec->error, &ec->ator_result, &ec->opts.result_allocator, "result");
ec->result.ator = &ec->ator_result;
ec->tmp.ator = &ec->ator_tmp;
ec->result.unordered = true;
ec->tmp.unordered = true;
if (ufbxi_evaluate_imp(ec)) {
ufbxi_buf_free(&ec->tmp);
ufbxi_free_ator(&ec->ator_tmp);
if (p_error) {
ufbxi_clear_error(p_error);
}
return &ec->scene_imp->scene;
} else {
ufbxi_fix_error_type(&ec->error, "Failed to evaluate");
if (p_error) *p_error = ec->error;
ufbxi_buf_free(&ec->tmp);
ufbxi_buf_free(&ec->result);
ufbxi_free_ator(&ec->ator_tmp);
ufbxi_free_ator(&ec->ator_result);
return NULL;
}
}
#endif
typedef struct {
ufbx_error error;
ufbxi_allocator ator_result;
ufbxi_buf result;
const ufbx_scene *scene;
ufbx_anim_opts opts;
ufbx_anim anim;
ufbxi_anim_imp *imp;
} ufbxi_create_anim_context;
ufbxi_nodiscard static ufbxi_noinline int ufbxi_check_string(ufbx_error *error, ufbx_string *dst, const ufbx_string *src)
{
size_t length = src->length != SIZE_MAX ? src->length : strlen(src->data);
const char *data = length != 0 ? src->data : ufbxi_empty_char;
if (length > 0) {
size_t valid_length = ufbxi_utf8_valid_length(data, length);
ufbxi_check_err_msg(error, valid_length == length, "Invalid UTF-8");
}
dst->data = data;
dst->length = length;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_anim_string(ufbxi_create_anim_context *ac, ufbx_string *str)
{
size_t length = str->length;
if (length > 0) {
char *copy = ufbxi_push(&ac->result, char, length + 1);
ufbxi_check_err(&ac->error, copy);
memcpy(copy, str->data, length);
copy[str->length] = '\0';
str->data = copy;
} else {
ufbx_assert(str->data == ufbxi_empty_char);
}
return 1;
}
static bool ufbxi_prop_override_prop_name_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_prop_override *a = (const ufbx_prop_override*)va, *b = (const ufbx_prop_override*)vb;
if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key;
return ufbxi_str_less(a->prop_name, b->prop_name);
}
static bool ufbxi_prop_override_less(void *user, const void *va, const void *vb)
{
(void)user;
const ufbx_prop_override *a = (const ufbx_prop_override*)va, *b = (const ufbx_prop_override*)vb;
if (a->element_id != b->element_id) return a->element_id < b->element_id;
if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key;
return strcmp(a->prop_name.data, b->prop_name.data) < 0;
}
static int ufbxi_cmp_transform_override(const void *va, const void *vb)
{
const ufbx_transform_override *a = (const ufbx_transform_override*)va, *b = (const ufbx_transform_override*)vb;
if (a->node_id != b->node_id) return a->node_id < b->node_id ? -1 : 1;
return 0;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_anim_context *ac)
{
const ufbx_scene *scene = ac->scene;
ufbx_anim *anim = &ac->anim;
ufbxi_init_ator(&ac->error, &ac->ator_result, &ac->opts.result_allocator, "result");
ac->result.unordered = true;
ac->result.ator = &ac->ator_result;
anim->ignore_connections = ac->opts.ignore_connections;
anim->custom = true;
size_t num_layers = ac->opts.layer_ids.count;
anim->layers.count = num_layers;
anim->layers.data = ufbxi_push_zero(&ac->result, ufbx_anim_layer*, num_layers);
ufbxi_check_err(&ac->error, anim->layers.data);
if (ac->opts.override_layer_weights.count > 0) {
ufbxi_check_err_msg(&ac->error, ac->opts.override_layer_weights.count == num_layers, "override_layer_weights[] count must match layer_ids[] count");
anim->override_layer_weights.data = ufbxi_push_copy(&ac->result, ufbx_real, num_layers, ac->opts.override_layer_weights.data);
ufbxi_check_err(&ac->error, anim->override_layer_weights.data);
anim->override_layer_weights.count = num_layers;
}
for (size_t i = 0; i < num_layers; i++) {
uint32_t index = ac->opts.layer_ids.data[i];
ufbxi_check_err_msg(&ac->error, index < scene->anim_layers.count, "layer_ids out of bounds");
anim->layers.data[i] = ac->scene->anim_layers.data[index];
}
ufbx_const_prop_override_desc_list prop_overrides = ac->opts.prop_overrides;
if (prop_overrides.count > 0) {
anim->prop_overrides.count = prop_overrides.count;
anim->prop_overrides.data = ufbxi_push_zero(&ac->result, ufbx_prop_override, prop_overrides.count);
ufbxi_check_err(&ac->error, anim->prop_overrides.data);
for (size_t i = 0; i < prop_overrides.count; i++) {
const ufbx_prop_override_desc *src = &prop_overrides.data[i];
ufbx_prop_override *dst = &anim->prop_overrides.data[i];
dst->element_id = src->element_id;
dst->value = src->value;
dst->value_int = src->value_int;
if (dst->value.x != 0.0f && dst->value_int == 0) {
dst->value_int = (int64_t)dst->value.x;
} else if (dst->value_int != 0 && dst->value.x == 0.0f) {
dst->value.x = (ufbx_real)dst->value_int;
}
ufbxi_check_err(&ac->error, ufbxi_check_string(&ac->error, &dst->prop_name, &src->prop_name));
ufbxi_check_err(&ac->error, ufbxi_check_string(&ac->error, &dst->value_str, &src->value_str));
dst->_internal_key = ufbxi_get_name_key(dst->prop_name.data, dst->prop_name.length);
}
// Sort `anim->prop_overrides` first by `prop_name` only so we can deduplicate and
// convert them to global strings in `ufbxi_strings[]` if possible.
ufbxi_unstable_sort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_prop_override_prop_name_less, NULL);
const ufbx_string *global_str = ufbxi_strings, *global_end = global_str + ufbxi_arraycount(ufbxi_strings);
ufbx_string prev_name = { ufbxi_empty_char };
ufbxi_for_list(ufbx_prop_override, over, anim->prop_overrides) {
if (over->value_str.length > 0) {
ufbxi_check_err(&ac->error, ufbxi_push_anim_string(ac, &over->value_str));
}
if (ufbxi_str_equal(over->prop_name, prev_name)) {
over->prop_name = prev_name;
continue;
}
while (global_str != global_end && ufbxi_str_less(*global_str, over->prop_name)) {
++global_str;
}
if (global_str != global_end && ufbxi_str_equal(*global_str, over->prop_name)) {
over->prop_name = *global_str;
} else {
ufbxi_check_err(&ac->error, ufbxi_push_anim_string(ac, &over->prop_name));
}
prev_name = over->prop_name;
}
// Sort `anim->prop_overrides` to the actual order expected by evaluation.
ufbxi_unstable_sort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_prop_override_less, NULL);
for (size_t i = 1; i < prop_overrides.count; i++) {
const ufbx_prop_override *prev = &anim->prop_overrides.data[i - 1];
const ufbx_prop_override *next = &anim->prop_overrides.data[i];
if (prev->element_id == next->element_id && prev->prop_name.data == next->prop_name.data) {
ufbxi_fmt_err_info(&ac->error, "element %u prop \"%s\"", prev->element_id, prev->prop_name.data);
ufbxi_fail_err_msg(&ac->error, "Duplicate override", "Duplicate override");
}
}
}
if (ac->opts.transform_overrides.count > 0) {
anim->transform_overrides.count = ac->opts.transform_overrides.count;
anim->transform_overrides.data = ufbxi_push_copy(&ac->result, ufbx_transform_override, anim->transform_overrides.count, ac->opts.transform_overrides.data);
ufbxi_check_err(&ac->error, anim->transform_overrides.data);
qsort(anim->transform_overrides.data, anim->transform_overrides.count, sizeof(ufbx_transform_override), &ufbxi_cmp_transform_override);
}
ac->imp = ufbxi_push(&ac->result, ufbxi_anim_imp, 1);
ufbxi_check_err(&ac->error, ac->imp);
ufbxi_init_ref(&ac->imp->refcount, UFBXI_ANIM_IMP_MAGIC, &(ufbxi_get_imp(ufbxi_scene_imp, scene))->refcount);
ac->imp->magic = UFBXI_ANIM_IMP_MAGIC;
ac->imp->anim = ac->anim;
ac->imp->refcount.ator = ac->ator_result;
ac->imp->refcount.buf = ac->result;
return 1;
}
// -- Animation baking
typedef struct {
ufbxi_refcount refcount;
ufbx_baked_anim bake;
uint32_t magic;
} ufbxi_baked_anim_imp;
#if UFBXI_FEATURE_ANIMATION_BAKING
typedef struct {
double time;
uint32_t flags;
} ufbxi_bake_time;
UFBX_LIST_TYPE(ufbxi_bake_time_list, ufbxi_bake_time);
typedef struct {
ufbx_error error;
ufbxi_allocator ator_tmp;
ufbxi_allocator ator_result;
ufbxi_buf result;
ufbxi_buf tmp;
ufbxi_buf tmp_prop;
ufbxi_buf tmp_times;
ufbxi_buf tmp_bake_props;
ufbxi_buf tmp_nodes;
ufbxi_buf tmp_elements;
ufbxi_buf tmp_props;
ufbxi_buf tmp_bake_stack;
ufbxi_bake_time_list layer_weight_times;
ufbx_baked_node **baked_nodes;
bool *nodes_to_bake;
const ufbx_scene *scene;
const ufbx_anim *anim;
ufbx_bake_opts opts;
double ktime_offset;
double time_begin;
double time_end;
double time_min;
double time_max;
ufbx_baked_anim bake;
ufbxi_baked_anim_imp *imp;
} ufbxi_bake_context;
typedef struct {
uint32_t sort_id;
uint32_t element_id;
const char *prop_name;
ufbx_anim_value *anim_value;
} ufbxi_bake_prop;
static int ufbxi_cmp_bake_prop(const void *va, const void *vb)
{
const ufbxi_bake_prop *a = (const ufbxi_bake_prop*)va;
const ufbxi_bake_prop *b = (const ufbxi_bake_prop*)vb;
if (a->sort_id != b->sort_id) return a->sort_id < b->sort_id ? -1 : 1;
if (a->element_id != b->element_id) return a->element_id < b->element_id ? -1 : 1;
if (a->prop_name != b->prop_name) return strcmp(a->prop_name, b->prop_name);
return a->anim_value < b->anim_value;
}
ufbx_static_assert(bake_step_left, UFBX_BAKED_KEY_STEP_LEFT == 0x1);
ufbx_static_assert(bake_step_right, UFBX_BAKED_KEY_STEP_RIGHT == 0x2);
ufbx_static_assert(bake_step_key, UFBX_BAKED_KEY_STEP_KEY == 0x4);
static ufbxi_forceinline int ufbxi_cmp_bake_time(ufbxi_bake_time a, ufbxi_bake_time b)
{
if (a.time != b.time) return a.time < b.time ? -1 : 1;
// Bit twiddling for a fast sorting of `0x1 (LEFT) < 0x0 < 0x2 (RIGHT)`
// by `step ^ 1`: `0x0 (LEFT) < 0x1 < 0x3 (RIGHT)`
uint32_t a_step = a.flags & 0x3, b_step = b.flags & 0x3;
if (a_step != b_step) return (a_step ^ 0x1) < (b_step ^ 0x1) ? -1 : 1;
return 0;
}
static int ufbxi_cmp_bake_time_fn(const void *va, const void *vb)
{
const ufbxi_bake_time a = *(const ufbxi_bake_time*)va;
const ufbxi_bake_time b = *(const ufbxi_bake_time*)vb;
return ufbxi_cmp_bake_time(a, b);
}
ufbxi_nodiscard static ufbxi_forceinline int ufbxi_bake_push_time(ufbxi_bake_context *bc, double time, uint32_t flags)
{
ufbxi_bake_time *p_key = ufbxi_push_fast(&bc->tmp_times, ufbxi_bake_time, 1);
if (!p_key) return 0;
p_key->time = time;
p_key->flags = flags;
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_times(ufbxi_bake_context *bc, const ufbx_anim_value *anim_value, bool resample_linear, uint32_t key_flag)
{
double sample_rate = bc->opts.resample_rate;
double min_duration = bc->opts.minimum_sample_rate > 0.0 ? 1.0 / bc->opts.minimum_sample_rate : 0.0;
for (size_t curve_ix = 0; curve_ix < 3; curve_ix++) {
ufbx_anim_curve *curve = anim_value->curves[curve_ix];
if (!curve) continue;
const ufbx_keyframe *keys = curve->keyframes.data;
size_t num_keys = curve->keyframes.count;
for (size_t key_ix = 0; key_ix < num_keys; key_ix++) {
ufbx_keyframe a = keys[key_ix];
double a_time = a.time;
ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time, key_flag));
if (key_ix + 1 >= num_keys) break;
ufbx_keyframe b = keys[key_ix + 1];
double b_time = b.time;
// Skip fully flat sections
if (a.value == b.value && a.right.dy == 0.0f && b.left.dy == 0.0f) continue;
if (a.interpolation == UFBX_INTERPOLATION_CONSTANT_PREV) {
ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, b_time, UFBX_BAKED_KEY_STEP_LEFT));
} else if (a.interpolation == UFBX_INTERPOLATION_CONSTANT_NEXT) {
ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time, UFBX_BAKED_KEY_STEP_RIGHT));
} else if ((resample_linear || a.interpolation == UFBX_INTERPOLATION_CUBIC) && sample_rate > 0.0) {
double duration = b_time - a_time;
if (duration <= min_duration) continue;
double factor = 1.0;
while (duration * sample_rate / factor >= (double)bc->opts.max_keyframe_segments) {
factor *= 2.0;
}
double padding = 0.5 / sample_rate;
double start = ufbx_ceil((a_time + padding) * sample_rate / factor) * factor;
double stop = b_time - padding;
for (size_t i = 0; i < bc->opts.max_keyframe_segments; i++) {
double time = (start + (double)i * factor) / sample_rate;
if (time >= stop) break;
ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, time, 0));
}
}
}
}
return 1;
}
static const char *const ufbxi_transform_props[] = {
ufbxi_Lcl_Translation, ufbxi_Lcl_Rotation, ufbxi_Lcl_Scaling, ufbxi_PreRotation, ufbxi_PostRotation,
ufbxi_RotationOffset, ufbxi_ScalingOffset, ufbxi_RotationPivot, ufbxi_ScalingPivot, ufbxi_RotationOrder,
};
static const char *const ufbxi_complex_translation_props[] = {
ufbxi_ScalingPivot, ufbxi_RotationPivot, ufbxi_RotationOffset, ufbxi_ScalingOffset,
};
static const char *const ufbxi_complex_rotation_props[] = {
ufbxi_PreRotation, ufbxi_PostRotation, ufbxi_RotationOrder,
};
static const char *const ufbxi_complex_rotation_sources[] = {
ufbxi_Lcl_Rotation, ufbxi_PreRotation, ufbxi_PostRotation, ufbxi_RotationOrder,
};
ufbxi_nodiscard static ufbxi_noinline bool ufbxi_in_list(const char *const *items, size_t count, const char *item)
{
for (size_t i = 0; i < count; i++) {
if (items[i] == item) return true;
}
return false;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_context *bc, ufbxi_bake_time_list *p_dst)
{
if (bc->layer_weight_times.count > 0) {
ufbxi_check_err(&bc->error, ufbxi_push_copy(&bc->tmp_times, ufbxi_bake_time, bc->layer_weight_times.count, bc->layer_weight_times.data));
}
if (bc->tmp_times.num_items == 0) {
ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_begin, 0));
ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_end, 0));
}
size_t num_times = bc->tmp_times.num_items;
ufbxi_bake_time *times = ufbxi_push_pop(&bc->tmp_prop, &bc->tmp_times, ufbxi_bake_time, num_times);
ufbxi_check_err(&bc->error, times);
// TODO: Something better
qsort(times, num_times, sizeof(ufbxi_bake_time), &ufbxi_cmp_bake_time_fn);
// Deduplicate times
if (num_times > 0) {
size_t dst = 0;
ufbxi_bake_time prev = times[0];
for (size_t src = 1; src < num_times; src++) {
ufbxi_bake_time next = times[src];
// Merge keys with the same time and step flags `(0x1, 0x2)`
if (next.time == prev.time) {
if (((next.flags ^ prev.flags) & 0x3) == 0) {
prev.flags |= next.flags;
continue;
} else if (prev.flags & UFBX_BAKED_KEY_STEP_LEFT) {
next.flags |= UFBX_BAKED_KEY_STEP_KEY;
} else if (next.flags & UFBX_BAKED_KEY_STEP_RIGHT) {
prev.flags |= UFBX_BAKED_KEY_STEP_KEY;
}
}
times[dst++] = prev;
prev = next;
}
times[dst++] = prev;
num_times = dst;
}
// Cull too close resampled keys, these may arise during merging multiple times
if (num_times > 0) {
double min_dist = 0.25 / bc->opts.resample_rate;
uint32_t keep_flags = UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT|UFBX_BAKED_KEY_STEP_KEY|UFBX_BAKED_KEY_KEYFRAME;
size_t dst = 0;
for (size_t src = 0; src < num_times; src++) {
ufbxi_bake_time cur = times[src];
double delta = UFBX_INFINITY;
bool keep = true;
if ((cur.flags & keep_flags) == 0) {
if (dst > 0) delta = cur.time - times[dst - 1].time;
if (src + 1 < num_times) delta = ufbx_fmin(delta, times[src + 1].time - cur.time);
if (delta < min_dist) keep = false;
}
if (keep) {
times[dst++] = cur;
}
}
num_times = dst;
}
// Enforce maximum sample rate
if (bc->opts.maximum_sample_rate > 0.0) {
const double epsilon = 0.0078125 / bc->opts.maximum_sample_rate;
double sample_rate = bc->opts.maximum_sample_rate;
double max_interval = 1.0 / bc->opts.maximum_sample_rate;
double min_interval = 1.0 / bc->opts.maximum_sample_rate - epsilon;
size_t dst = 0, src = 0;
// Pre-expand constant keyframes
for (size_t i = 0; i < num_times; i++) {
if ((times[i].flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) {
double sign = (times[i].flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 ? -1.0 : 1.0;
double time = times[i].time + sign * max_interval;
if (i > 0) time = ufbx_fmax(time, times[i - 1].time);
if (i + 1 < num_times) time = ufbx_fmin(time, times[i + 1].time);
times[i].time = time;
times[i].flags = UFBX_BAKED_KEY_REDUCED;
}
}
ufbxi_bake_time prev_time = { -UFBX_INFINITY };
while (src < num_times) {
ufbxi_bake_time src_time = times[src];
src++;
size_t start_src = src;
ufbxi_bake_time next_time;
next_time.time = ufbx_ceil(src_time.time * sample_rate - epsilon) / sample_rate;
next_time.flags = UFBX_BAKED_KEY_REDUCED;
while (src < num_times && times[src].time <= next_time.time + epsilon) {
src++;
}
if (src != start_src || src_time.time - prev_time.time <= min_interval) {
prev_time = next_time;
} else {
prev_time = src_time;
}
if (dst == 0 || prev_time.time > times[dst - 1].time) {
times[dst++] = prev_time;
}
}
num_times = dst;
}
if (num_times > 0) {
if (times[0].time < bc->time_min) bc->time_min = times[0].time;
if (times[num_times - 1].time > bc->time_max) bc->time_max = times[num_times - 1].time;
}
p_dst->data = times;
p_dst->count = num_times;
return 1;
}
#define ufbxi_add_epsilon(a, epsilon) ((a)>0 ? (a)*(epsilon) : (a)/(epsilon))
#define ufbxi_sub_epsilon(a, epsilon) ((a)>0 ? (a)/(epsilon) : (a)*(epsilon))
static ufbxi_noinline bool ufbxi_postprocess_step(ufbxi_bake_context *bc, double prev_time, double next_time, double *p_time, uint32_t flags)
{
ufbxi_dev_assert((flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0);
bool left = (flags & UFBX_BAKED_KEY_STEP_LEFT) != 0;
double step = 0.001;
double epsilon = 1.0 + FLT_EPSILON * 4.0f;
double time = *p_time;
switch (bc->opts.step_handling) {
case UFBX_BAKE_STEP_HANDLING_DEFAULT:
break;
case UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION:
step = bc->opts.step_custom_duration;
epsilon = 1.0 + bc->opts.step_custom_epsilon;
break;
case UFBX_BAKE_STEP_HANDLING_IDENTICAL_TIME:
return true;
case UFBX_BAKE_STEP_HANDLING_ADJACENT_DOUBLE:
if (left) {
*p_time = time = ufbx_nextafter(time, -UFBX_INFINITY);
return time > prev_time;
} else {
*p_time = time = ufbx_nextafter(time, UFBX_INFINITY);
return time < next_time;
}
case UFBX_BAKE_STEP_HANDLING_IGNORE:
return false;
default:
ufbxi_unreachable("Unhandled bake step handling");
return false;
}
if (left) {
double min_time = ufbx_fmax(prev_time + step, ufbxi_add_epsilon(prev_time, epsilon));
*p_time = time = ufbx_fmin(time - step, ufbxi_sub_epsilon(time, epsilon));
return time > min_time;
} else {
double max_time = ufbx_fmin(next_time - step, ufbxi_sub_epsilon(next_time, epsilon));
*p_time = time = ufbx_fmax(time + step, ufbxi_add_epsilon(time, epsilon));
return time < max_time;
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_postprocess_vec3(ufbxi_bake_context *bc, ufbx_baked_vec3_list *p_dst, bool *p_constant, ufbx_baked_vec3_list src)
{
if (src.count == 0) return 1;
// Offset times
if (bc->ktime_offset != 0.0) {
double scale = (double)bc->scene->metadata.ktime_second;
double offset = bc->ktime_offset;
for (size_t i = 0; i < src.count; i++) {
src.data[i].time = ufbx_rint(src.data[i].time * scale + offset) / scale;
}
}
// Postprocess stepped tangents
{
size_t dst = 0;
double prev_time = src.data[0].time;
for (size_t i = 0; i < src.count; i++) {
ufbx_baked_vec3 cur = src.data[i];
double next_time = i + 1 < src.count ? src.data[i + 1].time : UFBX_INFINITY;
bool keep = true;
if ((cur.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) {
keep = ufbxi_postprocess_step(bc, prev_time, next_time, &cur.time, cur.flags);
}
if (keep) {
src.data[dst] = cur;
dst++;
prev_time = cur.time;
}
}
src.count = dst;
}
if (bc->opts.key_reduction_enabled) {
double threshold = bc->opts.key_reduction_threshold * bc->opts.key_reduction_threshold;
for (size_t pass = 0; pass < bc->opts.key_reduction_passes; pass++) {
size_t dst = 1;
for (size_t i = 1; i < src.count; i++) {
ufbx_baked_vec3 prev = src.data[i - 1];
ufbx_baked_vec3 cur = src.data[i];
if (i + 1 < src.count) {
ufbx_baked_vec3 next = src.data[i + 1];
double delta = (cur.time - prev.time) / (next.time - prev.time);
ufbx_vec3 tmp = ufbxi_lerp3(prev.value, next.value, (ufbx_real)delta);
double error = 0.0;
error += ((double)tmp.x - (double)cur.value.x) * ((double)tmp.x - (double)cur.value.x);
error += ((double)tmp.y - (double)cur.value.y) * ((double)tmp.y - (double)cur.value.y);
error += ((double)tmp.z - (double)cur.value.z) * ((double)tmp.z - (double)cur.value.z);
if (error <= threshold) {
src.data[dst] = src.data[i + 1];
i += 1;
dst += 1;
continue;
}
}
src.data[dst] = src.data[i];
dst += 1;
}
if (dst == src.count) break;
src.count = dst;
}
}
bool constant = true;
ufbx_vec3 ref = src.data[0].value;
for (size_t i = 1; i < src.count; i++) {
ufbx_vec3 v = src.data[i].value;
if (v.x != ref.x || v.y != ref.y || v.z != ref.z) {
constant = false;
break;
}
}
*p_constant = constant;
p_dst->count = src.count;
p_dst->data = ufbxi_push_copy(&bc->result, ufbx_baked_vec3, src.count, src.data);
ufbxi_check_err(&bc->error, p_dst->data);
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_postprocess_quat(ufbxi_bake_context *bc, ufbx_baked_quat_list *p_dst, bool *p_constant, ufbx_baked_quat_list src)
{
if (src.count == 0) return 1;
// Offset times
if (bc->ktime_offset != 0.0) {
double scale = (double)bc->scene->metadata.ktime_second;
double offset = bc->ktime_offset;
for (size_t i = 0; i < src.count; i++) {
src.data[i].time = ufbx_rint(src.data[i].time * scale + offset) / scale;
}
}
// Postprocess stepped tangents
{
size_t dst = 0;
double prev_time = src.data[0].time;
for (size_t i = 0; i < src.count; i++) {
ufbx_baked_quat cur = src.data[i];
double next_time = i + 1 < src.count ? src.data[i + 1].time : UFBX_INFINITY;
bool keep = true;
if ((cur.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) {
keep = ufbxi_postprocess_step(bc, prev_time, next_time, &cur.time, cur.flags);
}
if (keep) {
prev_time = cur.time;
src.data[dst] = cur;
dst++;
}
}
src.count = dst;
}
// Fix quaternion antipodality
for (size_t i = 1; i < src.count; i++) {
src.data[i].value = ufbx_quat_fix_antipodal(src.data[i].value, src.data[i - 1].value);
}
if (bc->opts.key_reduction_enabled) {
double threshold = bc->opts.key_reduction_threshold * bc->opts.key_reduction_threshold;
for (size_t pass = 0; pass < bc->opts.key_reduction_passes; pass++) {
size_t dst = 1;
for (size_t i = 1; i < src.count; i++) {
ufbx_baked_quat prev = src.data[i - 1];
ufbx_baked_quat cur = src.data[i];
if (i + 1 < src.count) {
ufbx_baked_quat next = src.data[i + 1];
double delta = (cur.time - prev.time) / (next.time - prev.time);
double error = 0.0;
if (bc->opts.key_reduction_rotation) {
ufbx_quat tmp = ufbx_quat_slerp(prev.value, next.value, (ufbx_real)delta);
error += ((double)tmp.x - (double)cur.value.x) * ((double)tmp.x - (double)cur.value.x);
error += ((double)tmp.y - (double)cur.value.y) * ((double)tmp.y - (double)cur.value.y);
error += ((double)tmp.z - (double)cur.value.z) * ((double)tmp.z - (double)cur.value.z);
error += ((double)tmp.w - (double)cur.value.w) * ((double)tmp.w - (double)cur.value.w);
} else {
error += ((double)prev.value.x - (double)cur.value.x) * ((double)prev.value.x - (double)cur.value.x);
error += ((double)prev.value.y - (double)cur.value.y) * ((double)prev.value.y - (double)cur.value.y);
error += ((double)prev.value.z - (double)cur.value.z) * ((double)prev.value.z - (double)cur.value.z);
error += ((double)prev.value.w - (double)cur.value.w) * ((double)prev.value.w - (double)cur.value.w);
error += ((double)next.value.x - (double)cur.value.x) * ((double)next.value.x - (double)cur.value.x);
error += ((double)next.value.y - (double)cur.value.y) * ((double)next.value.y - (double)cur.value.y);
error += ((double)next.value.z - (double)cur.value.z) * ((double)next.value.z - (double)cur.value.z);
error += ((double)next.value.w - (double)cur.value.w) * ((double)next.value.w - (double)cur.value.w);
error *= 0.5;
}
if (error <= threshold) {
src.data[dst] = src.data[i + 1];
i += 1;
dst += 1;
continue;
}
}
src.data[dst] = src.data[i];
dst += 1;
}
if (dst == src.count) break;
src.count = dst;
}
}
bool constant = true;
ufbx_quat ref = src.data[0].value;
for (size_t i = 1; i < src.count; i++) {
ufbx_quat v = src.data[i].value;
if (v.x != ref.x || v.y != ref.y || v.z != ref.z || v.y != ref.w) {
constant = false;
break;
}
}
*p_constant = constant;
p_dst->count = src.count;
p_dst->data = ufbxi_push_copy(&bc->result, ufbx_baked_quat, src.count, src.data);
ufbxi_check_err(&bc->error, p_dst->data);
return 1;
}
static ufbxi_forceinline double ufbxi_bake_time_sample_time(ufbxi_bake_time time)
{
// Move an infinitesimal step for stepped tangents
if ((time.flags & (UFBX_BAKED_KEY_STEP_LEFT|UFBX_BAKED_KEY_STEP_RIGHT)) != 0) {
double dir = (time.flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 ? -UFBX_INFINITY : UFBX_INFINITY;
return ufbx_nextafter(time.time, dir);
} else {
return time.time;
}
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_resampled_times(ufbxi_bake_context *bc, const ufbx_baked_vec3_list *p_keys)
{
ufbx_baked_vec3_list keys = *p_keys;
ufbxi_bake_time *times = ufbxi_push(&bc->tmp_times, ufbxi_bake_time, keys.count);
ufbxi_check_err(&bc->error, times);
for (size_t i = 0; i < keys.count; i++) {
uint32_t flags = keys.data[i].flags;
double time = keys.data[i].time;
if ((flags & UFBX_BAKED_KEY_STEP_LEFT) != 0 && i + 1 < keys.count && (keys.data[i + 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) {
time = keys.data[i + 1].time;
} else if ((flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && i > 0 && (keys.data[i - 1].flags & UFBX_BAKED_KEY_STEP_KEY) != 0) {
time = keys.data[i - 1].time;
}
times[i].time = time;
times[i].flags = flags & 0x7;
}
return 1;
}
ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context *bc, uint32_t element_id, ufbxi_bake_pro
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment