diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index 5f94a8056604..114564c7e3ca 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -2029,7 +2029,7 @@ Error FBXDocument::_parse(Ref p_state, String p_path, Ref opts.space_conversion = UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY; if (!p_state->get_allow_geometry_helper_nodes()) { opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK; - opts.inherit_mode_handling = UFBX_INHERIT_MODE_HANDLING_IGNORE; + opts.inherit_mode_handling = UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK; } else { opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES; opts.inherit_mode_handling = UFBX_INHERIT_MODE_HANDLING_COMPENSATE; diff --git a/thirdparty/README.md b/thirdparty/README.md index 4a7ab7314afc..255915b655f8 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -894,7 +894,7 @@ number and run the script. ## ufbx - Upstream: https://github.com/ufbx/ufbx -- Version: git (v0.11.1, 2024) +- Version: 0.14.0 (80ff790ab36507b99ec7e4ef55b9cfb076ce821b, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/ufbx/ufbx.c b/thirdparty/ufbx/ufbx.c index e6b2c91c650b..6d584369a7b2 100644 --- a/thirdparty/ufbx/ufbx.c +++ b/thirdparty/ufbx/ufbx.c @@ -43,6 +43,7 @@ #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 @@ -210,6 +211,7 @@ #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 @@ -229,6 +231,7 @@ 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 @@ -439,6 +442,22 @@ #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 + #include +#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. @@ -551,29 +570,11 @@ ufbx_static_assert(sizeof_f64, sizeof(double) == 8); // -- Version -#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 11, 1) -const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION; +#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 14, 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); -// -- Architecture - -#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 - #include -#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 - // -- Fast copy #if UFBXI_HAS_SSE @@ -1032,6 +1033,53 @@ static ufbxi_noinline void ufbxi_stable_sort(size_t stride, size_t linear_size, 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 @@ -1158,7 +1206,7 @@ typedef enum { UFBXI_PARSE_DOUBLE_VERIFY_LENGTH = 0x2, } ufbxi_parse_double_flag; -static uint64_t ufbxi_pow5_tab[] = { +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 @@ -1188,10 +1236,10 @@ static uint64_t ufbxi_pow5_tab[] = { UINT64_C(0xa56fa5b99019a5c8), // 5^26 * 2^3 UINT64_C(0xcecb8f27f4200f3a), // 5^27 * 2^1 }; -static int8_t ufbxi_pow2_tab[] = { +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, }; -const double ufbxi_pow10_tab_f64[] = { +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, }; @@ -1201,8 +1249,8 @@ static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags() // 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 eps = 2.2250738585072014e-308; - if (1.0 + eps == 1.0 - eps) return UFBXI_PARSE_DOUBLE_ALLOW_FAST_PATH; + 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 @@ -2834,7 +2882,7 @@ static ufbxi_noinline void ufbxi_panicf_imp(ufbx_panic *panic, const char *fmt, { if (panic && panic->did_panic) return; - va_list args; + va_list args; // ufbxi_uninit va_start(args, fmt); if (panic) { @@ -2960,7 +3008,7 @@ static ufbxi_noinline void ufbxi_fmt_err_info(ufbx_error *err, const char *fmt, { if (!err) return; - va_list args; + 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); @@ -3544,14 +3592,14 @@ static ufbxi_forceinline void *ufbxi_push_size_fast(ufbxi_buf *b, size_t size, s } } -static ufbxi_forceinline void *ufbxi_push_size_zero(ufbxi_buf *b, size_t size, size_t n) +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_forceinline void *ufbxi_push_size_copy(ufbxi_buf *b, size_t size, size_t n, const void *data) +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); @@ -3563,6 +3611,18 @@ ufbxi_nodiscard static ufbxi_forceinline void *ufbxi_push_size_copy(ufbxi_buf *b 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); @@ -3774,6 +3834,7 @@ static ufbxi_noinline void ufbxi_buf_clear(ufbxi_buf *buf) #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) @@ -4273,7 +4334,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_vwarnf_imp(ufbxi_warnings *ws, u 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; + va_list args; // ufbxi_uninit va_start(args, fmt); int ok = ufbxi_vwarnf_imp(ws, type, element_id, fmt, args); va_end(args); @@ -4705,6 +4766,8 @@ 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"; @@ -4982,7 +5045,7 @@ 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 ufbx_string ufbxi_strings[] = { +static const ufbx_string ufbxi_strings[] = { { ufbxi_AllSame, 7 }, { ufbxi_Alphas, 6 }, { ufbxi_AmbientColor, 12 }, @@ -4998,6 +5061,8 @@ static ufbx_string ufbxi_strings[] = { { ufbxi_AspectRatioMode, 15 }, { ufbxi_AspectW, 7 }, { ufbxi_AspectWidth, 11 }, + { ufbxi_Audio, 5 }, + { ufbxi_AudioLayer, 10 }, { ufbxi_BaseLayer, 9 }, { ufbxi_BinaryData, 10 }, { ufbxi_BindPose, 8 }, @@ -5336,6 +5401,11 @@ ufbx_inline ufbx_vec3 ufbxi_normalize3(ufbx_vec3 a) { } } +ufbx_inline ufbx_real ufbxi_distsq2(ufbx_vec2 a, ufbx_vec2 b) { + ufbx_real dx = a.x - b.x, dy = a.y - b.y; + return dx*dx + dy*dy; +} + static ufbxi_noinline ufbx_vec3 ufbxi_slow_normalize3(const ufbx_vec3 *a) { return ufbxi_normalize3(*a); } @@ -5384,8 +5454,6 @@ struct ufbxi_thread_pool { ufbxi_task_group groups[UFBX_THREAD_GROUP_COUNT]; uint32_t group; - double accumulated_cost; - uint32_t num_tasks; ufbxi_task_imp *tasks; }; @@ -5496,22 +5564,7 @@ ufbxi_nodiscard ufbxi_noinline static uint32_t ufbxi_thread_pool_available_tasks return pool->num_tasks - (pool->start_index - pool->wait_index); } -static void ufbxi_thread_pool_flush(ufbxi_thread_pool *pool) -{ - uint32_t start_index = pool->execute_index; - uint32_t count = pool->start_index - start_index; - if (count > 0) { -#if 0 - if (pool->opts.pool.run_fn) { - uint32_t ran_count = pool->opts.pool.run_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, pool->group, start_index, count); - pool->execute_index = start_index + ran_count; - } -#endif - } - pool->accumulated_cost = 0.0; -} - -static void ufbxi_thread_pool_flush_group(ufbxi_thread_pool *pool) +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; @@ -5523,7 +5576,6 @@ static void ufbxi_thread_pool_flush_group(ufbxi_thread_pool *pool) pool->groups[group].max_index = start_index + count; pool->execute_index = start_index + count; } - pool->accumulated_cost = 0.0; pool->group = (group + 1) % UFBX_THREAD_GROUP_COUNT; } @@ -5550,17 +5602,12 @@ ufbxi_nodiscard ufbxi_noinline static ufbxi_task *ufbxi_thread_pool_create_task( return &imp->task; } -static void ufbxi_thread_pool_run_task(ufbxi_thread_pool *pool, ufbxi_task *task, double cost) +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; - pool->accumulated_cost += cost; - - if (pool->accumulated_cost >= 256*1024) { - ufbxi_thread_pool_flush(pool); - } } // -- Type definitions @@ -5791,6 +5838,11 @@ typedef struct { 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. @@ -5978,6 +6030,7 @@ typedef struct { bool has_geometry_transform_nodes; bool has_scale_helper_nodes; + bool retain_vertex_w; ufbx_mirror_axis mirror_axis; @@ -5997,6 +6050,9 @@ typedef struct { 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; @@ -7177,6 +7233,7 @@ typedef enum { 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, @@ -7250,6 +7307,7 @@ static ufbxi_noinline ufbxi_parse_state ufbxi_update_parse_state(ufbxi_parse_sta 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: @@ -7483,7 +7541,7 @@ static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, con info->flags = UFBXI_ARRAY_FLAG_RESULT; return true; } else if (name == ufbxi_NormalsW) { - info->type = uc->opts.retain_dom ? 'r' : '-'; + info->type = uc->retain_vertex_w ? 'r' : '-'; info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; return true; } @@ -7499,7 +7557,7 @@ static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, con info->flags = UFBXI_ARRAY_FLAG_RESULT; return true; } else if (name == ufbxi_BinormalsW) { - info->type = uc->opts.retain_dom ? 'r' : '-'; + info->type = uc->retain_vertex_w ? 'r' : '-'; info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; return true; } @@ -7515,7 +7573,7 @@ static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, con info->flags = UFBXI_ARRAY_FLAG_RESULT; return true; } else if (name == ufbxi_TangentsW) { - info->type = uc->opts.retain_dom ? 'r' : '-'; + info->type = uc->retain_vertex_w ? 'r' : '-'; info->flags = UFBXI_ARRAY_FLAG_RESULT | UFBXI_ARRAY_FLAG_PAD_BEGIN; return true; } @@ -7723,6 +7781,13 @@ static bool ufbxi_is_array_node(ufbxi_context *uc, ufbxi_parse_state parent, con } 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'; @@ -7800,6 +7865,10 @@ static ufbxi_noinline bool ufbxi_is_raw_string(ufbxi_context *uc, ufbxi_parse_st 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; @@ -7834,6 +7903,7 @@ static ufbxi_noinline bool ufbxi_is_raw_string(ufbxi_context *uc, ufbxi_parse_st 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) { @@ -7843,26 +7913,20 @@ ufbxi_nodiscard static ufbxi_noinline char *ufbxi_swap_endian(ufbxi_context *uc, const char *s = (const char*)src; switch (elem_size) { - case 1: - for (size_t i = 0; i < count; i++) { - d[0] = s[0]; - d += 1; s += 1; - } - break; case 2: - for (size_t i = 0; i < count; i++) { + 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: - for (size_t i = 0; i < count; i++) { + 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: - for (size_t i = 0; i < count; i++) { + 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; @@ -8338,6 +8402,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_binary_parse_node(ufbxi_context 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; @@ -8359,7 +8424,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_binary_parse_node(ufbxi_context } task->data = t; - ufbxi_thread_pool_run_task(&uc->thread_pool, task, (double)encoded_size); + ufbxi_thread_pool_run_task(&uc->thread_pool, task); deferred = true; } } @@ -8507,9 +8572,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_binary_parse_node(ufbxi_context switch (type) { - case 'C': case 'B': + case 'C': case 'B': case 'Z': type_mask |= (uint32_t)UFBXI_VALUE_NUMBER << (i*2); - vals[i].f = (double)(vals[i].i = (int64_t)value[0]); + vals[i].f = (double)(vals[i].i = (int64_t)(uint8_t)value[0]); ufbxi_consume_bytes(uc, 2); break; @@ -9201,22 +9266,17 @@ typedef struct { void *arr_data; char arr_type; size_t arr_size; - uint32_t parse_flags; - 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) +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); - uint32_t parse_flags = t->parse_flags; const char *src_begin = src; while (src != src_end) { @@ -9281,7 +9341,8 @@ ufbxi_noinline static const char *ufbxi_ascii_array_task_parse_ints(ufbxi_ascii_ 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') { - return ufbxi_ascii_array_task_parse_floats(t, src, src_end); + 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); } @@ -9804,7 +9865,6 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_parse_node(ufbxi_context * t.arr_size = deferred_size; t.num_spans = num_spans; t.spans = spans; - t.parse_flags = uc->double_parse_flags; t.offset = 0; // TODO: Split these further @@ -9812,7 +9872,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_parse_node(ufbxi_context * 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, deferred_size * 10.0); + ufbxi_thread_pool_run_task(&uc->thread_pool, task); } else { ufbxi_check_msg(ufbxi_ascii_array_task_imp(&t), "Threaded ASCII parse error"); } @@ -10574,7 +10634,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_strings(ufbxi_context *uc) // 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(ufbx_string, str, ufbxi_strings, ufbxi_arraycount(ufbxi_strings)) { + 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)); @@ -10748,9 +10808,9 @@ 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_forceinline bool ufbxi_is_transform_identity(ufbx_transform t) +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)); + 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) @@ -10789,7 +10849,7 @@ static ufbxi_forceinline bool ufbxi_name_key_less(ufbx_prop *prop, const char *d return prop_len < name_len; } -static const char *ufbxi_node_prop_names[] = { +static const char *const ufbxi_node_prop_names[] = { "AxisLen", "DefaultAttributeIndex", "Freeze", @@ -10867,8 +10927,8 @@ static const char *ufbxi_node_prop_names[] = { 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))); - ufbxi_for_ptr(const char, p_name, ufbxi_node_prop_names, ufbxi_arraycount(ufbxi_node_prop_names)) { - const char *name = *p_name; + 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); @@ -11266,16 +11326,6 @@ ufbxi_nodiscard static int ufbxi_match_exporter(ufbxi_context *uc) } 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); - } else if (ufbxi_match_version_string("fbx unity export version ?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_BC_UNITY_EXPORTER; - uc->exporter_version = ufbx_pack_version(version[0], version[1], 0); - } else if (ufbxi_match_version_string("fbx unity export version ?.?.?", creator, version)) { - uc->exporter = UFBX_EXPORTER_BC_UNITY_EXPORTER; - uc->exporter_version = ufbx_pack_version(version[0], version[1], version[2]); - } else if (ufbxi_match_version_string("made using asset forge", creator, version)) { - uc->exporter = UFBX_EXPORTER_BC_UNITY_EXPORTER; - } else if (ufbxi_match_version_string("model created by kenney", creator, version)) { - uc->exporter = UFBX_EXPORTER_BC_UNITY_EXPORTER; } uc->scene.metadata.exporter = uc->exporter; @@ -11407,7 +11457,7 @@ static ufbxi_noinline int ufbxi_push_synthetic_id(ufbxi_context *uc, uint64_t *p return 1; } -ufbxi_nodiscard static int ufbxi_split_type_and_name(ufbxi_context *uc, ufbx_string type_and_name, ufbx_string *type, ufbx_string *name) +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) @@ -11495,9 +11545,9 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_element_size(ufbx 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(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL); - ufbxi_check_return(ufbxi_push_copy(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL); - ufbxi_check_return(ufbxi_push_copy(&uc->tmp_element_fbx_ids, uint64_t, 1, &info->fbx_id), NULL); + 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); @@ -11513,7 +11563,7 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_element_size(ufbx *uc->p_element_id = element_id; } - ufbxi_check_return(ufbxi_push_copy(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL); + 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); @@ -11527,8 +11577,8 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_synthetic_element 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(&uc->tmp_typed_element_offsets[type], size_t, 1, &uc->tmp_element_byte_offset), NULL); - ufbxi_check_return(ufbxi_push_copy(&uc->tmp_element_offsets, size_t, 1, &uc->tmp_element_byte_offset), NULL); + 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); @@ -11542,12 +11592,12 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_synthetic_element elem->name.length = strlen(name); } - ufbxi_check_return(ufbxi_push_copy(&uc->tmp_element_ptrs, ufbx_element*, 1, &elem), NULL); + 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(&uc->tmp_element_fbx_ids, uint64_t, 1, &fbx_id), NULL); + 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; @@ -11867,8 +11917,19 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_check_indices(ufbxi_context *uc, return 1; } +ufbx_static_assert(vertex_real_size, sizeof(ufbx_vertex_real) == sizeof(ufbx_vertex_attrib)); +ufbx_static_assert(vertex_vec2_size, sizeof(ufbx_vertex_vec2) == sizeof(ufbx_vertex_attrib)); +ufbx_static_assert(vertex_vec3_size, sizeof(ufbx_vertex_vec3) == sizeof(ufbx_vertex_attrib)); +ufbx_static_assert(vertex_vec4_size, sizeof(ufbx_vertex_vec4) == sizeof(ufbx_vertex_attrib)); + +ufbxi_nodiscard ufbxi_noinline static int ufbxi_warn_polygon_mapping(ufbxi_context *uc, const char *data_name, const char *mapping) +{ + ufbxi_check(ufbxi_warnf(UFBX_WARNING_MISSING_POLYGON_MAPPING, "Ignoring geometry '%s' with bad mapping mode '%s'", data_name, mapping)); + 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, char data_type, size_t num_components) + 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; @@ -11895,8 +11956,8 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_vertex_element(ufbxi_contex attrib->exists = true; attrib->indices.count = mesh->num_indices; - const char *mapping = NULL; - ufbxi_check(ufbxi_find_val1(node, ufbxi_MappingInformationType, "C", (char**)&mapping)); + const char *mapping = ""; + ufbxi_ignore(ufbxi_find_val1(node, ufbxi_MappingInformationType, "C", (char**)&mapping)); attrib->values.count = num_elems ? num_elems : 1; @@ -11979,7 +12040,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_vertex_element(ufbxi_contex attrib->unique_per_vertex = true; } else { - ufbxi_fail("Invalid mapping"); + memset(attrib, 0, sizeof(ufbx_vertex_attrib)); + ufbxi_check(ufbxi_warn_polygon_mapping(uc, data_name, mapping)); + return 1; } } else { @@ -12030,7 +12093,22 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_vertex_element(ufbxi_contex attrib->unique_per_vertex = true; } else { - ufbxi_fail("Invalid mapping"); + memset(attrib, 0, sizeof(ufbx_vertex_attrib)); + ufbxi_check(ufbxi_warn_polygon_mapping(uc, data_name, mapping)); + return 1; + } + } + + 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)); + } } } @@ -12355,11 +12433,11 @@ typedef struct { uint32_t id, index; } ufbxi_id_group; -static int ufbxi_cmp_int32(const void *va, const void *vb) +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; - if (a != b) return a < b ? -1 : +1; - return 0; + 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)); @@ -12409,7 +12487,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_assign_face_groups(ufbxi_buf *bu } // Sort and deduplicate remaining IDs - qsort(ids, num_ids, sizeof(uint32_t), &ufbxi_cmp_int32); + 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; ) { @@ -12668,13 +12746,13 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufb 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, 'r', 3)); + 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, 'r', 3)); + ufbxi_Binormals, ufbxi_BinormalsIndex, ufbxi_BinormalsW, 'r', 3)); if (!layer->elem.exists) num_bitangents_read--; } else if (n->name == ufbxi_LayerElementTangent) { @@ -12682,7 +12760,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufb 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, 'r', 3)); + ufbxi_Tangents, ufbxi_TangentsIndex, ufbxi_TangentsW, 'r', 3)); if (!layer->elem.exists) num_tangents_read--; } else if (n->name == ufbxi_LayerElementUV) { @@ -12694,7 +12772,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufb } ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_uv, - ufbxi_UV, ufbxi_UVIndex, 'r', 2)); + ufbxi_UV, ufbxi_UVIndex, NULL, 'r', 2)); if (!set->vertex_uv.exists) mesh->uv_sets.count--; } else if (n->name == ufbxi_LayerElementColor) { @@ -12706,40 +12784,46 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufb } ufbxi_check(ufbxi_read_vertex_element(uc, mesh, n, (ufbx_vertex_attrib*)&set->vertex_color, - ufbxi_Colors, ufbxi_ColorIndex, 'r', 4)); + 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, 'r', 1)); + 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)); + const char *mapping = ""; + ufbxi_ignore(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 { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_EdgeCrease, mapping)); } } else if (n->name == ufbxi_LayerElementSmoothing) { - const char *mapping = NULL; - ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + const char *mapping = ""; + ufbxi_ignore(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 { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Smoothing, mapping)); } } else if (n->name == ufbxi_LayerElementVisibility) { - const char *mapping = NULL; - ufbxi_check(ufbxi_find_val1(n, ufbxi_MappingInformationType, "c", (char**)&mapping)); + const char *mapping = ""; + ufbxi_ignore(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 { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Visibility, mapping)); } } 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)); + const char *mapping = ""; + ufbxi_ignore(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) { @@ -12756,6 +12840,8 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufb *p_mat = material; } } + } else { + ufbxi_check(ufbxi_warn_polygon_mapping(uc, ufbxi_Materials, mapping)); } } else if (n->name == ufbxi_LayerElementPolygonGroup) { if (mesh->face_group.count) continue; @@ -13168,6 +13254,24 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_blend_channel(ufbxi_context } 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; } @@ -13833,6 +13937,21 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_character(ufbxi_context *uc 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; @@ -14112,6 +14231,10 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_object(ufbxi_context *uc, u 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)); } @@ -14178,8 +14301,6 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects_threaded(ufbxi_cont uc->p_element_id = NULL; } batch->num_nodes = 0; - - ufbxi_thread_pool_flush(&uc->thread_pool); } ufbxi_buf *tmp_buf = &uc->tmp_thread_parse[batch_index]; @@ -15213,7 +15334,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_mesh(ufbxi_context * 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, 'r', 2)); + ufbxi_TextureUV, ufbxi_TextureUVVerticeIndex, NULL, 'r', 2)); mesh->uv_sets.data = set; mesh->uv_sets.count = 1; @@ -15909,11 +16030,7 @@ static ufbxi_noinline void ufbxi_obj_free(ufbxi_context *uc) ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_read_line(ufbxi_context *uc) { - if (uc->obj.eof) { - uc->obj.line.data = "\n"; - uc->obj.line.length = 1; - return 1; - } + ufbxi_dev_assert(!uc->obj.eof); size_t offset = 0; @@ -17078,7 +17195,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context { 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) 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; @@ -17255,6 +17372,8 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context 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) { @@ -17376,7 +17495,8 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context 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) { + 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 @@ -17415,7 +17535,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context } } - } else if (uc->opts.inherit_mode_handling == UFBX_INHERIT_MODE_HANDLING_COMPENSATE) { + } 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; } @@ -17939,7 +18059,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_dst_elements(ufbxi_context } uc->tmp_element_flag[element_id] = 1; } - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &conn->src)); + ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1); + ufbxi_check(p_elem); + *p_elem = conn->src; num_elements++; } } @@ -17975,7 +18097,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_fetch_src_elements(ufbxi_context } uc->tmp_element_flag[element_id] = 1; } - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_element*, 1, &conn->dst)); + ufbx_element **p_elem = ufbxi_push(&uc->tmp_stack, ufbx_element*, 1); + ufbxi_check(p_elem); + *p_elem = conn->dst; num_elements++; } } @@ -18194,13 +18318,6 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_material_textures(ufbxi_con return 1; } -ufbxi_noinline static bool ufbxi_video_ptr_less(void *user, const void *va, const void *vb) -{ - (void)user; - const ufbx_video *a = *(const ufbx_video**)va, *b = *(const ufbx_video**)vb; - return ufbxi_str_less(a->absolute_filename, b->absolute_filename); -} - static ufbxi_noinline bool ufbxi_bone_pose_less(void *user, const void *va, const void *vb) { (void)user; @@ -18208,13 +18325,6 @@ static ufbxi_noinline bool ufbxi_bone_pose_less(void *user, const void *va, cons return a->bone_node->typed_id < b->bone_node->typed_id; } -ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_videos_by_filename(ufbxi_context *uc, ufbx_video **videos, size_t count) -{ - ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, count * sizeof(ufbx_video*))); - ufbxi_stable_sort(sizeof(ufbx_video*), 32, videos, uc->tmp_arr, count, &ufbxi_video_ptr_less, NULL); - return 1; -} - 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; @@ -19380,11 +19490,11 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_shader_texture(ufbxi_co shader->type = type; - static const char *name_props[] = { + static const char *const name_props[] = { "3dsMax|params|OSLShaderName", }; - static const char *source_props[] = { + static const char *const source_props[] = { "3dsMax|params|OSLCode", }; @@ -19996,7 +20106,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_modify_geometry(ufbxi_context *u if (node->is_root) continue; node->geometry_transform = ufbxi_get_geometry_transform(&node->props, node); - if (!ufbxi_is_transform_identity(node->geometry_transform)) { + 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 { @@ -20167,6 +20277,12 @@ ufbxi_noinline static void ufbxi_postprocess_scene(ufbxi_context *uc) } } } + + 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) @@ -20263,6 +20379,84 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_filenames(ufbxi_context 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) { @@ -20281,6 +20475,19 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_validate_indices(ufbxi_context * 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; @@ -20330,6 +20537,14 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_mesh_material(ufbxi_buf 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; @@ -20484,6 +20699,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc 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; } } @@ -20716,6 +20932,10 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc 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); @@ -20815,6 +21035,8 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc 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) { @@ -21014,7 +21236,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc if (material->shader) { material->shader_type = material->shader->type; } else { - if (uc->exporter == UFBX_EXPORTER_BLENDER_BINARY && uc->exporter_version >= ufbx_pack_version(4,12,0)) { + 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; } @@ -21149,39 +21371,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc } } - // HACK: If there are multiple textures in an FBX file that use the same embedded - // texture they get duplicated Video elements instead of a shared one _and only one - // of them has the content?!_ So let's gather all Video instances with content and - // sort them by filename so we can patch the other ones.. - ufbx_video **content_videos = ufbxi_push(&uc->tmp, ufbx_video*, uc->scene.videos.count); - ufbxi_check(content_videos); - - size_t num_content_videos = 0; - 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)); - if (video->content.size > 0) { - content_videos[num_content_videos++] = video; - } - } - - if (num_content_videos > 0) { - ufbxi_check(ufbxi_sort_videos_by_filename(uc, content_videos, num_content_videos)); - - ufbxi_for_ptr_list(ufbx_video, p_video, uc->scene.videos) { - ufbx_video *video = *p_video; - if (video->content.size > 0) continue; - - size_t index = SIZE_MAX; - ufbxi_macro_lower_bound_eq(ufbx_video*, 16, &index, content_videos, 0, num_content_videos, - ( ufbxi_str_less((*a)->absolute_filename, video->absolute_filename) ), - ( (*a)->absolute_filename.data == video->absolute_filename.data )); - if (index != SIZE_MAX) { - video->content = content_videos[index]->content; - } - } - } + ufbxi_check(ufbxi_resolve_file_content(uc)); ufbxi_for_ptr_list(ufbx_texture, p_texture, uc->scene.textures) { ufbx_texture *texture = *p_texture; @@ -21302,6 +21492,11 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc 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)); } @@ -21370,7 +21565,7 @@ static ufbxi_forceinline void ufbxi_mul_scale_real(ufbx_transform *t, ufbx_real t->scale.z *= v; } -static ufbxi_forceinline ufbx_quat ufbxi_mul_quat(ufbx_quat a, ufbx_quat b) +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; @@ -21395,7 +21590,7 @@ static ufbxi_forceinline void ufbxi_add_weighted_quat(ufbx_quat *r, ufbx_quat b, r->w += b.w * w; } -static ufbxi_forceinline void ufbxi_add_weighted_mat(ufbx_matrix *r, const ufbx_matrix *b, ufbx_real 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); @@ -21719,7 +21914,7 @@ ufbxi_noinline static void ufbxi_update_node(ufbx_node *node, const ufbx_transfo node->unscaled_node_to_world = unscaled_node_to_parent; } - if (!ufbxi_is_transform_identity(node->geometry_transform)) { + 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; @@ -21794,7 +21989,7 @@ ufbxi_noinline static void ufbxi_update_camera(ufbx_scene *scene, ufbx_camera *c 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 = (ufbx_real)30.0 * ufbxi_find_real(&camera->props, ufbxi_OrthoZoom, 1.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 }; @@ -22042,7 +22237,7 @@ ufbxi_noinline static void ufbxi_update_material(ufbx_scene *scene, ufbx_materia 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)) { + 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); @@ -22204,7 +22399,7 @@ static ufbxi_forceinline void ufbxi_mirror_matrix_src(ufbx_matrix *m, ufbx_mirro m->cols[ax].z = -m->cols[ax].z; } -static ufbxi_forceinline void ufbxi_mirror_matrix(ufbx_matrix *m, ufbx_mirror_axis axis) +static ufbxi_noinline void ufbxi_mirror_matrix(ufbx_matrix *m, ufbx_mirror_axis axis) { if (axis == 0) return; ufbxi_mirror_matrix_src(m, axis); @@ -22466,7 +22661,10 @@ ufbxi_noinline static void ufbxi_update_adjust_transforms(ufbxi_context *uc, ufb } 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); - node->adjust_post_scale *= 1.0f / scale.x; + 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; } } @@ -22613,6 +22811,19 @@ static ufbxi_noinline void ufbxi_update_scene_settings(ufbx_scene_settings *sett } } +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 @@ -23306,10 +23517,6 @@ static ufbxi_noinline int ufbxi_cache_setup_channels(ufbxi_cache_context *cc) static ufbxi_noinline int ufbxi_cache_load_imp(ufbxi_cache_context *cc, ufbx_string filename) { - // `ufbx_geometry_cache_opts` must be cleared to zero first! - ufbx_assert(cc->opts._begin_zero == 0 && cc->opts._end_zero == 0); - ufbxi_check_err_msg(&cc->error, cc->opts._begin_zero == 0 && cc->opts._end_zero == 0, "Uninitialized options"); - cc->tmp.ator = cc->ator_tmp; cc->tmp_stack.ator = cc->ator_tmp; @@ -23473,14 +23680,15 @@ typedef struct { size_t data_size; } ufbxi_external_file; -static int ufbxi_cmp_external_file(const void *va, const void *vb) +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 ? -1 : 1; + if (a->type != b->type) return a->type < b->type; int cmp = ufbxi_str_cmp(a->filename, b->filename); - if (cmp != 0) return cmp; - if (a->index != b->index) return a->index < b->index ? -1 : 1; - return 0; + 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) @@ -23568,7 +23776,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_external_files(ufbxi_contex // 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); - qsort(files, num_files, sizeof(ufbxi_external_file), &ufbxi_cmp_external_file); + 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; @@ -23641,7 +23849,7 @@ static ufbxi_noinline void ufbxi_transform_to_axes(ufbxi_context *uc, ufbx_coord 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)) { + 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); } @@ -23878,10 +24086,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) { // Check for deferred failure if (uc->deferred_failure) return 0; - - // `ufbx_load_opts` must be cleared to zero first! - ufbx_assert(uc->opts._begin_zero == 0 && uc->opts._end_zero == 0); - ufbxi_check_msg(uc->opts._begin_zero == 0 && uc->opts._end_zero == 0, "Uninitialized options"); ufbxi_check(uc->opts.path_separator >= 0x20 && uc->opts.path_separator <= 0x7e); ufbxi_check(ufbxi_fixup_opts_string(uc, &uc->opts.filename, false)); @@ -23914,6 +24118,8 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) 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)); @@ -23953,6 +24159,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) 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)) { @@ -24308,7 +24517,7 @@ typedef struct ufbxi_anim_layer_combine_ctx { bool has_rotation_order; } ufbxi_anim_layer_combine_ctx; -static double ufbxi_pow_abs(double v, double e) +static ufbxi_noinline double ufbxi_pow_abs(double v, double e) { if (e <= 0.0) return 1.0; if (e >= 1.0) return v; @@ -24640,6 +24849,13 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_element_list(ufbxi_eva 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); @@ -24651,10 +24867,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_translate_anim(ufbxi_eval_contex ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context *ec) { - // `ufbx_evaluate_opts` must be cleared to zero first! - ufbx_assert(ec->opts._begin_zero == 0 && ec->opts._end_zero == 0); - ufbxi_check_err_msg(&ec->error, ec->opts._begin_zero == 0 && ec->opts._end_zero == 0, "Uninitialized options"); - ec->scene = ec->src_scene; size_t num_elements = ec->scene.elements.count; @@ -24724,6 +24936,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context 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); @@ -24780,6 +24993,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context 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) { @@ -24791,14 +25005,8 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context ufbx_material *material = *p_material; material->shader = (ufbx_shader*)ufbxi_translate_element(ec, material->shader); - for (size_t i = 0; i < UFBX_MATERIAL_FBX_MAP_COUNT; i++) { - ufbx_material_map *map = &material->fbx.maps[i]; - map->texture = (ufbx_texture*)ufbxi_translate_element(ec, map->texture); - } - for (size_t i = 0; i < UFBX_MATERIAL_PBR_MAP_COUNT; i++) { - ufbx_material_map *map = &material->pbr.maps[i]; - map->texture = (ufbx_texture*)ufbxi_translate_element(ec, map->texture); - } + 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); @@ -24876,6 +25084,12 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context 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; @@ -25075,19 +25289,21 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_push_anim_string(ufbxi_create_an return 1; } -static int ufbxi_cmp_prop_override_prop_name(const void *va, const void *vb) +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 ? -1 : 1; - return ufbxi_str_cmp(a->prop_name, b->prop_name); + 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 int ufbxi_cmp_prop_override(const void *va, const void *vb) +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 ? -1 : 1; - if (a->_internal_key != b->_internal_key) return a->_internal_key < b->_internal_key ? -1 : 1; - return strcmp(a->prop_name.data, b->prop_name.data); + 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) @@ -25102,10 +25318,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_ani const ufbx_scene *scene = ac->scene; ufbx_anim *anim = &ac->anim; - // `ufbx_anim_opts` must be cleared to zero first! - ufbx_assert(ac->opts._begin_zero == 0 && ac->opts._end_zero == 0); - ufbxi_check_err_msg(&ac->error, ac->opts._begin_zero == 0 && ac->opts._end_zero == 0, "Uninitialized options"); - ufbxi_init_ator(&ac->error, &ac->ator_result, &ac->opts.result_allocator, "result"); ac->result.unordered = true; ac->result.ator = &ac->ator_result; @@ -25159,7 +25371,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_ani // Sort `anim->prop_overrides` first by `prop_name` only so we can deduplicate and // convert them to global strings in `ufbxi_strings[]` if possible. - qsort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_cmp_prop_override_prop_name); + 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 }; @@ -25187,7 +25399,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_create_anim_imp(ufbxi_create_ani } // Sort `anim->prop_overrides` to the actual order expected by evaluation. - qsort(anim->prop_overrides.data, anim->prop_overrides.count, sizeof(ufbx_prop_override), &ufbxi_cmp_prop_override); + 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]; @@ -25230,7 +25442,12 @@ typedef struct { #if UFBXI_FEATURE_ANIMATION_BAKING -UFBX_LIST_TYPE(ufbxi_double_list, double); +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; @@ -25247,7 +25464,7 @@ typedef struct { ufbxi_buf tmp_props; ufbxi_buf tmp_bake_stack; - ufbxi_double_list layer_weight_times; + ufbxi_bake_time_list layer_weight_times; ufbx_baked_node **baked_nodes; bool *nodes_to_bake; @@ -25256,8 +25473,12 @@ typedef struct { 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; @@ -25280,23 +25501,36 @@ static int ufbxi_cmp_bake_prop(const void *va, const void *vb) return a->anim_value < b->anim_value; } -static int ufbxi_cmp_double(const void *va, const void *vb) +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) { - const double a = *(const double*)va; - const double b = *(const double*)vb; - if (a != b) return a < b ? -1 : 1; + 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; } -ufbxi_nodiscard static ufbxi_forceinline int ufbxi_bake_push_time(ufbxi_bake_context *bc, double time) +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) { - double *p_key = ufbxi_push_fast(&bc->tmp_times, double, 1); + ufbxi_bake_time *p_key = ufbxi_push_fast(&bc->tmp_times, ufbxi_bake_time, 1); if (!p_key) return 0; - *p_key = time; + 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) +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; @@ -25309,25 +25543,19 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_times(ufbxi_bake_context *b 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 - bc->opts.time_start_offset; - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, a_time)); + 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 - bc->opts.time_start_offset; + 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) { - double time = b_time - bc->opts.constant_timestep; - if (time >= b_time) time = ufbx_nextafter(time, -UFBX_INFINITY); - if (time <= a_time) time = ufbx_nextafter(a_time, UFBX_INFINITY); - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, time)); + 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) { - double time = a_time + bc->opts.constant_timestep; - if (time <= a_time) time = ufbx_nextafter(time, UFBX_INFINITY); - if (time >= b_time) time = ufbx_nextafter(b_time, -UFBX_INFINITY); - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, time)); + 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; @@ -25343,7 +25571,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_times(ufbxi_bake_context *b 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)); + ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, time, 0)); } } } @@ -25377,34 +25605,67 @@ ufbxi_nodiscard static ufbxi_noinline bool ufbxi_in_list(const char *const *item return false; } -ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_context *bc, ufbxi_double_list *p_dst) +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, double, bc->layer_weight_times.count, bc->layer_weight_times.data)); + 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)); - ufbxi_check_err(&bc->error, ufbxi_bake_push_time(bc, bc->time_end)); + 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; - double *times = ufbxi_push_pop(&bc->tmp_prop, &bc->tmp_times, double, num_times); + 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(double), &ufbxi_cmp_double); + qsort(times, num_times, sizeof(ufbxi_bake_time), &ufbxi_cmp_bake_time_fn); // Deduplicate times - { - size_t dst = 0, src = 0; - while (src < num_times) { - if (src + 1 < num_times && times[src] == times[src + 1]) { - src++; - } else if (dst != src) { - times[dst++] = times[src++]; - } else { - dst++; src++; + 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; @@ -25414,27 +25675,42 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_c 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; - double prev_time = -UFBX_INFINITY; + // 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) { - double src_time = times[src]; + ufbxi_bake_time src_time = times[src]; src++; size_t start_src = src; - double next_time = ufbx_ceil(src_time * sample_rate - epsilon) / sample_rate; - while (src < num_times && times[src] <= next_time + epsilon) { + 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 - prev_time <= min_interval) { + 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 > times[dst - 1]) { + if (dst == 0 || prev_time.time > times[dst - 1].time) { times[dst++] = prev_time; } } @@ -25442,16 +25718,97 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_c 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; } -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_reduce_vec3(ufbxi_bake_context *bc, ufbx_baked_vec3_list *p_dst, bool *p_constant, ufbx_baked_vec3_list src) +#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++) { @@ -25501,10 +25858,39 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_reduce_vec3(ufbxi_bake_cont return 1; } -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_reduce_quat(ufbxi_bake_context *bc, ufbx_baked_quat_list *p_dst, bool *p_constant, ufbx_baked_quat_list src) +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); @@ -25574,6 +25960,38 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_reduce_quat(ufbxi_bake_cont 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_prop *props, size_t count) { ufbx_assert(bc->baked_nodes && bc->nodes_to_bake); @@ -25606,7 +26024,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context } } - ufbxi_double_list times_t, times_r, times_s; + ufbxi_bake_time_list times_t, times_r, times_s; // Translation bool resample_translation = false; @@ -25621,13 +26039,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context if (!scale_helper_t->constant_scale) { resample_translation = true; } - - ufbx_baked_vec3_list scale_keys = scale_helper_t->scale_keys; - double *times = ufbxi_push(&bc->tmp_times, double, scale_keys.count); - ufbxi_check_err(&bc->error, times); - for (size_t i = 0; i < scale_keys.count; i++) { - times[i] = scale_keys.data[i].time; - } + ufbxi_check_err(&bc->error, ufbxi_push_resampled_times(bc, &scale_helper_t->scale_keys)); } else { constant_scale_t = node->parent->scale_helper->inherit_scale; } @@ -25638,13 +26050,14 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context // Literally any transform related property can affect complex translation if (ufbxi_in_list(ufbxi_transform_props, ufbxi_arraycount(ufbxi_transform_props), prop->prop_name)) { bool resample_linear = resample_translation || prop->prop_name != ufbxi_Lcl_Translation; - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear)); + uint32_t key_flag = prop->prop_name == ufbxi_Lcl_Translation ? UFBX_BAKED_KEY_KEYFRAME : 0; + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear, key_flag)); } } } else { ufbxi_for(ufbxi_bake_prop, prop, props, count) { if (prop->prop_name == ufbxi_Lcl_Translation) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_translation)); + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_translation, UFBX_BAKED_KEY_KEYFRAME)); } } } @@ -25656,13 +26069,14 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context ufbxi_for(ufbxi_bake_prop, prop, props, count) { if (ufbxi_in_list(ufbxi_complex_rotation_sources, ufbxi_arraycount(ufbxi_complex_rotation_sources), prop->prop_name)) { bool resample_linear = !bc->opts.no_resample_rotation || prop->prop_name != ufbxi_Lcl_Rotation; - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear)); + uint32_t key_flag = prop->prop_name == ufbxi_Lcl_Rotation ? UFBX_BAKED_KEY_KEYFRAME : 0; + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_linear, key_flag)); } } } else { ufbxi_for(ufbxi_bake_prop, prop, props, count) { if (prop->prop_name == ufbxi_Lcl_Rotation) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, !bc->opts.no_resample_rotation)); + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, !bc->opts.no_resample_rotation, UFBX_BAKED_KEY_KEYFRAME)); } } } @@ -25681,13 +26095,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context if (!scale_helper_s->constant_scale) { resample_scale = true; } - - ufbx_baked_vec3_list scale_keys = scale_helper_s->scale_keys; - double *times = ufbxi_push(&bc->tmp_times, double, scale_keys.count); - ufbxi_check_err(&bc->error, times); - for (size_t i = 0; i < scale_keys.count; i++) { - times[i] = scale_keys.data[i].time; - } + ufbxi_check_err(&bc->error, ufbxi_push_resampled_times(bc, &scale_helper_s->scale_keys)); } else { constant_scale_s = inherit_helper->local_transform.scale; } @@ -25695,7 +26103,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context ufbxi_for(ufbxi_bake_prop, prop, props, count) { if (prop->prop_name == ufbxi_Lcl_Scaling) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_scale)); + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, resample_scale, UFBX_BAKED_KEY_KEYFRAME)); } } ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×_s)); @@ -25718,21 +26126,51 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context size_t ix_t = 0, ix_r = 0, ix_s = 0; while (ix_t < times_t.count || ix_r < times_r.count || ix_s < times_s.count) { - double time = UFBX_INFINITY; - if (ix_t < times_t.count && time > times_t.data[ix_t]) time = times_t.data[ix_t]; - if (ix_r < times_r.count && time > times_r.data[ix_r]) time = times_r.data[ix_r]; - if (ix_s < times_s.count && time > times_s.data[ix_s]) time = times_s.data[ix_s]; + ufbxi_bake_time bake_time = { UFBX_INFINITY }; + uint32_t flags_r = 0, flags_t = 0, flags_s = 0; + + uint32_t flags = 0; + if (ix_r < times_r.count) { + bake_time = times_r.data[ix_r]; + flags_r = bake_time.flags; + bake_time.flags &= 0x7; + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION; + } + if (ix_t < times_t.count) { + ufbxi_bake_time t = times_t.data[ix_t]; + int cmp = ufbxi_cmp_bake_time(t, bake_time); + if (cmp <= 0) { + if (cmp < 0) { + bake_time = t; + flags = 0; + } + bake_time.flags |= t.flags & 0x7; + flags_t = t.flags; + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION; + } + } + if (ix_s < times_s.count) { + ufbxi_bake_time t = times_s.data[ix_s]; + int cmp = ufbxi_cmp_bake_time(t, bake_time); + if (cmp <= 0) { + if (cmp < 0) { + bake_time = t; + flags = 0; + } + bake_time.flags |= t.flags & 0x7; + flags_s = t.flags; + flags |= UFBX_TRANSFORM_FLAG_INCLUDE_SCALE; + } + } - uint32_t flags = UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER|UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE|UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES; - if (ix_t < times_t.count && time == times_t.data[ix_t]) flags |= UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION; - if (ix_r < times_r.count && time == times_r.data[ix_r]) flags |= UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION; - if (ix_s < times_s.count && time == times_s.data[ix_s]) flags |= UFBX_TRANSFORM_FLAG_INCLUDE_SCALE; + flags |= UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER|UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE|UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES; - ufbx_transform transform = ufbx_evaluate_transform_flags(bc->anim, node, time, flags); + double eval_time = ufbxi_bake_time_sample_time(bake_time); + ufbx_transform transform = ufbx_evaluate_transform_flags(bc->anim, node, eval_time, flags); if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION) { if (scale_helper_t) { - ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_t->scale_keys, time); + ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_t->scale_keys, eval_time); transform.translation.x *= scale.x; transform.translation.y *= scale.y; transform.translation.z *= scale.z; @@ -25742,18 +26180,20 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context transform.translation.y *= constant_scale_t.y; transform.translation.z *= constant_scale_t.z; - keys_t.data[ix_t].time = time; + keys_t.data[ix_t].time = bake_time.time; keys_t.data[ix_t].value = transform.translation; + keys_t.data[ix_t].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_t); ix_t++; } if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION) { - keys_r.data[ix_r].time = time; + keys_r.data[ix_r].time = bake_time.time; keys_r.data[ix_r].value = transform.rotation; + keys_r.data[ix_r].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_r); ix_r++; } if (flags & UFBX_TRANSFORM_FLAG_INCLUDE_SCALE) { if (scale_helper_s) { - ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_s->scale_keys, time); + ufbx_vec3 scale = ufbx_evaluate_baked_vec3(scale_helper_s->scale_keys, eval_time); transform.scale.x *= scale.x; transform.scale.y *= scale.y; transform.scale.z *= scale.z; @@ -25763,8 +26203,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context transform.scale.y *= constant_scale_s.y; transform.scale.z *= constant_scale_s.z; - keys_s.data[ix_s].time = time; + keys_s.data[ix_s].time = bake_time.time; keys_s.data[ix_s].value = transform.scale; + keys_s.data[ix_s].flags = (ufbx_baked_key_flags)(bake_time.flags | flags_s); ix_s++; } } @@ -25774,9 +26215,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context baked_node->element_id = node->element_id; baked_node->typed_id = node->typed_id; - ufbxi_check_err(&bc->error, ufbxi_bake_reduce_vec3(bc, &baked_node->translation_keys, &baked_node->constant_translation, keys_t)); - ufbxi_check_err(&bc->error, ufbxi_bake_reduce_quat(bc, &baked_node->rotation_keys, &baked_node->constant_rotation, keys_r)); - ufbxi_check_err(&bc->error, ufbxi_bake_reduce_vec3(bc, &baked_node->scale_keys, &baked_node->constant_scale, keys_s)); + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_node->translation_keys, &baked_node->constant_translation, keys_t)); + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_quat(bc, &baked_node->rotation_keys, &baked_node->constant_rotation, keys_r)); + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_node->scale_keys, &baked_node->constant_scale, keys_s)); bc->baked_nodes[node->typed_id] = baked_node; @@ -25825,10 +26266,10 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node(ufbxi_bake_context *bc ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_prop(ufbxi_bake_context *bc, ufbx_element *element, const char *prop_name, ufbxi_bake_prop *props, size_t count) { ufbxi_for(ufbxi_bake_prop, prop, props, count) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, false)); + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, false, UFBX_BAKED_KEY_KEYFRAME)); } - ufbxi_double_list times; + ufbxi_bake_time_list times; ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, ×)); ufbx_baked_vec3_list keys; @@ -25841,10 +26282,12 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_prop(ufbxi_bake_contex name.length = strlen(prop_name); for (size_t i = 0; i < times.count; i++) { - double time = times.data[i]; - ufbx_prop prop = ufbx_evaluate_prop_len(bc->anim, element, name.data, name.length, time); - keys.data[i].time = time; + ufbxi_bake_time bake_time = times.data[i]; + double eval_time = ufbxi_bake_time_sample_time(bake_time); + ufbx_prop prop = ufbx_evaluate_prop_len(bc->anim, element, name.data, name.length, eval_time); + keys.data[i].time = bake_time.time; keys.data[i].value = prop.value_vec3; + keys.data[i].flags = (ufbx_baked_key_flags)bake_time.flags; } ufbx_baked_prop *baked_prop = ufbxi_push_zero(&bc->tmp_props, ufbx_baked_prop, 1); @@ -25854,7 +26297,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_prop(ufbxi_bake_contex baked_prop->name.data = ufbxi_push_copy(&bc->result, char, baked_prop->name.length + 1, prop_name); ufbxi_check_err(&bc->error, baked_prop->name.data); - ufbxi_check_err(&bc->error, ufbxi_bake_reduce_vec3(bc, &baked_prop->keys, &baked_prop->constant_value, keys)); + ufbxi_check_err(&bc->error, ufbxi_bake_postprocess_vec3(bc, &baked_prop->keys, &baked_prop->constant_value, keys)); ufbxi_buf_clear(&bc->tmp_prop); @@ -25900,12 +26343,22 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_element(ufbxi_bake_context return 1; } -ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc) +static ufbxi_noinline bool ufbxi_baked_node_less(void *user, const void *va, const void *vb) +{ + (void)user; + const ufbx_baked_node *a = (const ufbx_baked_node*)va, *b = (const ufbx_baked_node*)vb; + return a->typed_id < b->typed_id; +} + +static ufbxi_noinline bool ufbxi_baked_element_less(void *user, const void *va, const void *vb) { - // `ufbx_bake_opts` must be cleared to zero first! - ufbx_assert(bc->opts._begin_zero == 0 && bc->opts._end_zero == 0); - ufbxi_check_err_msg(&bc->error, bc->opts._begin_zero == 0 && bc->opts._end_zero == 0, "Uninitialized options"); + (void)user; + const ufbx_baked_element *a = (const ufbx_baked_element*)va, *b = (const ufbx_baked_element*)vb; + return a->element_id < b->element_id; +} +ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc) +{ const ufbx_anim *anim = bc->anim; const ufbx_scene *scene = bc->scene; @@ -25955,17 +26408,17 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc if (prop->prop_name != ufbxi_Weight) continue; ufbx_element *element = scene->elements.data[prop->element_id]; if (element->type == UFBX_ELEMENT_ANIM_LAYER) { - ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, true)); + ufbxi_check_err(&bc->error, ufbxi_bake_times(bc, prop->anim_value, true, 0)); has_weight_times = true; } } if (has_weight_times) { - ufbxi_double_list weight_times = { 0 }; + ufbxi_bake_time_list weight_times = { 0 }; ufbxi_check_err(&bc->error, ufbxi_finalize_bake_times(bc, &weight_times)); bc->layer_weight_times.count = weight_times.count; - bc->layer_weight_times.data = ufbxi_push_copy(&bc->tmp, double, weight_times.count, weight_times.data); + bc->layer_weight_times.data = ufbxi_push_copy(&bc->tmp, ufbxi_bake_time, weight_times.count, weight_times.data); ufbxi_check_err(&bc->error, bc->layer_weight_times.data); ufbxi_buf_clear(&bc->tmp_prop); @@ -25994,6 +26447,20 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim(ufbxi_bake_context *bc bc->bake.elements.data = ufbxi_push_pop(&bc->result, &bc->tmp_elements, ufbx_baked_element, num_elements); ufbxi_check_err(&bc->error, bc->bake.elements.data); + ufbxi_unstable_sort(bc->bake.nodes.data, bc->bake.nodes.count, sizeof(ufbx_baked_node), &ufbxi_baked_node_less, NULL); + ufbxi_unstable_sort(bc->bake.elements.data, bc->bake.elements.count, sizeof(ufbx_baked_element), &ufbxi_baked_element_less, NULL); + + if (bc->time_min < bc->time_max) { + bc->bake.key_time_min = bc->time_min; + bc->bake.key_time_max = bc->time_max; + } + + if (bc->time_begin < bc->time_end) { + bc->bake.playback_time_begin = bc->time_begin; + bc->bake.playback_time_end = bc->time_end; + bc->bake.playback_duration = bc->time_end - bc->time_begin; + } + return 1; } @@ -26004,7 +26471,10 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_imp(ufbxi_bake_context if (bc->opts.max_keyframe_segments == 0) bc->opts.max_keyframe_segments = 32; if (bc->opts.key_reduction_threshold == 0) bc->opts.key_reduction_threshold = 0.000001; if (bc->opts.key_reduction_passes == 0) bc->opts.key_reduction_passes = 4; - if (bc->opts.constant_timestep <= 0.0) bc->opts.constant_timestep = 0.0; + + if (bc->opts.trim_start_time && anim->time_begin > 0.0) { + bc->ktime_offset = -anim->time_begin * (double)bc->scene->metadata.ktime_second; + } ufbxi_init_ator(&bc->error, &bc->ator_tmp, &bc->opts.temp_allocator, "temp"); ufbxi_init_ator(&bc->error, &bc->ator_result, &bc->opts.result_allocator, "result"); @@ -26027,8 +26497,12 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_imp(ufbxi_bake_context bc->tmp_bake_stack.ator = &bc->ator_tmp; bc->anim = anim; - bc->time_begin = anim->time_begin; - bc->time_end = anim->time_end; + if (anim->time_begin < anim->time_end) { + bc->time_begin = anim->time_begin; + bc->time_end = anim->time_end; + } + bc->time_min = UFBX_INFINITY; + bc->time_max = -UFBX_INFINITY; bc->imp = ufbxi_push(&bc->result, ufbxi_baked_anim_imp, 1); ufbxi_check_err(&bc->error, bc->imp); @@ -26037,10 +26511,16 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_imp(ufbxi_bake_context ufbxi_init_ref(&bc->imp->refcount, UFBXI_BAKED_ANIM_IMP_MAGIC, NULL); + bc->bake.metadata.result_memory_used = bc->ator_result.current_size; + bc->bake.metadata.temp_memory_used = bc->ator_tmp.current_size; + bc->bake.metadata.result_allocs = bc->ator_result.num_allocs; + bc->bake.metadata.temp_allocs = bc->ator_tmp.num_allocs; + bc->imp->magic = UFBXI_BAKED_ANIM_IMP_MAGIC; bc->imp->bake = bc->bake; bc->imp->refcount.ator = bc->ator_result; bc->imp->refcount.buf = bc->result; + return 1; } @@ -26119,10 +26599,6 @@ typedef struct { ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_curve_imp(ufbxi_tessellate_curve_context *tc) { - // `ufbx_tessellate_opts` must be cleared to zero first! - ufbx_assert(tc->opts._begin_zero == 0 && tc->opts._end_zero == 0); - ufbxi_check_err_msg(&tc->error, tc->opts._begin_zero == 0 && tc->opts._end_zero == 0, "Uninitialized options"); - if (tc->opts.span_subdivision <= 0) { tc->opts.span_subdivision = 4; } @@ -26216,10 +26692,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_curve_imp(ufbxi ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_surface_imp(ufbxi_tessellate_surface_context *tc) { - // `ufbx_tessellate_opts` must be cleared to zero first! - ufbx_assert(tc->opts._begin_zero == 0 && tc->opts._end_zero == 0); - ufbxi_check_err_msg(&tc->error, tc->opts._begin_zero == 0 && tc->opts._end_zero == 0, "Uninitialized options"); - if (tc->opts.span_subdivision_u <= 0) { tc->opts.span_subdivision_u = 4; } @@ -26534,7 +27006,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_surface_imp(ufb typedef struct { ufbx_real split; - uint32_t index; + uint32_t index_plus_one; // 0 for empty uint32_t slow_left; uint32_t slow_right; uint32_t slow_end; @@ -26559,8 +27031,10 @@ typedef struct { uint32_t indices[3]; } ufbxi_kd_triangle; -ufbxi_forceinline static ufbx_vec2 ufbxi_ngon_project(ufbxi_ngon_context *nc, ufbx_vec3 point) +ufbxi_noinline static ufbx_vec2 ufbxi_ngon_project(ufbxi_ngon_context *nc, uint32_t index) { + ufbx_vec3 point = nc->positions.values.data[nc->positions.indices.data[nc->face.index_begin + index]]; + ufbx_vec2 p; p.x = ufbxi_dot3(nc->axes[0], point); p.y = ufbxi_dot3(nc->axes[1], point); @@ -26572,10 +27046,10 @@ ufbxi_forceinline static ufbx_real ufbxi_orient2d(ufbx_vec2 a, ufbx_vec2 b, ufbx return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); } -ufbxi_forceinline static bool ufbxi_kd_check_point(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t index, ufbx_vec3 point) +ufbxi_noinline static bool ufbxi_kd_check_point(ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t index) { if (index == tri->indices[0] || index == tri->indices[1] || index == tri->indices[2]) return false; - ufbx_vec2 p = ufbxi_ngon_project(nc, point); + ufbx_vec2 p = ufbxi_ngon_project(nc, index); ufbx_real u = ufbxi_orient2d(p, tri->points[0], tri->points[1]); ufbx_real v = ufbxi_orient2d(p, tri->points[1], tri->points[2]); @@ -26606,7 +27080,7 @@ ufbxi_noinline static bool ufbxi_kd_check_slow(ufbxi_ngon_context *nc, const ufb bool hit_right = tri->max_t[axis] >= split; if (hit_left && hit_right) { - if (ufbxi_kd_check_point(nc, tri, index, point)) { + if (ufbxi_kd_check_point(nc, tri, index)) { return true; } @@ -26632,11 +27106,9 @@ ufbxi_noinline static bool ufbxi_kd_check_fast(ufbxi_ngon_context *nc, const ufb ufbxi_recursive_function(bool, ufbxi_kd_check_fast, (nc, tri, kd_index, axis, depth), UFBXI_KD_FAST_DEPTH, (ufbxi_ngon_context *nc, const ufbxi_kd_triangle *tri, uint32_t kd_index, uint32_t axis, uint32_t depth)) { - ufbx_vertex_vec3 pos = nc->positions; - for (;;) { ufbxi_kd_node node = nc->kd_nodes[kd_index]; - if (node.slow_end == 0) return false; + if (node.index_plus_one == 0) return false; bool hit_left = tri->min_t[axis] <= node.split; bool hit_right = tri->max_t[axis] >= node.split; @@ -26646,8 +27118,8 @@ ufbxi_noinline static bool ufbxi_kd_check_fast(ufbxi_ngon_context *nc, const ufb if (hit_left && hit_right) { // Check for the point on the split plane - ufbx_vec3 point = pos.values.data[pos.indices.data[nc->face.index_begin + node.index]]; - if (ufbxi_kd_check_point(nc, tri, node.index, point)) { + uint32_t index = node.index_plus_one - 1; + if (ufbxi_kd_check_point(nc, tri, index)) { return true; } @@ -26677,6 +27149,22 @@ ufbxi_noinline static bool ufbxi_kd_check_fast(ufbxi_ngon_context *nc, const ufb } } +ufbxi_noinline static bool ufbxi_kd_check(ufbxi_ngon_context *nc, const ufbx_vec2 *points, const uint32_t *indices) +{ + ufbxi_kd_triangle tri; // ufbxi_uninit + tri.points[0] = points[0]; + tri.points[1] = points[1]; + tri.points[2] = points[2]; + tri.indices[0] = indices[0]; + tri.indices[1] = indices[1]; + tri.indices[2] = indices[2]; + tri.min_t[0] = ufbxi_min_real(ufbxi_min_real(points[0].x, points[1].x), points[2].x); + tri.min_t[1] = ufbxi_min_real(ufbxi_min_real(points[0].y, points[1].y), points[2].y); + tri.max_t[0] = ufbxi_max_real(ufbxi_max_real(points[0].x, points[1].x), points[2].x); + tri.max_t[1] = ufbxi_max_real(ufbxi_max_real(points[0].y, points[1].y), points[2].y); + return ufbxi_kd_check_fast(nc, &tri, 0, 0, 0); +} + ufbxi_noinline static bool ufbxi_kd_index_less(void *user, const void *va, const void *vb) { ufbxi_ngon_context *nc = (ufbxi_ngon_context*)user; @@ -26716,7 +27204,7 @@ ufbxi_noinline static void ufbxi_kd_build(ufbxi_ngon_context *nc, uint32_t *indi ufbxi_kd_node *kd = &nc->kd_nodes[fast_index]; kd->split = ufbxi_dot3(axis_dir, pos.values.data[pos.indices.data[face.index_begin + index]]); - kd->index = index; + kd->index_plus_one = index + 1; if (depth + 1 == UFBXI_KD_FAST_DEPTH) { kd->slow_left = (uint32_t)(indices - nc->kd_indices); @@ -26743,9 +27231,25 @@ ufbxi_noinline static void ufbxi_kd_build(ufbxi_ngon_context *nc, uint32_t *indi #if UFBXI_FEATURE_TRIANGULATION +ufbxi_noinline static ufbx_real ufbxi_ngon_tri_weight(const ufbx_vec2 *points) +{ + ufbx_vec2 p0 = points[0], p1 = points[1], p2 = points[2]; + ufbx_real orient = ufbxi_orient2d(p0, p1, p2); + if (orient <= 0.0f) return -1.0f; + + ufbx_real a = ufbxi_distsq2(p0, p1); + ufbx_real b = ufbxi_distsq2(p1, p2); + ufbx_real c = ufbxi_distsq2(p2, p0); + ufbx_real ab = (a + b - c) / (ufbx_real)ufbx_sqrt(4.0f * a * b); + ufbx_real bc = (b + c - a) / (ufbx_real)ufbx_sqrt(4.0f * b * c); + ufbx_real ca = (c + a - b) / (ufbx_real)ufbx_sqrt(4.0f * c * a); + return (ufbx_real)ufbx_fmax(UFBX_EPSILON, 2.0f - ufbx_fmax(ufbx_fmax(ab, bc), ca)); +} + ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, uint32_t *indices, uint32_t num_indices) { ufbx_face face = nc->face; + ufbx_assert(face.num_indices > 4); // Form an orthonormal basis to project the polygon into a 2D plane ufbx_vec3 normal = ufbx_get_weighted_face_normal(&nc->positions, face); @@ -26776,18 +27280,17 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui nc->kd_indices = kd_indices; uint32_t *kd_tmp = indices + face.num_indices; - ufbx_vertex_vec3 pos = nc->positions; // Collect all the reflex corners for intersection testing. uint32_t num_kd_indices = 0; { - ufbx_vec2 a = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + face.num_indices - 1]]); - ufbx_vec2 b = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + 0]]); + ufbx_vec2 a = ufbxi_ngon_project(nc, face.num_indices - 1); + ufbx_vec2 b = ufbxi_ngon_project(nc, 0); for (uint32_t i = 0; i < face.num_indices; i++) { uint32_t next = i + 1 < face.num_indices ? i + 1 : 0; - ufbx_vec2 c = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + next]]); + ufbx_vec2 c = ufbxi_ngon_project(nc, next); - if (ufbxi_orient2d(a, b, c) < 0.0f) { + if (ufbxi_orient2d(a, b, c) <= 0.0f) { kd_indices[num_kd_indices++] = i; } @@ -26796,7 +27299,7 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui } } - // Build a KD-tree out of the collected reflex vertices. + // Build a KD-tree of the vertices. uint32_t num_skip_indices = (1u << (UFBXI_KD_FAST_DEPTH + 1)) - 1; uint32_t kd_slow_indices = num_kd_indices > num_skip_indices ? num_kd_indices - num_skip_indices : 0; ufbxi_ignore(kd_slow_indices); @@ -26822,73 +27325,64 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui // to iterate the polygon once if we move backwards one step every time we clip an ear. uint32_t indices_left = face.num_indices; { - ufbxi_kd_triangle tri; // ufbxi_uninit - - uint32_t ix = 1; - ufbx_vec2 a = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + 0]]); - ufbx_vec2 b = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + 1]]); - ufbx_vec2 c = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + 2]]); - bool prev_was_reflex = false; + uint32_t point_indices[4] = { 0, 1, 2, 3 }; + ufbx_real weights[2]; // ufbxi_uninit + ufbx_vec2 points[4]; // ufbxi_uninit uint32_t num_steps = 0; - while (indices_left > 3) { - uint32_t prev = edges[ix*2 + 0]; - uint32_t next = edges[ix*2 + 1]; - if (ufbxi_orient2d(a, b, c) > 0.0f) { - - tri.points[0] = a; - tri.points[1] = b; - tri.points[2] = c; - tri.indices[0] = prev; - tri.indices[1] = ix; - tri.indices[2] = next; - tri.min_t[0] = ufbxi_min_real(ufbxi_min_real(a.x, b.x), c.x); - tri.min_t[1] = ufbxi_min_real(ufbxi_min_real(a.y, b.y), c.y); - tri.max_t[0] = ufbxi_max_real(ufbxi_max_real(a.x, b.x), c.x); - tri.max_t[1] = ufbxi_max_real(ufbxi_max_real(a.y, b.y), c.y); + points[0] = ufbxi_ngon_project(nc, point_indices[0]); + points[1] = ufbxi_ngon_project(nc, point_indices[1]); + points[2] = ufbxi_ngon_project(nc, point_indices[2]); + points[3] = ufbxi_ngon_project(nc, point_indices[3]); + + weights[0] = ufbxi_ngon_tri_weight(points + 0); + weights[1] = ufbxi_ngon_tri_weight(points + 1); + + uint32_t first_side = weights[1] > weights[0] ? 1 : 0; + bool clipped = false; + ufbxi_nounroll for (uint32_t side_ix = 0; side_ix < 2; side_ix++) { + uint32_t side = side_ix ^ first_side; + if (!(weights[side] >= 0.0f)) break; // If there is no reflex angle contained within the triangle formed // by `{ a, b, c }` connect the vertices `a - c` (prev, next) directly. - if (!ufbxi_kd_check_fast(nc, &tri, 0, 0, 0)) { + if (!ufbxi_kd_check(nc, points + side, point_indices + side)) { + uint32_t ia = point_indices[side + 0]; + uint32_t ib = point_indices[side + 1]; + uint32_t ic = point_indices[side + 2]; // Mark as clipped - edges[ix*2 + 0] |= 0x80000000; - edges[ix*2 + 1] |= 0x80000000; + edges[ib*2 + 0] |= 0x80000000; + edges[ib*2 + 1] |= 0x80000000; - edges[next*2 + 0] = prev; - edges[prev*2 + 1] = next; + edges[ic*2 + 0] = ia; + edges[ia*2 + 1] = ic; indices_left -= 1; - // Move backwards only if the previous was reflex as now it would - // cover a superset of the previous area, failing the intersection test. - if (prev_was_reflex) { - prev_was_reflex = false; - ix = prev; - b = a; - uint32_t prev_prev = edges[prev*2 + 0]; - a = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + prev_prev]]); + // TODO: This may cause O(n^2) behavior! + num_steps = 0; + + if (side == 1) { + point_indices[2] = point_indices[3]; + point_indices[3] = edges[point_indices[3]*2 + 1]; } else { - ix = next; - b = c; - uint32_t next_next = edges[next*2 + 1]; - c = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + next_next]]); + point_indices[1] = point_indices[0]; + point_indices[0] = edges[point_indices[0]*2 + 0]; } - continue; - } - prev_was_reflex = false; - } else { - prev_was_reflex = true; + clipped = true; + break; + } } + if (clipped) continue; // Continue forward - ix = next; - a = b; - b = c; - next = edges[next*2 + 1]; - c = ufbxi_ngon_project(nc, pos.values.data[pos.indices.data[face.index_begin + next]]); + point_indices[0] = point_indices[1]; + point_indices[1] = point_indices[2]; + point_indices[2] = point_indices[3]; + point_indices[3] = edges[point_indices[3]*2 + 1]; num_steps++; // If we have walked around the entire polygon it is irregular and @@ -26899,6 +27393,7 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui // Fallback: Cut non-ears until the polygon is completed. // TODO: Could do something better here.. + uint32_t ix = point_indices[1]; while (indices_left > 3) { uint32_t prev = edges[ix*2 + 0]; uint32_t next = edges[ix*2 + 1]; @@ -27155,20 +27650,6 @@ typedef struct { } ufbxi_subdivide_context; -static int ufbxi_subdivide_sum_real(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) -{ - (void)user; - ufbx_real dst = 0.0f; - ufbxi_nounroll for (size_t i = 0; i != num_inputs; i++) { - ufbx_real src = *(const ufbx_real*)inputs[i].data; - ufbx_real weight = inputs[i].weight; - dst += src * weight; - } - *(ufbx_real*)output = dst; - - return 1; -} - static int ufbxi_subdivide_sum_vec2(void *user, void *output, const ufbxi_subdivide_input *inputs, size_t num_inputs) { (void)user; @@ -27260,8 +27741,8 @@ static int ufbxi_subdivide_sum_vertex_weights(void *user, void *output, const uf qsort(tmp_weights, num_weights, sizeof(ufbx_subdivision_weight), ufbxi_cmp_subdivision_weight); - if (num_weights > sc->max_vertex_weights) { - num_weights = sc->max_vertex_weights; + if (sc->max_vertex_weights != SIZE_MAX) { + num_weights = ufbxi_min_sz(sc->max_vertex_weights, num_weights); // Normalize weights ufbx_real prefix_weight = 0.0f; @@ -27284,8 +27765,8 @@ static int ufbxi_subdivide_sum_vertex_weights(void *user, void *output, const uf return 1; } -static ufbxi_subdivide_sum_fn *ufbxi_real_sum_fns[] = { - &ufbxi_subdivide_sum_real, +static ufbxi_subdivide_sum_fn *const ufbxi_real_sum_fns[] = { + NULL, &ufbxi_subdivide_sum_vec2, &ufbxi_subdivide_sum_vec3, &ufbxi_subdivide_sum_vec4, @@ -27745,7 +28226,7 @@ static ufbxi_noinline int ufbxi_subdivide_attrib(ufbxi_subdivide_context *sc, uf { if (!attrib->exists) return 1; - ufbx_assert(attrib->value_reals >= 1 && attrib->value_reals <= 4); + ufbx_assert(attrib->value_reals >= 2 && attrib->value_reals <= 4); ufbxi_subdivide_layer_input input; // ufbxi_uninit input.sum_fn = ufbxi_real_sum_fns[attrib->value_reals - 1]; @@ -28206,10 +28687,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_level(ufbxi_subdi ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_imp(ufbxi_subdivide_context *sc, size_t level) { - // `ufbx_subdivide_opts` must be cleared to zero first! - ufbx_assert(sc->opts._begin_zero == 0 && sc->opts._end_zero == 0); - ufbxi_check_err_msg(&sc->error, sc->opts._begin_zero == 0 && sc->opts._end_zero == 0, "Uninitialized options"); - if (sc->opts.boundary == UFBX_SUBDIVISION_BOUNDARY_DEFAULT) { sc->opts.boundary = sc->src_mesh.subdivision_boundary; } @@ -28246,8 +28723,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_imp(ufbxi_subdivi ufbxi_buf_free(&sc->tmp); ufbx_mesh *mesh = &sc->dst_mesh; - memset(&mesh->vertex_normal, 0, sizeof(mesh->vertex_normal)); - memset(&mesh->skinned_normal, 0, sizeof(mesh->skinned_normal)); // Subdivision always results in a mesh that consists only of quads mesh->max_face_triangles = 2; @@ -28255,6 +28730,11 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_subdivide_mesh_imp(ufbxi_subdivi mesh->num_point_faces = 0; mesh->num_line_faces = 0; + if (!sc->opts.interpolate_normals) { + memset(&mesh->vertex_normal, 0, sizeof(mesh->vertex_normal)); + memset(&mesh->skinned_normal, 0, sizeof(mesh->skinned_normal)); + } + if (!sc->opts.interpolate_normals && !sc->opts.ignore_normals) { ufbx_topo_edge *topo = ufbxi_push(&sc->tmp, ufbx_topo_edge, mesh->num_indices); @@ -28581,36 +29061,66 @@ static ufbxi_noinline void ufbxi_release_ref(ufbxi_refcount *refcount) } } +static ufbxi_noinline void *ufbxi_uninitialized_options(ufbx_error *p_error) +{ + if (p_error) { + memset(p_error, 0, sizeof(ufbx_error)); + p_error->type = UFBX_ERROR_UNINITIALIZED_OPTIONS; + p_error->description.data = "Uninitialized options"; + p_error->description.length = strlen("Uninitialized options"); + } + return NULL; +} + +#define ufbxi_check_opts_ptr(m_type, m_opts, m_error) do { if (m_opts) { \ + uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ + ufbx_assert(opts_cleared_to_zero == 0); \ + if (opts_cleared_to_zero != 0) return (m_type*)ufbxi_uninitialized_options(m_error); \ + } } while (0) + +#define ufbxi_check_opts_return(m_value, m_opts, m_error) do { if (m_opts) { \ + uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ + ufbx_assert(opts_cleared_to_zero == 0); \ + if (opts_cleared_to_zero != 0) { \ + ufbxi_uninitialized_options(m_error); \ + return m_value; \ + } \ + } } while (0) + +#define ufbxi_check_opts_return_no_error(m_value, m_opts) do { if (m_opts) { \ + uint32_t opts_cleared_to_zero = m_opts->_begin_zero | m_opts->_end_zero; \ + if (opts_cleared_to_zero != 0) return m_value; \ + } } while (0) + // -- API #ifdef __cplusplus extern "C" { #endif -const ufbx_string ufbx_empty_string = { ufbxi_empty_char, 0 }; -const ufbx_blob ufbx_empty_blob = { NULL, 0 }; -const ufbx_matrix ufbx_identity_matrix = { 1,0,0, 0,1,0, 0,0,1, 0,0,0 }; -const ufbx_transform ufbx_identity_transform = { {0,0,0}, {0,0,0,1}, {1,1,1} }; -const ufbx_vec2 ufbx_zero_vec2 = { 0,0 }; -const ufbx_vec3 ufbx_zero_vec3 = { 0,0,0 }; -const ufbx_vec4 ufbx_zero_vec4 = { 0,0,0,0 }; -const ufbx_quat ufbx_identity_quat = { 0,0,0,1 }; +ufbx_abi_data_def const ufbx_string ufbx_empty_string = { ufbxi_empty_char, 0 }; +ufbx_abi_data_def const ufbx_blob ufbx_empty_blob = { NULL, 0 }; +ufbx_abi_data_def const ufbx_matrix ufbx_identity_matrix = { 1,0,0, 0,1,0, 0,0,1, 0,0,0 }; +ufbx_abi_data_def const ufbx_transform ufbx_identity_transform = { {0,0,0}, {0,0,0,1}, {1,1,1} }; +ufbx_abi_data_def const ufbx_vec2 ufbx_zero_vec2 = { 0,0 }; +ufbx_abi_data_def const ufbx_vec3 ufbx_zero_vec3 = { 0,0,0 }; +ufbx_abi_data_def const ufbx_vec4 ufbx_zero_vec4 = { 0,0,0,0 }; +ufbx_abi_data_def const ufbx_quat ufbx_identity_quat = { 0,0,0,1 }; -const ufbx_coordinate_axes ufbx_axes_right_handed_y_up = { +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_right_handed_y_up = { UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Y, UFBX_COORDINATE_AXIS_POSITIVE_Z, }; -const ufbx_coordinate_axes ufbx_axes_right_handed_z_up = { +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_right_handed_z_up = { UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_NEGATIVE_Y, }; -const ufbx_coordinate_axes ufbx_axes_left_handed_y_up = { +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_left_handed_y_up = { UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Y, UFBX_COORDINATE_AXIS_NEGATIVE_Z, }; -const ufbx_coordinate_axes ufbx_axes_left_handed_z_up = { +ufbx_abi_data_def const ufbx_coordinate_axes ufbx_axes_left_handed_z_up = { UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_POSITIVE_Y, }; - -const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT] = { +ufbx_abi_data_def const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT] = { sizeof(ufbx_unknown), sizeof(ufbx_node), sizeof(ufbx_mesh), @@ -28649,6 +29159,8 @@ const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT] = { sizeof(ufbx_selection_node), sizeof(ufbx_character), sizeof(ufbx_constraint), + sizeof(ufbx_audio_layer), + sizeof(ufbx_audio_clip), sizeof(ufbx_pose), sizeof(ufbx_metadata_object), }; @@ -28736,6 +29248,7 @@ ufbx_abi bool ufbx_is_thread_safe(void) ufbx_abi ufbx_scene *ufbx_load_memory(const void *data, size_t size, const ufbx_load_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_scene, opts, error); ufbxi_context uc = { UFBX_ERROR_NONE }; uc.data_begin = uc.data = (const char *)data; uc.data_size = size; @@ -28750,6 +29263,7 @@ ufbx_abi ufbx_scene *ufbx_load_file(const char *filename, const ufbx_load_opts * ufbx_abi ufbx_scene *ufbx_load_file_len(const char *filename, size_t filename_len, const ufbx_load_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_scene, opts, error); ufbx_load_opts opts_copy; if (opts) { opts_copy = *opts; @@ -28795,6 +29309,7 @@ ufbx_abi ufbx_scene *ufbx_load_stdio(void *file_void, const ufbx_load_opts *opts ufbx_abi ufbx_scene *ufbx_load_stdio_prefix(void *file_void, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_scene, opts, error); FILE *file = (FILE*)file_void; ufbxi_context uc = { UFBX_ERROR_NONE }; @@ -28834,6 +29349,7 @@ ufbx_abi ufbx_scene *ufbx_load_stream(const ufbx_stream *stream, const ufbx_load ufbx_abi ufbx_scene *ufbx_load_stream_prefix(const ufbx_stream *stream, const void *prefix, size_t prefix_size, const ufbx_load_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_scene, opts, error); ufbxi_context uc = { UFBX_ERROR_NONE }; uc.data_begin = uc.data = (const char *)prefix; uc.data_size = prefix_size; @@ -29171,7 +29687,7 @@ ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time return curve->keyframes.data[curve->keyframes.count - 1].value; } -ufbx_abi ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time) +ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time) { if (!anim_value) { return 0.0f; @@ -29182,19 +29698,6 @@ ufbx_abi ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_val return res; } -ufbx_abi ufbxi_noinline ufbx_vec2 ufbx_evaluate_anim_value_vec2(const ufbx_anim_value *anim_value, double time) -{ - if (!anim_value) { - ufbx_vec2 zero = { 0.0f }; - return zero; - } - - ufbx_vec2 res = { anim_value->default_value.x, anim_value->default_value.y }; - if (anim_value->curves[0]) res.x = ufbx_evaluate_curve(anim_value->curves[0], time, res.x); - if (anim_value->curves[1]) res.y = ufbx_evaluate_curve(anim_value->curves[1], time, res.y); - return res; -} - ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time) { if (!anim_value) { @@ -29418,6 +29921,7 @@ ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_ ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_scene, opts, error); #if UFBXI_FEATURE_SCENE_EVALUATION ufbxi_eval_context ec = { 0 }; return ufbxi_evaluate_scene(&ec, (ufbx_scene*)scene, anim, time, opts, error); @@ -29433,6 +29937,7 @@ ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_ani ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_anim, opts, error); ufbx_assert(scene); ufbxi_create_anim_context ac = { UFBX_ERROR_NONE }; @@ -29483,6 +29988,7 @@ ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_ani { ufbx_assert(scene); #if UFBXI_FEATURE_ANIMATION_BAKING + ufbxi_check_opts_ptr(ufbx_baked_anim, opts, error); if (!anim) { anim = scene->anim; } @@ -29547,6 +30053,35 @@ ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake) ufbxi_release_ref(&imp->refcount); } + +ufbx_abi ufbx_baked_node *ufbx_find_baked_node_by_typed_id(ufbx_baked_anim *bake, uint32_t typed_id) +{ + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_baked_node, 8, &index, bake->nodes.data, 0, bake->nodes.count, + ( a->typed_id < typed_id ), ( a->typed_id == typed_id) ); + return index < SIZE_MAX ? &bake->nodes.data[index] : NULL; +} + +ufbx_abi ufbx_baked_node *ufbx_find_baked_node(ufbx_baked_anim *bake, ufbx_node *node) +{ + if (!bake || !node) return NULL; + return ufbx_find_baked_node_by_typed_id(bake, node->typed_id); +} + +ufbx_abi ufbx_baked_element *ufbx_find_baked_element_by_element_id(ufbx_baked_anim *bake, uint32_t element_id) +{ + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_baked_element, 8, &index, bake->elements.data, 0, bake->elements.count, + ( a->element_id < element_id ), ( a->element_id == element_id) ); + return index < SIZE_MAX ? &bake->elements.data[index] : NULL; +} + +ufbx_abi ufbx_baked_element *ufbx_find_baked_element(ufbx_baked_anim *bake, ufbx_element *element) +{ + if (!bake || !element) return NULL; + return ufbx_find_baked_element_by_element_id(bake, element->element_id); +} + ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time) { size_t begin = 0; @@ -29568,7 +30103,11 @@ ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, doub if (begin == 0) return next->value; const ufbx_baked_vec3 *prev = next - 1; + if (prev > keys && (prev->flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && prev[-1].time == time) prev--; + if (time == prev->time) return prev->value; double t = (time - prev->time) / (next->time - prev->time); + if (prev->flags & UFBX_BAKED_KEY_STEP_LEFT) t = 0.0; + if (next->flags & UFBX_BAKED_KEY_STEP_RIGHT) t = 1.0; return ufbxi_lerp3(prev->value, next->value, (ufbx_real)t); } @@ -29596,7 +30135,12 @@ ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, doub if (begin == 0) return next->value; const ufbx_baked_quat *prev = next - 1; + if (prev > keys && prev[-1].time == time) prev--; + if (time == prev->time) return prev->value; double t = (time - prev->time) / (next->time - prev->time); + if (prev > keys && (prev->flags & UFBX_BAKED_KEY_STEP_RIGHT) != 0 && prev[-1].time == time) prev--; + if (prev->flags & UFBX_BAKED_KEY_STEP_LEFT) t = 0.0; + if (next->flags & UFBX_BAKED_KEY_STEP_RIGHT) t = 1.0; return ufbx_quat_slerp(prev->value, next->value, (ufbx_real)t); } @@ -29695,6 +30239,11 @@ ufbx_abi ufbx_quat ufbx_quat_mul(ufbx_quat a, ufbx_quat b) return ufbxi_mul_quat(a, b); } +ufbx_abi ufbx_vec3 ufbx_vec3_normalize(ufbx_vec3 v) +{ + return ufbxi_normalize3(v); +} + ufbx_abi ufbxi_noinline ufbx_real ufbx_quat_dot(ufbx_quat a, ufbx_quat b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; @@ -29980,24 +30529,18 @@ ufbx_abi ufbx_matrix ufbx_matrix_invert(const ufbx_matrix *m) ufbx_abi ufbxi_noinline ufbx_matrix ufbx_matrix_for_normals(const ufbx_matrix *m) { ufbx_real det = ufbx_matrix_determinant(m); + ufbx_real det_sign = det >= 0.0f ? 1.0f : -1.0f; ufbx_matrix r; - if (ufbx_fabs(det) <= UFBX_EPSILON) { - memset(&r, 0, sizeof(r)); - return r; - } - - ufbx_real rcp_det = 1.0f / det; - - r.m00 = ( - m->m12*m->m21 + m->m11*m->m22) * rcp_det; - r.m01 = ( + m->m12*m->m20 - m->m10*m->m22) * rcp_det; - r.m02 = ( - m->m11*m->m20 + m->m10*m->m21) * rcp_det; - r.m10 = ( + m->m02*m->m21 - m->m01*m->m22) * rcp_det; - r.m11 = ( - m->m02*m->m20 + m->m00*m->m22) * rcp_det; - r.m12 = ( + m->m01*m->m20 - m->m00*m->m21) * rcp_det; - r.m20 = ( - m->m02*m->m11 + m->m01*m->m12) * rcp_det; - r.m21 = ( + m->m02*m->m10 - m->m00*m->m12) * rcp_det; - r.m22 = ( - m->m01*m->m10 + m->m00*m->m11) * rcp_det; + r.m00 = ( - m->m12*m->m21 + m->m11*m->m22) * det_sign; + r.m01 = ( + m->m12*m->m20 - m->m10*m->m22) * det_sign; + r.m02 = ( - m->m11*m->m20 + m->m10*m->m21) * det_sign; + r.m10 = ( + m->m02*m->m21 - m->m01*m->m22) * det_sign; + r.m11 = ( - m->m02*m->m20 + m->m00*m->m22) * det_sign; + r.m12 = ( + m->m01*m->m20 - m->m00*m->m21) * det_sign; + r.m20 = ( - m->m02*m->m11 + m->m01*m->m12) * det_sign; + r.m21 = ( + m->m02*m->m10 - m->m00*m->m12) * det_sign; + r.m22 = ( - m->m01*m->m10 + m->m00*m->m11) * det_sign; r.m03 = r.m13 = r.m23 = 0.0f; return r; @@ -30479,6 +31022,7 @@ ufbx_abi ufbxi_noinline ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufb ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *curve, const ufbx_tessellate_curve_opts *opts, ufbx_error *error) { #if UFBXI_FEATURE_TESSELLATION + ufbxi_check_opts_ptr(ufbx_line_curve, opts, error); ufbx_assert(curve); if (!curve) return NULL; @@ -30518,6 +31062,7 @@ ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surf { #if UFBXI_FEATURE_TESSELLATION ufbx_assert(surface); + ufbxi_check_opts_ptr(ufbx_mesh, opts, error); if (!surface) return NULL; ufbxi_tessellate_surface_context tc = { UFBX_ERROR_NONE }; @@ -30703,45 +31248,23 @@ ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic if (face.num_indices < 3) { return ufbx_zero_vec3; } else if (face.num_indices == 3) { - ufbx_vec3 a, b, c; - if (panic) { - a = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 0); - b = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 1); - c = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 2); - } else { - a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); - b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); - c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); - } + ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); + ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); + ufbx_vec3 c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); return ufbxi_cross3(ufbxi_sub3(b, a), ufbxi_sub3(c, a)); } else if (face.num_indices == 4) { - ufbx_vec3 a, b, c, d; - if (panic) { - a = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 0); - b = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 1); - c = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 2); - d = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + 3); - } else { - a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); - b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); - c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); - d = ufbx_get_vertex_vec3(positions, face.index_begin + 3); - } + ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + 0); + ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + 1); + ufbx_vec3 c = ufbx_get_vertex_vec3(positions, face.index_begin + 2); + ufbx_vec3 d = ufbx_get_vertex_vec3(positions, face.index_begin + 3); return ufbxi_cross3(ufbxi_sub3(c, a), ufbxi_sub3(d, b)); } else { - ufbx_vec3 a, b; - // Newell's Method ufbx_vec3 result = ufbx_zero_vec3; for (size_t i = 0; i < face.num_indices; i++) { size_t next = i + 1 < face.num_indices ? i + 1 : 0; - if (panic) { - a = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + i); - b = ufbx_catch_get_vertex_vec3(panic, positions, face.index_begin + next); - } else { - a = ufbx_get_vertex_vec3(positions, face.index_begin + i); - b = ufbx_get_vertex_vec3(positions, face.index_begin + next); - } + ufbx_vec3 a = ufbx_get_vertex_vec3(positions, face.index_begin + i); + ufbx_vec3 b = ufbx_get_vertex_vec3(positions, face.index_begin + next); result.x += (a.y - b.y) * (a.z + b.z); result.y += (a.z - b.z) * (a.x + b.x); result.z += (a.x - b.x) * (a.y + b.y); @@ -30837,6 +31360,7 @@ ufbx_abi void ufbx_compute_normals(const ufbx_mesh *mesh, const ufbx_vertex_vec3 ufbx_abi ufbx_mesh *ufbx_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_mesh, opts, error); if (!mesh) return NULL; if (level == 0) return (ufbx_mesh*)mesh; return ufbxi_subdivide_mesh(mesh, level, opts, error); @@ -30876,6 +31400,7 @@ ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache_len( const char *filename, size_t filename_len, const ufbx_geometry_cache_opts *opts, ufbx_error *error) { + ufbxi_check_opts_ptr(ufbx_geometry_cache, opts, error); ufbx_string str = ufbxi_safe_string(filename, filename_len); return ufbxi_load_geometry_cache(str, opts, error); } @@ -30902,9 +31427,18 @@ ufbx_abi void ufbx_retain_geometry_cache(ufbx_geometry_cache *cache) ufbxi_retain_ref(&imp->refcount); } +typedef struct { + union { + double f64[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; + float f32[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; + } src; + ufbx_real dst[UFBXI_GEOMETRY_CACHE_BUFFER_SIZE]; +} ufbxi_geometry_cache_buffer; + ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_frame *frame, ufbx_real *data, size_t count, const ufbx_geometry_cache_data_opts *user_opts) { #if UFBXI_FEATURE_GEOMETRY_CACHE + ufbxi_check_opts_return_no_error(0, user_opts); if (!frame || count == 0) return 0; ufbx_assert(data); if (!data) return 0; @@ -30920,10 +31454,6 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr opts.open_file_cb.fn = ufbx_default_open_file; } - // `ufbx_geometry_cache_data_opts` must be cleared to zero first! - ufbx_assert(opts._begin_zero == 0 && opts._end_zero == 0); - if (!(opts._begin_zero == 0 && opts._end_zero == 0)) return 0; - bool use_double = false; size_t src_count = 0; @@ -30990,122 +31520,74 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr ufbx_real *dst = data; size_t mirror_ix = (size_t)frame->mirror_axis - 1; - if (use_double) { - double buffer[512]; // ufbxi_uninit - while (src_count > 0) { - size_t to_read = ufbxi_min_sz(src_count, ufbxi_arraycount(buffer)); - src_count -= to_read; - size_t bytes_read = stream.read_fn(stream.user, buffer, to_read * sizeof(double)); + ufbxi_geometry_cache_buffer buffer; // ufbxi_uninit + while (src_count > 0) { + size_t to_read = ufbxi_min_sz(src_count, UFBXI_GEOMETRY_CACHE_BUFFER_SIZE); + src_count -= to_read; + size_t num_read = 0; + if (use_double) { + size_t bytes_read = stream.read_fn(stream.user, buffer.src.f64, to_read * sizeof(double)); if (bytes_read == SIZE_MAX) bytes_read = 0; - size_t num_read = bytes_read / sizeof(double); - + num_read = bytes_read / sizeof(double); if (src_big_endian != dst_big_endian) { for (size_t i = 0; i < num_read; i++) { - char t, *v = (char*)&buffer[i]; + char t, *v = (char*)&buffer.src.f64[i]; t = v[0]; v[0] = v[7]; v[7] = t; t = v[1]; v[1] = v[6]; v[6] = t; t = v[2]; v[2] = v[5]; v[5] = t; t = v[3]; v[3] = v[4]; v[4] = t; } } - - if (!opts.ignore_transform) { - double scale = frame->scale_factor; - if (scale != 1.0f) { - for (size_t i = 0; i < num_read; i++) { - buffer[i] *= scale; - } - } - if (frame->mirror_axis) { - while (mirror_ix < num_read) { - buffer[mirror_ix] = -buffer[mirror_ix]; - mirror_ix += 3; - } - mirror_ix -= num_read; - } + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + buffer.dst[i] = (ufbx_real)buffer.src.f64[i]; } - - if (dst) { - ufbx_real weight = opts.weight; - if (opts.additive && opts.use_weight) { - for (size_t i = 0; i < num_read; i++) { - dst[i] += (ufbx_real)buffer[i] * weight; - } - } else if (opts.additive) { - for (size_t i = 0; i < num_read; i++) { - dst[i] += (ufbx_real)buffer[i]; - } - } else if (opts.use_weight) { - for (size_t i = 0; i < num_read; i++) { - dst[i] = (ufbx_real)buffer[i] * weight; - } - } else { - for (size_t i = 0; i < num_read; i++) { - dst[i] = (ufbx_real)buffer[i]; - } - } - dst += num_read; - } - - if (num_read != to_read) break; - } - } else { - float buffer[1024]; // ufbxi_uninit - while (src_count > 0) { - size_t to_read = ufbxi_min_sz(src_count, ufbxi_arraycount(buffer)); - src_count -= to_read; - size_t bytes_read = stream.read_fn(stream.user, buffer, to_read * sizeof(float)); + } else { + size_t bytes_read = stream.read_fn(stream.user, buffer.src.f32, to_read * sizeof(float)); if (bytes_read == SIZE_MAX) bytes_read = 0; - size_t num_read = bytes_read / sizeof(float); - + num_read = bytes_read / sizeof(float); if (src_big_endian != dst_big_endian) { for (size_t i = 0; i < num_read; i++) { - char t, *v = (char*)&buffer[i]; + char t, *v = (char*)&buffer.src.f32[i]; t = v[0]; v[0] = v[3]; v[3] = t; t = v[1]; v[1] = v[2]; v[2] = t; } } + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + buffer.dst[i] = (ufbx_real)buffer.src.f32[i]; + } + } - if (!opts.ignore_transform) { - float scale = (float)frame->scale_factor; - if (scale != 1.0f) { - for (size_t i = 0; i < num_read; i++) { - buffer[i] *= scale; - } + if (!opts.ignore_transform) { + ufbx_real scale = frame->scale_factor; + if (scale != 1.0f) { + for (size_t i = 0; i < num_read; i++) { + buffer.dst[i] *= scale; } - if (frame->mirror_axis) { - while (mirror_ix < num_read) { - buffer[mirror_ix] = -buffer[mirror_ix]; - mirror_ix += 3; - } - mirror_ix -= num_read; + } + if (frame->mirror_axis) { + while (mirror_ix < num_read) { + buffer.dst[mirror_ix] = -buffer.dst[mirror_ix]; + mirror_ix += 3; } + mirror_ix -= num_read; } + } - if (dst) { - ufbx_real weight = opts.weight; - if (opts.additive && opts.use_weight) { - for (size_t i = 0; i < num_read; i++) { - dst[i] += (ufbx_real)buffer[i] * weight; - } - } else if (opts.additive) { - for (size_t i = 0; i < num_read; i++) { - dst[i] += (ufbx_real)buffer[i]; - } - } else if (opts.use_weight) { - for (size_t i = 0; i < num_read; i++) { - dst[i] = (ufbx_real)buffer[i] * weight; - } - } else { - for (size_t i = 0; i < num_read; i++) { - dst[i] = (ufbx_real)buffer[i]; - } + if (dst) { + ufbx_real weight = opts.use_weight ? opts.weight : 1.0f; + if (opts.additive) { + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + dst[i] += buffer.dst[i] * weight; + } + } else { + ufbxi_nounroll for (size_t i = 0; i < num_read; i++) { + dst[i] = buffer.dst[i] * weight; } - dst += num_read; } - - if (num_read != to_read) break; + dst += num_read; } + + if (num_read != to_read) break; } if (stream.close_fn) { @@ -31121,6 +31603,7 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr ufbx_abi ufbxi_noinline size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t count, const ufbx_geometry_cache_data_opts *user_opts) { #if UFBXI_FEATURE_GEOMETRY_CACHE + ufbxi_check_opts_return_no_error(0, user_opts); if (!channel || count == 0) return 0; ufbx_assert(data); if (!data) return 0; @@ -31133,10 +31616,6 @@ ufbx_abi ufbxi_noinline size_t ufbx_sample_geometry_cache_real(const ufbx_cache_ memset(&opts, 0, sizeof(opts)); } - // `ufbx_geometry_cache_data_opts` must be cleared to zero first! - ufbx_assert(opts._begin_zero == 0 && opts._end_zero == 0); - if (!(opts._begin_zero == 0 && opts._end_zero == 0)) return 0; - size_t begin = 0; size_t end = channel->frames.count; const ufbx_cache_frame *frames = channel->frames.data; @@ -31253,7 +31732,7 @@ ufbx_abi void *ufbx_thread_pool_get_user_ptr(ufbx_thread_pool_context ctx) return pool->user_ptr; } -ufbx_abi ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index) +ufbx_abi ufbxi_noinline ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index) { if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return 0.0f; uint32_t ix = v->indices.data[index]; @@ -31261,7 +31740,7 @@ ufbx_abi ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vert return v->values.data[(int32_t)ix]; } -ufbx_abi ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index) +ufbx_abi ufbxi_noinline ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index) { if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec2; uint32_t ix = v->indices.data[index]; @@ -31269,7 +31748,7 @@ ufbx_abi ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vert return v->values.data[(int32_t)ix]; } -ufbx_abi ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) { if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec3; uint32_t ix = v->indices.data[index]; @@ -31277,7 +31756,7 @@ ufbx_abi ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vert return v->values.data[(int32_t)ix]; } -ufbx_abi ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index) +ufbx_abi ufbxi_noinline ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index) { if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return ufbx_zero_vec4; uint32_t ix = v->indices.data[index]; @@ -31285,10 +31764,13 @@ ufbx_abi ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vert return v->values.data[(int32_t)ix]; } -ufbx_abi size_t ufbx_get_triangulate_face_num_indices(ufbx_face face) +ufbx_abi ufbx_real ufbx_catch_get_vertex_w_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index) { - if (face.num_indices < 3) return 0; - return (face.num_indices - 2) * 3; + if (ufbxi_panicf(panic, index < v->indices.count, "index (%zu) out of range (%zu)", index, v->indices.count)) return 0.0f; + if (v->values_w.count == 0) return 0.0f; + uint32_t ix = v->indices.data[index]; + if (ufbxi_panicf(panic, (size_t)ix < v->values.count || ix == UFBX_NO_INDEX, "Corrupted or missing vertex attribute (%u) at %zu", ix, index)) return 0.0f; + return v->values_w.data[(int32_t)ix]; } ufbx_abi ufbx_unknown *ufbx_as_unknown(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_UNKNOWN ? (ufbx_unknown*)element : NULL; } @@ -31329,186 +31811,11 @@ ufbx_abi ufbx_selection_set *ufbx_as_selection_set(const ufbx_element *element) ufbx_abi ufbx_selection_node *ufbx_as_selection_node(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_SELECTION_NODE ? (ufbx_selection_node*)element : NULL; } ufbx_abi ufbx_character *ufbx_as_character(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CHARACTER ? (ufbx_character*)element : NULL; } ufbx_abi ufbx_constraint *ufbx_as_constraint(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_CONSTRAINT ? (ufbx_constraint*)element : NULL; } +ufbx_abi ufbx_audio_layer *ufbx_as_audio_layer(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_AUDIO_LAYER ? (ufbx_audio_layer*)element : NULL; } +ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_AUDIO_CLIP ? (ufbx_audio_clip*)element : NULL; } ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_POSE ? (ufbx_pose*)element : NULL; } ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element) { return element && element->type == UFBX_ELEMENT_METADATA_OBJECT ? (ufbx_metadata_object*)element : NULL; } -ufbx_abi void ufbx_ffi_find_int_len(int64_t *retval, const ufbx_props *props, const char *name, size_t name_len, const int64_t *def) -{ - *retval = ufbx_find_int_len(props, name, name_len, *def); -} - -ufbx_abi void ufbx_ffi_find_vec3_len(ufbx_vec3 *retval, const ufbx_props *props, const char *name, size_t name_len, const ufbx_vec3 *def) -{ - *retval = ufbx_find_vec3_len(props, name, name_len, *def); -} - -ufbx_abi void ufbx_ffi_find_string_len(ufbx_string *retval, const ufbx_props *props, const char *name, size_t name_len, const ufbx_string *def) -{ - *retval = ufbx_find_string_len(props, name, name_len, *def); -} - -ufbx_abi void ufbx_ffi_find_anim_props(ufbx_anim_prop_list *retval, const ufbx_anim_layer *layer, const ufbx_element *element) -{ - *retval = ufbx_find_anim_props(layer, element); -} - -ufbx_abi void ufbx_ffi_get_compatible_matrix_for_normals(ufbx_matrix *retval, const ufbx_node *node) -{ - *retval = ufbx_get_compatible_matrix_for_normals(node); -} - -ufbx_abi void ufbx_ffi_evaluate_anim_value_vec2(ufbx_vec2 *retval, const ufbx_anim_value *anim_value, double time) -{ - *retval = ufbx_evaluate_anim_value_vec2(anim_value, time); -} - -ufbx_abi void ufbx_ffi_evaluate_anim_value_vec3(ufbx_vec3 *retval, const ufbx_anim_value *anim_value, double time) -{ - *retval = ufbx_evaluate_anim_value_vec3(anim_value, time); -} - -ufbx_abi void ufbx_ffi_evaluate_prop_len(ufbx_prop *retval, const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time) -{ - *retval = ufbx_evaluate_prop_len(anim, element, name, name_len, time); -} - -ufbx_abi void ufbx_ffi_evaluate_props(ufbx_props *retval, const ufbx_anim *anim, ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size) -{ - *retval = ufbx_evaluate_props(anim, element, time, buffer, buffer_size); -} - -ufbx_abi void ufbx_ffi_evaluate_transform(ufbx_transform *retval, const ufbx_anim *anim, const ufbx_node *node, double time) -{ - *retval = ufbx_evaluate_transform(anim, node, time); -} - -ufbx_abi ufbx_real ufbx_ffi_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time) -{ - return ufbx_evaluate_blend_weight(anim, channel, time); -} - -ufbx_abi void ufbx_ffi_quat_mul(ufbx_quat *retval, const ufbx_quat *a, const ufbx_quat *b) -{ - *retval = ufbx_quat_mul(*a, *b); -} - -ufbx_abi void ufbx_ffi_quat_normalize(ufbx_quat *retval, const ufbx_quat *q) -{ - *retval = ufbx_quat_normalize(*q); -} - -ufbx_abi void ufbx_ffi_quat_fix_antipodal(ufbx_quat *retval, const ufbx_quat *q, const ufbx_quat *reference) -{ - *retval = ufbx_quat_fix_antipodal(*q, *reference); -} - -ufbx_abi void ufbx_ffi_quat_slerp(ufbx_quat *retval, const ufbx_quat *a, const ufbx_quat *b, ufbx_real t) -{ - *retval = ufbx_quat_slerp(*a, *b, t); -} - -ufbx_abi void ufbx_ffi_quat_rotate_vec3(ufbx_vec3 *retval, const ufbx_quat *q, const ufbx_vec3 *v) -{ - *retval = ufbx_quat_rotate_vec3(*q, *v); -} - -ufbx_abi void ufbx_ffi_quat_to_euler(ufbx_vec3 *retval, const ufbx_quat *q, ufbx_rotation_order order) -{ - *retval = ufbx_quat_to_euler(*q, order); -} - -ufbx_abi void ufbx_ffi_euler_to_quat(ufbx_quat *retval, const ufbx_vec3 *v, ufbx_rotation_order order) -{ - *retval = ufbx_euler_to_quat(*v, order); -} - -ufbx_abi void ufbx_ffi_matrix_mul(ufbx_matrix *retval, const ufbx_matrix *a, const ufbx_matrix *b) -{ - *retval = ufbx_matrix_mul(a, b); -} - -ufbx_abi void ufbx_ffi_matrix_invert(ufbx_matrix *retval, const ufbx_matrix *m) -{ - *retval = ufbx_matrix_invert(m); -} - -ufbx_abi void ufbx_ffi_matrix_for_normals(ufbx_matrix *retval, const ufbx_matrix *m) -{ - *retval = ufbx_matrix_for_normals(m); -} - -ufbx_abi void ufbx_ffi_transform_position(ufbx_vec3 *retval, const ufbx_matrix *m, const ufbx_vec3 *v) -{ - *retval = ufbx_transform_position(m, *v); -} - -ufbx_abi void ufbx_ffi_transform_direction(ufbx_vec3 *retval, const ufbx_matrix *m, const ufbx_vec3 *v) -{ - *retval = ufbx_transform_direction(m, *v); -} - -ufbx_abi void ufbx_ffi_transform_to_matrix(ufbx_matrix *retval, const ufbx_transform *t) -{ - *retval = ufbx_transform_to_matrix(t); -} - -ufbx_abi void ufbx_ffi_matrix_to_transform(ufbx_transform *retval, const ufbx_matrix *m) -{ - *retval = ufbx_matrix_to_transform(m); -} - -ufbx_abi void ufbx_ffi_get_skin_vertex_matrix(ufbx_matrix *retval, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback) -{ - *retval = ufbx_get_skin_vertex_matrix(skin, vertex, fallback); -} - -ufbx_abi void ufbx_ffi_get_blend_shape_vertex_offset(ufbx_vec3 *retval, const ufbx_blend_shape *shape, size_t vertex) -{ - *retval = ufbx_get_blend_shape_vertex_offset(shape, vertex); -} - -ufbx_abi void ufbx_ffi_get_blend_vertex_offset(ufbx_vec3 *retval, const ufbx_blend_deformer *blend, size_t vertex) -{ - *retval = ufbx_get_blend_vertex_offset(blend, vertex); -} - -ufbx_abi void ufbx_ffi_evaluate_nurbs_curve(ufbx_curve_point *retval, const ufbx_nurbs_curve *curve, ufbx_real u) -{ - *retval = ufbx_evaluate_nurbs_curve(curve, u); -} - -ufbx_abi void ufbx_ffi_evaluate_nurbs_surface(ufbx_surface_point *retval, const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v) -{ - *retval = ufbx_evaluate_nurbs_surface(surface, u, v); -} - -ufbx_abi void ufbx_ffi_get_weighted_face_normal(ufbx_vec3 *retval, const ufbx_vertex_vec3 *positions, const ufbx_face *face) -{ - *retval = ufbx_get_weighted_face_normal(positions, *face); -} - -ufbx_abi uint32_t ufbx_ffi_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, const ufbx_face *face) -{ - return ufbx_triangulate_face(indices, num_indices, mesh, *face); -} - -ufbx_abi size_t ufbx_ffi_get_triangulate_face_num_indices(const ufbx_face *face) -{ - return ufbx_get_triangulate_face_num_indices(*face); -} - -ufbx_abi ufbx_vec3 ufbx_ffi_evaluate_baked_vec3(const ufbx_baked_vec3 *keyframes, size_t num_keyframes, double time) -{ - ufbx_baked_vec3_list list = { (ufbx_baked_vec3*)keyframes, num_keyframes }; - return ufbx_evaluate_baked_vec3(list, time); -} - -ufbx_abi ufbx_quat ufbx_ffi_evaluate_baked_quat(const ufbx_baked_quat *keyframes, size_t num_keyframes, double time) -{ - ufbx_baked_quat_list list = { (ufbx_baked_quat*)keyframes, num_keyframes }; - return ufbx_evaluate_baked_quat(list, time); -} - #ifdef __cplusplus } #endif diff --git a/thirdparty/ufbx/ufbx.h b/thirdparty/ufbx/ufbx.h index bb331102a597..072569068aae 100644 --- a/thirdparty/ufbx/ufbx.h +++ b/thirdparty/ufbx/ufbx.h @@ -94,6 +94,10 @@ #define ufbx_inline static #endif +// Assertion function used in ufbx, defaults to C standard `assert()`. +// You can define this to your custom preferred assert macro, but in that case +// make sure that it is also used within `ufbx.c`. +// Defining `UFBX_NO_ASSERT` to any value disables assertions. #ifndef ufbx_assert #if defined(UFBX_NO_ASSERT) #define ufbx_assert(cond) (void)0 @@ -110,21 +114,58 @@ // breaking API guarantees. #define ufbx_unsafe +// Linkage of the main ufbx API functions. +// Defaults to nothing, or `static` if `UFBX_STATIC` is defined. +// If you want to isolate ufbx to a single translation unit you can do the following: +// #define UFBX_STATIC +// #include "ufbx.h" +// #include "ufbx.c" #ifndef ufbx_abi - #define ufbx_abi + #if defined(UFBX_STATIC) + #define ufbx_abi static + #else + #define ufbx_abi + #endif +#endif + +// Linkage of the main ufbx data fields in the header. +// Defaults to `extern`, or `static` if `UFBX_STATIC` is defined. +#ifndef ufbx_abi_data + #if defined(UFBX_STATIC) + #define ufbx_abi_data static + #else + #define ufbx_abi_data extern + #endif +#endif + +// Linkage of the main ufbx data fields in the source. +// Defaults to nothing, or `static` if `UFBX_STATIC` is defined. +#ifndef ufbx_abi_data_definition + #if defined(UFBX_STATIC) + #define ufbx_abi_data_def static + #else + #define ufbx_abi_data_def + #endif #endif // -- Configuration -#if defined(UFBX_REAL_IS_FLOAT) - typedef float ufbx_real; -#else - typedef double ufbx_real; +#ifndef UFBX_REAL_TYPE + #if defined(UFBX_REAL_IS_FLOAT) + #define UFBX_REAL_TYPE float + #else + #define UFBX_REAL_TYPE double + #endif #endif +// Limits for embedded arrays within structures. #define UFBX_ERROR_STACK_MAX_DEPTH 8 #define UFBX_PANIC_MESSAGE_LENGTH 128 #define UFBX_ERROR_INFO_LENGTH 256 + +// Number of thread groups to use if threading is enabled. +// A thread group processes a number of tasks and is then waited and potentially +// re-used later. In essence, this controls the granularity of threading. #define UFBX_THREAD_GROUP_COUNT 4 // -- Language @@ -214,17 +255,27 @@ struct ufbx_converter { }; // -- Version +// Packing/unpacking for `UFBX_HEADER_VERSION` and `ufbx_source_version`. #define ufbx_pack_version(major, minor, patch) ((uint32_t)(major)*1000000u + (uint32_t)(minor)*1000u + (uint32_t)(patch)) #define ufbx_version_major(version) ((uint32_t)(version)/1000000u%1000u) #define ufbx_version_minor(version) ((uint32_t)(version)/1000u%1000u) #define ufbx_version_patch(version) ((uint32_t)(version)%1000u) -#define UFBX_HEADER_VERSION ufbx_pack_version(0, 11, 1) +// Version of the ufbx header. +// `UFBX_VERSION` is simply an alias of `UFBX_HEADER_VERSION`. +// `ufbx_source_version` contains the version of the corresponding source file. +// HINT: The version can be compared numerically to the result of `ufbx_pack_version()`, +// for example `#if UFBX_VERSION >= ufbx_pack_version(0, 12, 0)`. +#define UFBX_HEADER_VERSION ufbx_pack_version(0, 14, 0) #define UFBX_VERSION UFBX_HEADER_VERSION // -- Basic types -#define UFBX_NO_INDEX ((uint32_t)~0u) +// Main floating point type used everywhere in ufbx, defaults to `double`. +// If you define `UFBX_REAL_IS_FLOAT` to any value, `ufbx_real` will be defined +// as `float` instead. +// You can also manually define `UFBX_REAL_TYPE` to any floating point type. +typedef UFBX_REAL_TYPE ufbx_real; // Null-terminated UTF-8 encoded string within an FBX file typedef struct ufbx_string { @@ -340,6 +391,9 @@ UFBX_LIST_TYPE(ufbx_vec3_list, ufbx_vec3); UFBX_LIST_TYPE(ufbx_vec4_list, ufbx_vec4); UFBX_LIST_TYPE(ufbx_string_list, ufbx_string); +// Sentinel value used to represent a missing index. +#define UFBX_NO_INDEX ((uint32_t)~0u) + // -- Document object model typedef enum ufbx_dom_value_type UFBX_ENUM_REPR { @@ -578,6 +632,10 @@ typedef struct ufbx_selection_node ufbx_selection_node; typedef struct ufbx_character ufbx_character; typedef struct ufbx_constraint ufbx_constraint; +// Audio +typedef struct ufbx_audio_layer ufbx_audio_layer; +typedef struct ufbx_audio_clip ufbx_audio_clip; + // Miscellaneous typedef struct ufbx_pose ufbx_pose; typedef struct ufbx_metadata_object ufbx_metadata_object; @@ -621,6 +679,8 @@ UFBX_LIST_TYPE(ufbx_selection_set_list, ufbx_selection_set*); UFBX_LIST_TYPE(ufbx_selection_node_list, ufbx_selection_node*); UFBX_LIST_TYPE(ufbx_character_list, ufbx_character*); UFBX_LIST_TYPE(ufbx_constraint_list, ufbx_constraint*); +UFBX_LIST_TYPE(ufbx_audio_layer_list, ufbx_audio_layer*); +UFBX_LIST_TYPE(ufbx_audio_clip_list, ufbx_audio_clip*); UFBX_LIST_TYPE(ufbx_pose_list, ufbx_pose*); UFBX_LIST_TYPE(ufbx_metadata_object_list, ufbx_metadata_object*); @@ -663,6 +723,8 @@ typedef enum ufbx_element_type UFBX_ENUM_REPR { UFBX_ELEMENT_SELECTION_NODE, // < `ufbx_selection_node` UFBX_ELEMENT_CHARACTER, // < `ufbx_character` UFBX_ELEMENT_CONSTRAINT, // < `ufbx_constraint` + UFBX_ELEMENT_AUDIO_LAYER, // < `ufbx_audio_layer` + UFBX_ELEMENT_AUDIO_CLIP, // < `ufbx_audio_clip` UFBX_ELEMENT_POSE, // < `ufbx_pose` UFBX_ELEMENT_METADATA_OBJECT, // < `ufbx_metadata_object` @@ -690,7 +752,7 @@ UFBX_LIST_TYPE(ufbx_connection_list, ufbx_connection); // Some fields (like `connections_src`) are advanced and not visible // in the specialized element structs. // NOTE: The `element_id` value is consistent when loading the -// _same_ file, but re-exporting the file will invalidate them. (TOMOVE) +// _same_ file, but re-exporting the file will invalidate them. struct ufbx_element { ufbx_string name; ufbx_props props; @@ -756,6 +818,7 @@ typedef enum ufbx_inherit_mode UFBX_ENUM_REPR { UFBX_ENUM_TYPE(ufbx_inherit_mode, UFBX_INHERIT_MODE, UFBX_INHERIT_MODE_COMPONENTWISE_SCALE); +// Axis used to mirror transformations for handedness conversion. typedef enum ufbx_mirror_axis UFBX_ENUM_REPR { UFBX_MIRROR_AXIS_NONE, @@ -799,6 +862,7 @@ struct ufbx_node { ufbx_nullable ufbx_mesh *mesh; ufbx_nullable ufbx_light *light; ufbx_nullable ufbx_camera *camera; + ufbx_nullable ufbx_bone *bone; // Less common attributes use these fields. // @@ -930,11 +994,23 @@ struct ufbx_node { // single defined value per vertex accessible via: // attrib.values.data[attrib.indices.data[mesh->vertex_first_index[vertex_ix]] typedef struct ufbx_vertex_attrib { + // Is this attribute defined by the mesh. bool exists; + // List of values the attribute uses. ufbx_void_list values; + // Indices into `values[]`, indexed up to `ufbx_mesh.num_indices`. ufbx_uint32_list indices; + // Number of `ufbx_real` entries per value. size_t value_reals; + // `true` if this attribute is defined per vertex, instead of per index. bool unique_per_vertex; + // Optional 4th 'W' component for the attribute. + // May be defined for the following: + // ufbx_mesh.vertex_normal + // ufbx_mesh.vertex_tangent / ufbx_uv_set.vertex_tangent + // ufbx_mesh.vertex_bitangent / ufbx_uv_set.vertex_bitangent + // NOTE: This is not loaded by default, set `ufbx_load_opts.retain_vertex_attrib_w`. + ufbx_real_list values_w; } ufbx_vertex_attrib; // 1D vertex attribute, see `ufbx_vertex_attrib` for information @@ -944,6 +1020,7 @@ typedef struct ufbx_vertex_real { ufbx_uint32_list indices; size_t value_reals; bool unique_per_vertex; + ufbx_real_list values_w; UFBX_VERTEX_ATTRIB_IMPL(ufbx_real) } ufbx_vertex_real; @@ -955,6 +1032,7 @@ typedef struct ufbx_vertex_vec2 { ufbx_uint32_list indices; size_t value_reals; bool unique_per_vertex; + ufbx_real_list values_w; UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec2) } ufbx_vertex_vec2; @@ -966,6 +1044,7 @@ typedef struct ufbx_vertex_vec3 { ufbx_uint32_list indices; size_t value_reals; bool unique_per_vertex; + ufbx_real_list values_w; UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec3) } ufbx_vertex_vec3; @@ -977,6 +1056,7 @@ typedef struct ufbx_vertex_vec4 { ufbx_uint32_list indices; size_t value_reals; bool unique_per_vertex; + ufbx_real_list values_w; UFBX_VERTEX_ATTRIB_IMPL(ufbx_vec4) } ufbx_vertex_vec4; @@ -1244,6 +1324,11 @@ struct ufbx_mesh { // Segments for each face group. ufbx_mesh_part_list face_group_parts; + // Order of `material_parts` by first face that refers to it. + // Useful for compatibility with FBX SDK and various importers using it, + // as they use this material order by default. + ufbx_uint32_list material_part_usage_order; + // Skinned vertex positions, for efficiency the skinned positions are the // same as the static ones for non-skinned meshes and `skinned_is_local` // is set to true meaning you need to transform them manually using @@ -2272,6 +2357,7 @@ typedef enum ufbx_shader_type UFBX_ENUM_REPR { UFBX_SHADER_SHADERFX_GRAPH, // Variation of the FBX phong shader that can recover PBR properties like // `metalness` or `roughness` from the FBX non-physical values. + // NOTE: Enable `ufbx_load_opts.use_blender_pbr_material`. UFBX_SHADER_BLENDER_PHONG, // Wavefront .mtl format shader (used by .obj files) UFBX_SHADER_WAVEFRONT_MTL, @@ -2663,6 +2749,7 @@ typedef enum ufbx_shader_texture_type UFBX_ENUM_REPR { UFBX_ENUM_TYPE(ufbx_shader_texture_type, UFBX_SHADER_TEXTURE_TYPE, UFBX_SHADER_TEXTURE_OSL); +// Input to a shader texture, see `ufbx_shader_texture`. typedef struct ufbx_shader_texture_input { // Name of the input. @@ -2702,6 +2789,13 @@ typedef struct ufbx_shader_texture_input { UFBX_LIST_TYPE(ufbx_shader_texture_input_list, ufbx_shader_texture_input); +// Texture that emulates a shader graph node. +// 3ds Max exports some materials as node graphs serialized to textures. +// ufbx can parse a small subset of these, as normal maps are often hidden behind +// some kind of bump node. +// NOTE: These encode a lot of details of 3ds Max internals, not recommended for direct use. +// HINT: `ufbx_texture.file_textures[]` contains a list of "real" textures that are connected +// to the `ufbx_texture` that is pretending to be a shader node. typedef struct ufbx_shader_texture { // Type of this shader node. @@ -3246,6 +3340,52 @@ struct ufbx_constraint { ufbx_vec3 ik_pole_vector; }; +// -- Audio + +struct ufbx_audio_layer { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Clips contained in this layer. + ufbx_audio_clip_list clips; +}; + +struct ufbx_audio_clip { + union { ufbx_element element; struct { + ufbx_string name; + ufbx_props props; + uint32_t element_id; + uint32_t typed_id; + }; }; + + // Filename relative to the currently loaded file. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_string filename; + // Absolute filename specified in the file. + ufbx_string absolute_filename; + // Relative filename specified in the file. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_string relative_filename; + + // Filename relative to the loaded file, non-UTF-8 encoded. + // HINT: If using functions other than `ufbx_load_file()`, you can provide + // `ufbx_load_opts.filename/raw_filename` to let ufbx resolve this. + ufbx_blob raw_filename; + // Absolute filename specified in the file, non-UTF-8 encoded. + ufbx_blob raw_absolute_filename; + // Relative filename specified in the file, non-UTF-8 encoded. + // NOTE: May be absolute if the file is saved in a different drive. + ufbx_blob raw_relative_filename; + + // Optional embedded content blob, eg. raw .png format data + ufbx_blob content; +}; + // -- Miscellaneous typedef struct ufbx_bone_pose { @@ -3313,12 +3453,11 @@ typedef enum ufbx_exporter UFBX_ENUM_REPR { UFBX_EXPORTER_BLENDER_BINARY, UFBX_EXPORTER_BLENDER_ASCII, UFBX_EXPORTER_MOTION_BUILDER, - UFBX_EXPORTER_BC_UNITY_EXPORTER, UFBX_ENUM_FORCE_WIDTH(UFBX_EXPORTER) } ufbx_exporter; -UFBX_ENUM_TYPE(ufbx_exporter, UFBX_EXPORTER, UFBX_EXPORTER_BC_UNITY_EXPORTER); +UFBX_ENUM_TYPE(ufbx_exporter, UFBX_EXPORTER, UFBX_EXPORTER_MOTION_BUILDER); typedef struct ufbx_application { ufbx_string vendor; @@ -3355,6 +3494,12 @@ typedef enum ufbx_warning_type UFBX_ENUM_REPR { // Duplicated connection between two elements that shouldn't have. UFBX_WARNING_DUPLICATE_CONNECTION, + // Vertex 'W' attribute length differs from main attribute. + UFBX_WARNING_BAD_VERTEX_W_ATTRIBUTE, + + // Missing polygon mapping type. + UFBX_WARNING_MISSING_POLYGON_MAPPING, + // Out-of-bounds index has been clamped to be in-bounds. // HINT: You can use `ufbx_index_error_handling` to adjust behavior. UFBX_WARNING_INDEX_CLAMPED, @@ -3524,6 +3669,9 @@ typedef struct ufbx_metadata { ufbx_real bone_prop_size_unit; bool bone_prop_limb_length_relative; + + ufbx_real ortho_size_unit; + int64_t ktime_second; // < One second in internal KTime units ufbx_string original_file_path; @@ -3606,11 +3754,14 @@ typedef struct ufbx_scene_settings { // HINT: Use `ufbx_load_opts.target_unit_meters` to normalize this. ufbx_real unit_meters; + // Frames per second the animation is defined at. double frames_per_second; ufbx_vec3 ambient_color; ufbx_string default_camera; + // Animation user interface settings. + // HINT: Use `ufbx_scene_settings.frames_per_second` instead of interpreting these yourself. ufbx_time_mode time_mode; ufbx_time_protocol time_protocol; ufbx_snap_mode snap_mode; @@ -3691,6 +3842,10 @@ struct ufbx_scene { ufbx_character_list characters; ufbx_constraint_list constraints; + // Audio + ufbx_audio_layer_list audio_layers; + ufbx_audio_clip_list audio_clips; + // Miscellaneous ufbx_pose_list poses; ufbx_metadata_object_list metadata_objects; @@ -3748,10 +3903,13 @@ typedef struct ufbx_topo_edge { ufbx_topo_flags flags; } ufbx_topo_edge; +// Vertex data array for `ufbx_generate_indices()`. +// NOTE: `ufbx_generate_indices()` compares the vertices using `memcmp()`, so +// any padding should be cleared to zero. typedef struct ufbx_vertex_stream { - void *data; - size_t vertex_count; - size_t vertex_size; + void *data; // < Data pointer of shape `char[vertex_count][vertex_size]`. + size_t vertex_count; // < Number of vertices in this stream, for sanity checking. + size_t vertex_size; // < Size of a vertex in bytes. } ufbx_vertex_stream; // -- Memory callbacks @@ -4179,6 +4337,10 @@ typedef enum ufbx_inherit_mode_handling UFBX_ENUM_REPR { // as `UFBX_INHERIT_MODE_HANDLING_HELPER_NODES`. UFBX_INHERIT_MODE_HANDLING_COMPENSATE, + // Attempt to compensate for bone scale by inversely scaling children. + // Will never create helper nodes. + UFBX_INHERIT_MODE_HANDLING_COMPENSATE_NO_FALLBACK, + // Ignore non-standard inheritance modes. // Forces all nodes to have `UFBX_INHERIT_MODE_NORMAL` regardless of the // inherit mode specified in the file. This can be useful for emulating @@ -4207,51 +4369,120 @@ typedef enum ufbx_pivot_handling UFBX_ENUM_REPR { UFBX_ENUM_TYPE(ufbx_pivot_handling, UFBX_PIVOT_HANDLING, UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT); +typedef enum ufbx_baked_key_flags UFBX_FLAG_REPR { + // This keyframe represents a constant step from the left side + UFBX_BAKED_KEY_STEP_LEFT = 0x1, + // This keyframe represents a constant step from the right side + UFBX_BAKED_KEY_STEP_RIGHT = 0x2, + // This keyframe is the main part of a step + // Bordering either `UFBX_BAKED_KEY_STEP_LEFT` or `UFBX_BAKED_KEY_STEP_RIGHT`. + UFBX_BAKED_KEY_STEP_KEY = 0x4, + // This keyframe is a real keyframe in the source animation + UFBX_BAKED_KEY_KEYFRAME = 0x8, + // This keyframe has been reduced by maximum sample rate. + // See `ufbx_bake_opts.maximum_sample_rate`. + UFBX_BAKED_KEY_REDUCED = 0x10, + + UFBX_FLAG_FORCE_WIDTH(UFBX_BAKED_KEY) +} ufbx_baked_key_flags; + typedef struct ufbx_baked_vec3 { - double time; - ufbx_vec3 value; + double time; // < Time of the keyframe, in seconds + ufbx_vec3 value; // < Value at `time`, can be linearly interpolated + ufbx_baked_key_flags flags; // < Additional information about the keyframe } ufbx_baked_vec3; UFBX_LIST_TYPE(ufbx_baked_vec3_list, ufbx_baked_vec3); typedef struct ufbx_baked_quat { - double time; - ufbx_quat value; + double time; // < Time of the keyframe, in seconds + ufbx_quat value; // < Value at `time`, can be (spherically) linearly interpolated + ufbx_baked_key_flags flags; // < Additional information about the keyframe } ufbx_baked_quat; UFBX_LIST_TYPE(ufbx_baked_quat_list, ufbx_baked_quat); +// Baked transform animation for a single node. typedef struct ufbx_baked_node { + + // Typed ID of the node, maps to `ufbx_scene.nodes[]`. uint32_t typed_id; + // Element ID of the element, maps to `ufbx_scene.elements[]`. uint32_t element_id; + + // The translation channel has constant values for the whole animation. bool constant_translation; + // The rotation channel has constant values for the whole animation. bool constant_rotation; + // The scale channel has constant values for the whole animation. bool constant_scale; + + // Translation keys for the animation, maps to `ufbx_node.local_transform.translation`. ufbx_baked_vec3_list translation_keys; + // Rotation keyframes, maps to `ufbx_node.local_transform.rotation`. ufbx_baked_quat_list rotation_keys; + // Scale keyframes, maps to `ufbx_node.local_transform.scale`. ufbx_baked_vec3_list scale_keys; + } ufbx_baked_node; UFBX_LIST_TYPE(ufbx_baked_node_list, ufbx_baked_node); +// Baked property animation. typedef struct ufbx_baked_prop { + // Name of the property, eg. `"Visibility"`. ufbx_string name; + // The value of the property is constant for the whole animation. bool constant_value; + // Property value keys. ufbx_baked_vec3_list keys; } ufbx_baked_prop; UFBX_LIST_TYPE(ufbx_baked_prop_list, ufbx_baked_prop); +// Baked property animation for a single element. typedef struct ufbx_baked_element { + // Element ID of the element, maps to `ufbx_scene.elements[]`. uint32_t element_id; + // List of properties the animation modifies. ufbx_baked_prop_list props; } ufbx_baked_element; UFBX_LIST_TYPE(ufbx_baked_element_list, ufbx_baked_element); +typedef struct ufbx_baked_anim_metadata { + // Memory statistics + size_t result_memory_used; + size_t temp_memory_used; + size_t result_allocs; + size_t temp_allocs; +} ufbx_baked_anim_metadata; + +// Animation baked into linearly interpolated keyframes. +// See `ufbx_bake_anim()`. typedef struct ufbx_baked_anim { + + // Nodes that are modified by the animation. + // Some nodes may be missing if the specified animation does not transform them. + // Conversely, some non-obviously animated nodes may be included as exporters + // often may add dummy keyframes for objects. ufbx_baked_node_list nodes; + + // Element properties modified by the animation. ufbx_baked_element_list elements; + + // Playback time range for the animation. + double playback_time_begin; + double playback_time_end; + double playback_duration; + + // Keyframe time range. + double key_time_min; + double key_time_max; + + // Additional bake information. + ufbx_baked_anim_metadata metadata; + } ufbx_baked_anim; // -- Thread API @@ -4329,6 +4560,12 @@ typedef struct ufbx_load_opts { // Clean-up skin weights by removing negative, zero and NAN weights. bool clean_skin_weights; + // Read Blender materials as PBR values. + // Blender converts PBR materials to legacy FBX Phong materials in a deterministic way. + // If this setting is enabled, such materials will be read as `UFBX_SHADER_BLENDER_PHONG`, + // which means ufbx will be able to parse roughness and metallic textures. + bool use_blender_pbr_material; + // Don't adjust reading the FBX file depending on the detected exporter bool disable_quirks; @@ -4470,6 +4707,10 @@ typedef struct ufbx_load_opts { // Specify how to handle Unicode errors in strings. ufbx_unicode_error_handling unicode_error_handling; + // Retain the 'W' component of mesh normal/tangent/bitangent. + // See `ufbx_vertex_attrib.values_w`. + bool retain_vertex_attrib_w; + // Retain the raw document structure using `ufbx_dom_node`. bool retain_dom; @@ -4512,6 +4753,16 @@ typedef struct ufbx_load_opts { // (.obj) Data for the .mtl file. ufbx_blob obj_mtl_data; + // The world unit in meters that .obj files are assumed to be in. + // .obj files do not define the working units. By default the unit scale + // is read as zero, and no unit conversion is performed. + ufbx_real obj_unit_meters; + + // Coordinate space .obj files are assumed to be in. + // .obj files do not define the coordinate space they use. By default no + // coordinate space is assumed and no conversion is performed. + ufbx_coordinate_axes obj_axes; + uint32_t _end_zero; } ufbx_load_opts; @@ -4559,16 +4810,19 @@ UFBX_LIST_TYPE(ufbx_const_transform_override_list, const ufbx_transform_override typedef struct ufbx_anim_opts { uint32_t _begin_zero; - // Animation layers + // Animation layers indices. + // Corresponding to `ufbx_scene.anim_layers[]`, aka `ufbx_anim_layer.typed_id`. ufbx_const_uint32_list layer_ids; - // Override layer weights + // Override layer weights, parallel to `ufbx_anim_opts.layer_ids[]`. ufbx_const_real_list override_layer_weights; - // Property overrides + // Property overrides. + // These allow you to override FBX properties, such as 'UFBX_Lcl_Rotation`. ufbx_const_prop_override_desc_list prop_overrides; - // Transform overrides + // Transform overrides. + // These allow you to override individual nodes' `ufbx_node.local_transform`. ufbx_const_transform_override_list transform_overrides; // Ignore connected properties @@ -4579,16 +4833,49 @@ typedef struct ufbx_anim_opts { uint32_t _end_zero; } ufbx_anim_opts; +// Specifies how to handle stepped tangents. +typedef enum ufbx_bake_step_handling UFBX_ENUM_REPR { + + // One millisecond default step duration, with potential extra slack for converting to `float`. + UFBX_BAKE_STEP_HANDLING_DEFAULT, + + // Use a custom interpolation duration for the constant step. + // See `ufbx_bake_opts.step_custom_duration` and optionally `ufbx_bake_opts.step_custom_epsilon`. + UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION, + + // Stepped keyframes are represented as keyframes at the exact same time. + // Use flags `UFBX_BAKED_KEY_STEP_LEFT` and `UFBX_BAKED_KEY_STEP_RIGHT` to differentiate + // between the primary key and edge limits. + UFBX_BAKE_STEP_HANDLING_IDENTICAL_TIME, + + // Represent stepped keyframe times as the previous/next representable `double` value. + // Using this and robust linear interpolation will handle stepped tangents correctly + // without having to look at the key flags. + // NOTE: Casting these values to `float` or otherwise modifying them can collapse + // the keyframes to have the identical time. + UFBX_BAKE_STEP_HANDLING_ADJACENT_DOUBLE, + + // Treat all stepped tangents as linearly interpolated. + UFBX_BAKE_STEP_HANDLING_IGNORE, + + UFBX_ENUM_FORCE_WIDTH(ufbx_bake_step_handling) +} ufbx_bake_step_handling; + +UFBX_ENUM_TYPE(ufbx_bake_step_handling, UFBX_BAKE_STEP_HANDLING, UFBX_BAKE_STEP_HANDLING_IGNORE); + typedef struct ufbx_bake_opts { uint32_t _begin_zero; ufbx_allocator_opts temp_allocator; // < Allocator used during loading ufbx_allocator_opts result_allocator; // < Allocator used for the final baked animation - // Offset to start the evaluation from. - double time_start_offset; + // Move the keyframe times to start from zero regardless of the animation start time. + // For example, for an animation spanning between frames [30, 60] will be moved to + // [0, 30] in the baked animation. + // NOTE: This is in general not equivalent to subtracting `ufbx_anim.time_begin` + // from each keyframe, as this trimming is done exactly using internal FBX ticks. + bool trim_start_time; - // Sample rate in seconds. // Samples per second to use for resampling non-linear animation. // Default: 30 double resample_rate; @@ -4620,9 +4907,16 @@ typedef struct ufbx_bake_opts { // Default: 32 size_t max_keyframe_segments; - // Timestep in seconds for constant interpolation. - // Default of `0.0` uses the smallest representable time offset. - double constant_timestep; + // How to handle stepped tangents. + ufbx_bake_step_handling step_handling; + + // Interpolation duration used by `UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION`. + double step_custom_duration; + + // Interpolation epsilon used by `UFBX_BAKE_STEP_HANDLING_CUSTOM_DURATION`. + // Defined as the minimum fractional decrease/increase in key time, ie. + // `time / (1.0 + step_custom_epsilon)` and `time * (1.0 + step_custom_epsilon)`. + double step_custom_epsilon; // Enable key reduction. bool key_reduction_enabled; @@ -4640,10 +4934,6 @@ typedef struct ufbx_bake_opts { // Default: `4` size_t key_reduction_passes; - // Compensate for `UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE` by adjusting child scale. - // NOTE: This is an lossy operation, and properly works only for uniform scaling. - bool compensate_inherit_no_scale; - uint32_t _end_zero; } ufbx_bake_opts; @@ -4656,7 +4946,7 @@ typedef struct ufbx_tessellate_curve_opts { ufbx_allocator_opts result_allocator; // < Allocator used for the final line curve // How many segments tessellate each span in `ufbx_nurbs_basis.spans`. - uint32_t span_subdivision; + size_t span_subdivision; uint32_t _end_zero; } ufbx_tessellate_curve_opts; @@ -4674,8 +4964,8 @@ typedef struct ufbx_tessellate_surface_opts { // would make it easy to create an FBX file with an absurdly high subdivision // rate (similar to mesh subdivision). Please enforce copy the value yourself // enforcing whatever limits you deem reasonable. - uint32_t span_subdivision_u; - uint32_t span_subdivision_v; + size_t span_subdivision_u; + size_t span_subdivision_v; // Skip computing `ufbx_mesh.material_parts[]` bool skip_mesh_parts; @@ -4781,27 +5071,26 @@ extern "C" { #endif // Various zero/empty/identity values -extern const ufbx_string ufbx_empty_string; -extern const ufbx_blob ufbx_empty_blob; -extern const ufbx_matrix ufbx_identity_matrix; -extern const ufbx_transform ufbx_identity_transform; -extern const ufbx_vec2 ufbx_zero_vec2; -extern const ufbx_vec3 ufbx_zero_vec3; -extern const ufbx_vec4 ufbx_zero_vec4; -extern const ufbx_quat ufbx_identity_quat; - -// Commonly used coordinate axes - -extern const ufbx_coordinate_axes ufbx_axes_right_handed_y_up; -extern const ufbx_coordinate_axes ufbx_axes_right_handed_z_up; -extern const ufbx_coordinate_axes ufbx_axes_left_handed_y_up; -extern const ufbx_coordinate_axes ufbx_axes_left_handed_z_up; +ufbx_abi_data const ufbx_string ufbx_empty_string; +ufbx_abi_data const ufbx_blob ufbx_empty_blob; +ufbx_abi_data const ufbx_matrix ufbx_identity_matrix; +ufbx_abi_data const ufbx_transform ufbx_identity_transform; +ufbx_abi_data const ufbx_vec2 ufbx_zero_vec2; +ufbx_abi_data const ufbx_vec3 ufbx_zero_vec3; +ufbx_abi_data const ufbx_vec4 ufbx_zero_vec4; +ufbx_abi_data const ufbx_quat ufbx_identity_quat; + +// Commonly used coordinate axes. +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_right_handed_y_up; +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_right_handed_z_up; +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_left_handed_y_up; +ufbx_abi_data const ufbx_coordinate_axes ufbx_axes_left_handed_z_up; // Sizes of element types. eg `sizeof(ufbx_node)` -extern const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT]; +ufbx_abi_data const size_t ufbx_element_type_size[UFBX_ELEMENT_TYPE_COUNT]; // Version of the source file, comparable to `UFBX_HEADER_VERSION` -extern const uint32_t ufbx_source_version; +ufbx_abi_data const uint32_t ufbx_source_version; // Practically always `true` (see below), if not you need to be careful with threads. // @@ -4958,7 +5247,6 @@ ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time // Evaluate a value from bundled animation curves. ufbx_abi ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time); -ufbx_abi ufbx_vec2 ufbx_evaluate_anim_value_vec2(const ufbx_anim_value *anim_value, double time); ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time); // Evaluate an animated property `name` from `element` at `time`. @@ -4973,6 +5261,7 @@ ufbx_inline ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_eleme // `ufbx_props.defaults`. This lets you use `ufbx_find_prop/value()` for the results. ufbx_abi ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size); +// Flags to control `ufbx_evaluate_transform_flags()`. typedef enum ufbx_transform_flags UFBX_FLAG_REPR { // Ignore parent scale helper. @@ -4986,16 +5275,22 @@ typedef enum ufbx_transform_flags UFBX_FLAG_REPR { // Require explicit components UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES = 0x4, + // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.translation`. UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION = 0x10, + // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.rotation`. UFBX_TRANSFORM_FLAG_INCLUDE_ROTATION = 0x20, + // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.scale`. UFBX_TRANSFORM_FLAG_INCLUDE_SCALE = 0x40, UFBX_FLAG_FORCE_WIDTH(UFBX_TRANSFORM_FLAGS) } ufbx_transform_flags; +// Evaluate the animated transform of a node given a time. ufbx_abi ufbx_transform ufbx_evaluate_transform(const ufbx_anim *anim, const ufbx_node *node, double time); ufbx_abi ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, const ufbx_node *node, double time, uint32_t flags); +// Evaluate the blend shape weight of a blend channel. +// NOTE: Return value uses `1.0` for full weight, instead of `100.0` that the internal property `UFBX_Weight` uses. ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time); // Evaluate the whole `scene` at a specific `time` in the animation `anim`. @@ -5007,42 +5302,70 @@ ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_ // scene cannot be freed until all evaluated scenes are freed. ufbx_abi ufbx_scene *ufbx_evaluate_scene(const ufbx_scene *scene, const ufbx_anim *anim, double time, const ufbx_evaluate_opts *opts, ufbx_error *error); +// Create a custom animation descriptor. +// `ufbx_anim_opts` is used to specify animation layers and weights. +// HINT: You can also leave `ufbx_anim_opts.layer_ids[]` empty and only specify +// overrides to evaluate the scene with different properties or local transforms. ufbx_abi ufbx_anim *ufbx_create_anim(const ufbx_scene *scene, const ufbx_anim_opts *opts, ufbx_error *error); -ufbx_abi void ufbx_retain_anim(ufbx_anim *anim); +// Free an animation returned by `ufbx_create_anim()`. ufbx_abi void ufbx_free_anim(ufbx_anim *anim); +// Increase the animation reference count. +ufbx_abi void ufbx_retain_anim(ufbx_anim *anim); + // Animation baking +// "Bake" an animation to linearly interpolated keyframes. +// Composites the FBX transformation chain into quaternion rotations. ufbx_abi ufbx_baked_anim *ufbx_bake_anim(const ufbx_scene *scene, const ufbx_anim *anim, const ufbx_bake_opts *opts, ufbx_error *error); ufbx_abi void ufbx_retain_baked_anim(ufbx_baked_anim *bake); ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake); +ufbx_abi ufbx_baked_node *ufbx_find_baked_node_by_typed_id(ufbx_baked_anim *bake, uint32_t typed_id); +ufbx_abi ufbx_baked_node *ufbx_find_baked_node(ufbx_baked_anim *bake, ufbx_node *node); + +ufbx_abi ufbx_baked_element *ufbx_find_baked_element_by_element_id(ufbx_baked_anim *bake, uint32_t element_id); +ufbx_abi ufbx_baked_element *ufbx_find_baked_element(ufbx_baked_anim *bake, ufbx_element *element); + +// Evaluate baked animation `keyframes` at `time`. +// Internally linearly interpolates between two adjacent keyframes. +// Handles stepped tangents cleanly, which is not strictly necessary for custom interpolation. ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time); + +// Evaluate baked animation `keyframes` at `time`. +// Internally spherically interpolates (`ufbx_quat_slerp()`) between two adjacent keyframes. +// Handles stepped tangents cleanly, which is not strictly necessary for custom interpolation. ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, double time); // Poses +// Retrieve the bone pose for `node`. +// Returns `NULL` if the pose does not contain `node`. ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node); // Materials +// Find a texture for a given material FBX property. ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len); ufbx_inline ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name) { return ufbx_find_prop_texture_len(material, name, strlen(name)); } +// Find a texture for a given shader property. ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len); ufbx_inline ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_len(shader, name, strlen(name)); } +// Map from a shader property to material property. ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len); ufbx_inline ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_bindings_len(shader, name, strlen(name)); } +// Find an input in a shader texture. ufbx_abi ufbx_shader_texture_input *ufbx_find_shader_texture_input_len(const ufbx_shader_texture *shader, const char *name, size_t name_len); ufbx_inline ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx_shader_texture *shader, const char *name) { return ufbx_find_shader_texture_input_len(shader, name, strlen(name)); @@ -5050,8 +5373,13 @@ ufbx_inline ufbx_shader_texture_input *ufbx_find_shader_texture_input(const ufbx // Math +// Returns `true` if `axes` forms a valid coordinate space. ufbx_abi bool ufbx_coordinate_axes_valid(ufbx_coordinate_axes axes); +// Vector math utility functions. +ufbx_abi ufbx_vec3 ufbx_vec3_normalize(ufbx_vec3 v); + +// Quaternion math utility functions. ufbx_abi ufbx_real ufbx_quat_dot(ufbx_quat a, ufbx_quat b); ufbx_abi ufbx_quat ufbx_quat_mul(ufbx_quat a, ufbx_quat b); ufbx_abi ufbx_quat ufbx_quat_normalize(ufbx_quat q); @@ -5061,40 +5389,74 @@ ufbx_abi ufbx_vec3 ufbx_quat_rotate_vec3(ufbx_quat q, ufbx_vec3 v); ufbx_abi ufbx_vec3 ufbx_quat_to_euler(ufbx_quat q, ufbx_rotation_order order); ufbx_abi ufbx_quat ufbx_euler_to_quat(ufbx_vec3 v, ufbx_rotation_order order); +// Matrix math utility functions. ufbx_abi ufbx_matrix ufbx_matrix_mul(const ufbx_matrix *a, const ufbx_matrix *b); ufbx_abi ufbx_real ufbx_matrix_determinant(const ufbx_matrix *m); ufbx_abi ufbx_matrix ufbx_matrix_invert(const ufbx_matrix *m); + +// Get a matrix that can be used to transform geometry normals. +// NOTE: You must normalize the normals after transforming them with this matrix, +// eg. using `ufbx_vec3_normalize()`. +// NOTE: This function flips the normals if the determinant is negative. ufbx_abi ufbx_matrix ufbx_matrix_for_normals(const ufbx_matrix *m); + +// Matrix transformation utilities. ufbx_abi ufbx_vec3 ufbx_transform_position(const ufbx_matrix *m, ufbx_vec3 v); ufbx_abi ufbx_vec3 ufbx_transform_direction(const ufbx_matrix *m, ufbx_vec3 v); + +// Conversions between `ufbx_matrix` and `ufbx_transform`. ufbx_abi ufbx_matrix ufbx_transform_to_matrix(const ufbx_transform *t); ufbx_abi ufbx_transform ufbx_matrix_to_transform(const ufbx_matrix *m); // Skinning +// Get a matrix representing the deformation for a single vertex. +// Returns `fallback` if the vertex is not skinned. ufbx_abi ufbx_matrix ufbx_catch_get_skin_vertex_matrix(ufbx_panic *panic, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback); ufbx_inline ufbx_matrix ufbx_get_skin_vertex_matrix(const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback) { return ufbx_catch_get_skin_vertex_matrix(NULL, skin, vertex, fallback); } +// Resolve the index into `ufbx_blend_shape.position_offsets[]` given a vertex. +// Returns `UFBX_NO_INDEX` if the vertex is not included in the blend shape. ufbx_abi uint32_t ufbx_get_blend_shape_offset_index(const ufbx_blend_shape *shape, size_t vertex); + +// Get the offset for a given vertex in the blend shape. +// Returns `ufbx_zero_vec3` if the vertex is not a included in the blend shape. ufbx_abi ufbx_vec3 ufbx_get_blend_shape_vertex_offset(const ufbx_blend_shape *shape, size_t vertex); + +// Get the _current_ blend offset given a blend deformer. +// NOTE: This depends on the current animated blend weight of the deformer. ufbx_abi ufbx_vec3 ufbx_get_blend_vertex_offset(const ufbx_blend_deformer *blend, size_t vertex); +// Apply the blend shape with `weight` to given vertices. ufbx_abi void ufbx_add_blend_shape_vertex_offsets(const ufbx_blend_shape *shape, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight); + +// Apply the blend deformer with `weight` to given vertices. +// NOTE: This depends on the current animated blend weight of the deformer. ufbx_abi void ufbx_add_blend_vertex_offsets(const ufbx_blend_deformer *blend, ufbx_vec3 *vertices, size_t num_vertices, ufbx_real weight); // Curves/surfaces +// Low-level utility to evaluate NURBS the basis functions. ufbx_abi size_t ufbx_evaluate_nurbs_basis(const ufbx_nurbs_basis *basis, ufbx_real u, ufbx_real *weights, size_t num_weights, ufbx_real *derivatives, size_t num_derivatives); +// Evaluate a point on a NURBS curve given the parameter `u`. ufbx_abi ufbx_curve_point ufbx_evaluate_nurbs_curve(const ufbx_nurbs_curve *curve, ufbx_real u); + +// Evaluate a point on a NURBS surface given the parameter `u` and `v`. ufbx_abi ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v); +// Tessellate a NURBS curve into a polyline. ufbx_abi ufbx_line_curve *ufbx_tessellate_nurbs_curve(const ufbx_nurbs_curve *curve, const ufbx_tessellate_curve_opts *opts, ufbx_error *error); + +// Tessellate a NURBS surface into a mesh. ufbx_abi ufbx_mesh *ufbx_tessellate_nurbs_surface(const ufbx_nurbs_surface *surface, const ufbx_tessellate_surface_opts *opts, ufbx_error *error); +// Free a line returned by `ufbx_tessellate_nurbs_curve()`. ufbx_abi void ufbx_free_line_curve(ufbx_line_curve *curve); + +// Increase the refcount of the line. ufbx_abi void ufbx_retain_line_curve(ufbx_line_curve *curve); // Mesh Topology @@ -5103,6 +5465,9 @@ ufbx_abi void ufbx_retain_line_curve(ufbx_line_curve *curve); // Returns `UFBX_NO_INDEX` if out of bounds. ufbx_abi uint32_t ufbx_find_face_index(ufbx_mesh *mesh, size_t index); +// Triangulate a mesh face, returning the number of triangles. +// NOTE: You need to space for `(face.num_indices - 2) * 3 - 1` indices! +// HINT: Using `ufbx_mesh.max_face_triangles * 3` is always safe. ufbx_abi uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face); ufbx_inline uint32_t ufbx_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, ufbx_face face) { return ufbx_catch_triangulate_face(NULL, indices, num_indices, mesh, face); @@ -5117,21 +5482,27 @@ ufbx_inline void ufbx_compute_topology(const ufbx_mesh *mesh, ufbx_topo_edge *to // Get the next/previous edge around a vertex // NOTE: Does not return the half-edge on the opposite side (ie. `topo[index].twin`) +// Get the next half-edge in `topo`. ufbx_abi uint32_t ufbx_catch_topo_next_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); ufbx_inline uint32_t ufbx_topo_next_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { return ufbx_catch_topo_next_vertex_edge(NULL, topo, num_topo, index); } +// Get the previous half-edge in `topo`. ufbx_abi uint32_t ufbx_catch_topo_prev_vertex_edge(ufbx_panic *panic, const ufbx_topo_edge *topo, size_t num_topo, uint32_t index); ufbx_inline uint32_t ufbx_topo_prev_vertex_edge(const ufbx_topo_edge *topo, size_t num_topo, uint32_t index) { return ufbx_catch_topo_prev_vertex_edge(NULL, topo, num_topo, index); } +// Calculate a normal for a given face. +// The returned normal is weighted by face area. ufbx_abi ufbx_vec3 ufbx_catch_get_weighted_face_normal(ufbx_panic *panic, const ufbx_vertex_vec3 *positions, ufbx_face face); ufbx_inline ufbx_vec3 ufbx_get_weighted_face_normal(const ufbx_vertex_vec3 *positions, ufbx_face face) { return ufbx_catch_get_weighted_face_normal(NULL, positions, face); } +// Generate indices for normals from the topology. +// Respects smoothing groups. ufbx_abi size_t ufbx_catch_generate_normal_mapping(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth); @@ -5139,6 +5510,8 @@ ufbx_abi size_t ufbx_generate_normal_mapping(const ufbx_mesh *mesh, const ufbx_topo_edge *topo, size_t num_topo, uint32_t *normal_indices, size_t num_normal_indices, bool assume_smooth); +// Compute normals given normal indices. +// You can use `ufbx_generate_normal_mapping()` to generate the normal indices. ufbx_abi void ufbx_catch_compute_normals(ufbx_panic *panic, const ufbx_mesh *mesh, const ufbx_vertex_vec3 *positions, const uint32_t *normal_indices, size_t num_normal_indices, ufbx_vec3 *normals, size_t num_normals); @@ -5146,13 +5519,20 @@ ufbx_abi void ufbx_compute_normals(const ufbx_mesh *mesh, const ufbx_vertex_vec3 const uint32_t *normal_indices, size_t num_normal_indices, ufbx_vec3 *normals, size_t num_normals); +// Subdivide a mesh using the Catmull-Clark subdivision `level` times. ufbx_abi ufbx_mesh *ufbx_subdivide_mesh(const ufbx_mesh *mesh, size_t level, const ufbx_subdivide_opts *opts, ufbx_error *error); +// Free a mesh returned from `ufbx_subdivide_mesh()` or `ufbx_tessellate_nurbs_surface()`. ufbx_abi void ufbx_free_mesh(ufbx_mesh *mesh); + +// Increase the mesh reference count. ufbx_abi void ufbx_retain_mesh(ufbx_mesh *mesh); // Geometry caches +// Load geometry cache information from a file. +// As geometry caches can be massive, this does not actually read the data, but +// only seeks through the files to form the metadata. ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache( const char *filename, const ufbx_geometry_cache_opts *opts, ufbx_error *error); @@ -5160,21 +5540,29 @@ ufbx_abi ufbx_geometry_cache *ufbx_load_geometry_cache_len( const char *filename, size_t filename_len, const ufbx_geometry_cache_opts *opts, ufbx_error *error); +// Free a geometry cache returned from `ufbx_load_geometry_cache()`. ufbx_abi void ufbx_free_geometry_cache(ufbx_geometry_cache *cache); +// Increase the geometry cache reference count. ufbx_abi void ufbx_retain_geometry_cache(ufbx_geometry_cache *cache); +// Read a frame from a geometry cache. ufbx_abi size_t ufbx_read_geometry_cache_real(const ufbx_cache_frame *frame, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); -ufbx_abi size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); ufbx_abi size_t ufbx_read_geometry_cache_vec3(const ufbx_cache_frame *frame, ufbx_vec3 *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); +// Sample the a geometry cache channel, linearly blending between adjacent frames. +ufbx_abi size_t ufbx_sample_geometry_cache_real(const ufbx_cache_channel *channel, double time, ufbx_real *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); ufbx_abi size_t ufbx_sample_geometry_cache_vec3(const ufbx_cache_channel *channel, double time, ufbx_vec3 *data, size_t num_data, const ufbx_geometry_cache_data_opts *opts); // DOM +// Find a DOM node given a name. ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const char *name, size_t name_len); ufbx_inline ufbx_dom_node *ufbx_dom_find(const ufbx_dom_node *parent, const char *name) { return ufbx_dom_find_len(parent, name, strlen(name)); } // Utility +// Generate an index buffer for a flat vertex buffer. +// `streams` specifies one or more vertex data arrays, each stream must contain `num_indices` vertices. +// This function compacts the data within `streams` in-place, writing the deduplicated indices to `indices`. ufbx_abi size_t ufbx_generate_indices(const ufbx_vertex_stream *streams, size_t num_streams, uint32_t *indices, size_t num_indices, const ufbx_allocator_opts *allocator, ufbx_error *error); // Thread pool @@ -5183,23 +5571,30 @@ ufbx_abi size_t ufbx_generate_indices(const ufbx_vertex_stream *streams, size_t // See `ufbx_thread_pool_run_fn` for more information. ufbx_unsafe ufbx_abi void ufbx_thread_pool_run_task(ufbx_thread_pool_context ctx, uint32_t index); +// Get or set an arbitrary user pointer for the thread pool context. +// `ufbx_thread_pool_get_user_ptr()` returns `NULL` if unset. ufbx_unsafe ufbx_abi void ufbx_thread_pool_set_user_ptr(ufbx_thread_pool_context ctx, void *user_ptr); ufbx_unsafe ufbx_abi void *ufbx_thread_pool_get_user_ptr(ufbx_thread_pool_context ctx); // -- Inline API +// Utility functions for reading geometry data for a single index. ufbx_abi ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index); ufbx_abi ufbx_vec2 ufbx_catch_get_vertex_vec2(ufbx_panic *panic, const ufbx_vertex_vec2 *v, size_t index); ufbx_abi ufbx_vec3 ufbx_catch_get_vertex_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index); ufbx_abi ufbx_vec4 ufbx_catch_get_vertex_vec4(ufbx_panic *panic, const ufbx_vertex_vec4 *v, size_t index); +// Utility functions for reading geometry data for a single index. ufbx_inline ufbx_real ufbx_get_vertex_real(const ufbx_vertex_real *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } ufbx_inline ufbx_vec2 ufbx_get_vertex_vec2(const ufbx_vertex_vec2 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } ufbx_inline ufbx_vec3 ufbx_get_vertex_vec3(const ufbx_vertex_vec3 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } ufbx_inline ufbx_vec4 ufbx_get_vertex_vec4(const ufbx_vertex_vec4 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values.data[(int32_t)v->indices.data[index]]; } -ufbx_abi size_t ufbx_get_triangulate_face_num_indices(ufbx_face face); +ufbx_abi ufbx_real ufbx_catch_get_vertex_w_vec3(ufbx_panic *panic, const ufbx_vertex_vec3 *v, size_t index); +ufbx_inline ufbx_real ufbx_get_vertex_w_vec3(const ufbx_vertex_vec3 *v, size_t index) { ufbx_assert(index < v->indices.count); return v->values_w.count > 0 ? v->values_w.data[(int32_t)v->indices.data[index]] : 0.0f; } +// Functions for converting an untyped `ufbx_element` to a concrete type. +// Returns `NULL` if the element is not that type. ufbx_abi ufbx_unknown *ufbx_as_unknown(const ufbx_element *element); ufbx_abi ufbx_node *ufbx_as_node(const ufbx_element *element); ufbx_abi ufbx_mesh *ufbx_as_mesh(const ufbx_element *element); @@ -5238,47 +5633,11 @@ ufbx_abi ufbx_selection_set *ufbx_as_selection_set(const ufbx_element *element); ufbx_abi ufbx_selection_node *ufbx_as_selection_node(const ufbx_element *element); ufbx_abi ufbx_character *ufbx_as_character(const ufbx_element *element); ufbx_abi ufbx_constraint *ufbx_as_constraint(const ufbx_element *element); +ufbx_abi ufbx_audio_layer *ufbx_as_audio_layer(const ufbx_element *element); +ufbx_abi ufbx_audio_clip *ufbx_as_audio_clip(const ufbx_element *element); ufbx_abi ufbx_pose *ufbx_as_pose(const ufbx_element *element); ufbx_abi ufbx_metadata_object *ufbx_as_metadata_object(const ufbx_element *element); -// -- FFI API - -ufbx_abi void ufbx_ffi_find_int_len(int64_t *retval, const ufbx_props *props, const char *name, size_t name_len, const int64_t *def); -ufbx_abi void ufbx_ffi_find_vec3_len(ufbx_vec3 *retval, const ufbx_props *props, const char *name, size_t name_len, const ufbx_vec3 *def); -ufbx_abi void ufbx_ffi_find_string_len(ufbx_string *retval, const ufbx_props *props, const char *name, size_t name_len, const ufbx_string *def); -ufbx_abi void ufbx_ffi_find_anim_props(ufbx_anim_prop_list *retval, const ufbx_anim_layer *layer, const ufbx_element *element); -ufbx_abi void ufbx_ffi_get_compatible_matrix_for_normals(ufbx_matrix *retval, const ufbx_node *node); -ufbx_abi void ufbx_ffi_evaluate_anim_value_vec2(ufbx_vec2 *retval, const ufbx_anim_value *anim_value, double time); -ufbx_abi void ufbx_ffi_evaluate_anim_value_vec3(ufbx_vec3 *retval, const ufbx_anim_value *anim_value, double time); -ufbx_abi void ufbx_ffi_evaluate_prop_len(ufbx_prop *retval, const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time); -ufbx_abi void ufbx_ffi_evaluate_props(ufbx_props *retval, const ufbx_anim *anim, ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size); -ufbx_abi void ufbx_ffi_evaluate_transform(ufbx_transform *retval, const ufbx_anim *anim, const ufbx_node *node, double time); -ufbx_abi ufbx_real ufbx_ffi_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time); -ufbx_abi void ufbx_ffi_quat_mul(ufbx_quat *retval, const ufbx_quat *a, const ufbx_quat *b); -ufbx_abi void ufbx_ffi_quat_normalize(ufbx_quat *retval, const ufbx_quat *q); -ufbx_abi void ufbx_ffi_quat_fix_antipodal(ufbx_quat *retval, const ufbx_quat *q, const ufbx_quat *reference); -ufbx_abi void ufbx_ffi_quat_slerp(ufbx_quat *retval, const ufbx_quat *a, const ufbx_quat *b, ufbx_real t); -ufbx_abi void ufbx_ffi_quat_rotate_vec3(ufbx_vec3 *retval, const ufbx_quat *q, const ufbx_vec3 *v); -ufbx_abi void ufbx_ffi_quat_to_euler(ufbx_vec3 *retval, const ufbx_quat *q, ufbx_rotation_order order); -ufbx_abi void ufbx_ffi_euler_to_quat(ufbx_quat *retval, const ufbx_vec3 *v, ufbx_rotation_order order); -ufbx_abi void ufbx_ffi_matrix_mul(ufbx_matrix *retval, const ufbx_matrix *a, const ufbx_matrix *b); -ufbx_abi void ufbx_ffi_matrix_invert(ufbx_matrix *retval, const ufbx_matrix *m); -ufbx_abi void ufbx_ffi_matrix_for_normals(ufbx_matrix *retval, const ufbx_matrix *m); -ufbx_abi void ufbx_ffi_transform_position(ufbx_vec3 *retval, const ufbx_matrix *m, const ufbx_vec3 *v); -ufbx_abi void ufbx_ffi_transform_direction(ufbx_vec3 *retval, const ufbx_matrix *m, const ufbx_vec3 *v); -ufbx_abi void ufbx_ffi_transform_to_matrix(ufbx_matrix *retval, const ufbx_transform *t); -ufbx_abi void ufbx_ffi_matrix_to_transform(ufbx_transform *retval, const ufbx_matrix *m); -ufbx_abi void ufbx_ffi_get_skin_vertex_matrix(ufbx_matrix *retval, const ufbx_skin_deformer *skin, size_t vertex, const ufbx_matrix *fallback); -ufbx_abi void ufbx_ffi_get_blend_shape_vertex_offset(ufbx_vec3 *retval, const ufbx_blend_shape *shape, size_t vertex); -ufbx_abi void ufbx_ffi_get_blend_vertex_offset(ufbx_vec3 *retval, const ufbx_blend_deformer *blend, size_t vertex); -ufbx_abi void ufbx_ffi_evaluate_nurbs_curve(ufbx_curve_point *retval, const ufbx_nurbs_curve *curve, ufbx_real u); -ufbx_abi void ufbx_ffi_evaluate_nurbs_surface(ufbx_surface_point *retval, const ufbx_nurbs_surface *surface, ufbx_real u, ufbx_real v); -ufbx_abi void ufbx_ffi_get_weighted_face_normal(ufbx_vec3 *retval, const ufbx_vertex_vec3 *positions, const ufbx_face *face); -ufbx_abi size_t ufbx_ffi_get_triangulate_face_num_indices(const ufbx_face *face); -ufbx_abi uint32_t ufbx_ffi_triangulate_face(uint32_t *indices, size_t num_indices, const ufbx_mesh *mesh, const ufbx_face *face); -ufbx_abi ufbx_vec3 ufbx_ffi_evaluate_baked_vec3(const ufbx_baked_vec3 *keyframes, size_t num_keyframes, double time); -ufbx_abi ufbx_quat ufbx_ffi_evaluate_baked_quat(const ufbx_baked_quat *keyframes, size_t num_keyframes, double time); - #ifdef __cplusplus } #endif