summaryrefslogtreecommitdiffstats
path: root/thirdparty
diff options
context:
space:
mode:
authorbqqbarbhg <bqqbarbhg@gmail.com>2024-05-03 21:07:25 +0300
committerbqqbarbhg <bqqbarbhg@gmail.com>2024-05-07 14:27:21 +0300
commit5cd7ae198d2f8df63cc1890b0b80c0a93c63590e (patch)
tree95cab1a4e99aa07b37c704e73659237d47c4f9ac /thirdparty
parent03e6fbb010c3546593bd91a0dabc045a9882705a (diff)
downloadredot-engine-5cd7ae198d2f8df63cc1890b0b80c0a93c63590e.tar.gz
Update ufbx to v0.14.0
Diffstat (limited to 'thirdparty')
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/ufbx/ufbx.c1897
-rw-r--r--thirdparty/ufbx/ufbx.h545
3 files changed, 1555 insertions, 889 deletions
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 4a7ab7314a..255915b655 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 e6b2c91c65..6d584369a7 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 <xmmintrin.h>
+ #include <emmintrin.h>
+#else
+ #define UFBXI_HAS_SSE 0
+#endif
+
+#if !defined(UFBX_LITTLE_ENDIAN)
+ #if !defined(UFBX_STANDARD_C) && (defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__))
+ #define UFBX_LITTLE_ENDIAN 1
+ #else
+ #define UFBX_LITTLE_ENDIAN 0
+ #endif
+#endif
+
// Unaligned little-endian load functions
// On platforms that support unaligned access natively (x86, x64, ARM64) just use normal loads,
// with unaligned attributes, otherwise do manual byte-wise load.
@@ -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 <xmmintrin.h>
- #include <emmintrin.h>
-#else
- #define UFBXI_HAS_SSE 0
-#endif
-
-#if !defined(UFBX_LITTLE_ENDIAN)
- #if !defined(UFBX_STANDARD_C) && (defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__))
- #define UFBX_LITTLE_ENDIAN 1
- #else
- #define UFBX_LITTLE_ENDIAN 0
- #endif
-#endif
-
// -- 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
@@ -5792,6 +5839,11 @@ typedef struct {
} ufbxi_tmp_anim_stack;
typedef struct {
+ ufbx_string absolute_filename;
+ ufbx_blob content;
+} ufbxi_file_content;
+
+typedef struct {
// Current line and tokens.
// NOTE: `line` and `tokens` are not NULL-terminated nor UTF-8!
@@ -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, &times_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, &times));
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 bb331102a5..072569068a 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