From fa5823e76e637ab7469dca153ecc1ed0dba60498 Mon Sep 17 00:00:00 2001 From: bqqbarbhg Date: Sat, 2 Mar 2024 02:44:24 +0200 Subject: [PATCH] Update ufbx to v0.11.1 --- Cargo.toml | 2 +- sfs-deps.json | 2 +- sfs-deps.json.lock | 2 +- src/generated.rs | 70 +++++-- ufbx/ufbx.c | 505 +++++++++++++++++++++++++++++++-------------- ufbx/ufbx.h | 129 +++++++++++- 6 files changed, 524 insertions(+), 186 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 809b878..303cbc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ufbx" -version = "0.3.0" +version = "0.4.0" authors = ["bqqbarbhg "] edition = "2018" description = "Bindings for ufbx" diff --git a/sfs-deps.json b/sfs-deps.json index a2a9262..d07109a 100644 --- a/sfs-deps.json +++ b/sfs-deps.json @@ -8,7 +8,7 @@ { "name": "ufbx", "url": "https://github.com/ufbx/ufbx", - "branch": "integration", + "branch": "master", "files": [ { "root": "ufbx", diff --git a/sfs-deps.json.lock b/sfs-deps.json.lock index 6ab7c41..500c8cd 100644 --- a/sfs-deps.json.lock +++ b/sfs-deps.json.lock @@ -1,2 +1,2 @@ sfs=04890ac20b5797f26b523e502141d1fc637f50e5 -ufbx=85ed46443798d8824cb373451052ada6946e522e +ufbx=6dd7771ee215c7489a211b7aa259f1056ce14354 diff --git a/src/generated.rs b/src/generated.rs index f93afdf..70bb6d1 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -398,6 +398,7 @@ pub struct Node { pub geometry_to_node: Matrix, pub geometry_to_world: Matrix, pub unscaled_node_to_world: Matrix, + pub adjust_pre_translation: Vec3, pub adjust_pre_rotation: Quat, pub adjust_pre_scale: Real, pub adjust_post_rotation: Quat, @@ -405,6 +406,7 @@ pub struct Node { pub adjust_translation_scale: Real, pub adjust_mirror_axis: MirrorAxis, pub materials: RefList, + pub bind_pose: Option>, pub visible: bool, pub is_root: bool, pub has_geometry_transform: bool, @@ -1865,6 +1867,7 @@ pub struct Constraint { pub struct BonePose { pub bone_node: Ref, pub bone_to_world: Matrix, + pub bone_to_parent: Matrix, } #[repr(C)] @@ -2361,24 +2364,26 @@ pub enum ErrorType { None = 0, Unknown = 1, FileNotFound = 2, - ExternalFileNotFound = 3, - OutOfMemory = 4, - MemoryLimit = 5, - AllocationLimit = 6, - TruncatedFile = 7, - Io = 8, - Cancelled = 9, - UnrecognizedFileFormat = 10, - UninitializedOptions = 11, - ZeroVertexSize = 12, - TruncatedVertexStream = 13, - InvalidUtf8 = 14, - FeatureDisabled = 15, - BadNurbs = 16, - BadIndex = 17, - ThreadedAsciiParse = 18, - UnsafeOptions = 19, - DuplicateOverride = 20, + EmptyFile = 3, + ExternalFileNotFound = 4, + OutOfMemory = 5, + MemoryLimit = 6, + AllocationLimit = 7, + TruncatedFile = 8, + Io = 9, + Cancelled = 10, + UnrecognizedFileFormat = 11, + UninitializedOptions = 12, + ZeroVertexSize = 13, + TruncatedVertexStream = 14, + InvalidUtf8 = 15, + FeatureDisabled = 16, + BadNurbs = 17, + BadIndex = 18, + NodeDepthLimit = 19, + ThreadedAsciiParse = 20, + UnsafeOptions = 21, + DuplicateOverride = 22, } impl Default for ErrorType { @@ -2515,6 +2520,17 @@ impl Default for InheritModeHandling { fn default() -> Self { Self::Preserve } } +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum PivotHandling { + Retain = 0, + AdjustToPivot = 1, +} + +impl Default for PivotHandling { + fn default() -> Self { Self::Retain } +} + #[repr(C)] pub struct BakedVec3 { pub time: f64, @@ -2622,6 +2638,7 @@ pub struct RawLoadOpts { pub generate_missing_normals: bool, pub open_main_file_with_default: bool, pub path_separator: u8, + pub node_depth_limit: u32, pub file_size_estimate: u64, pub read_buffer_size: usize, pub filename: RawString, @@ -2631,6 +2648,7 @@ pub struct RawLoadOpts { pub open_file_cb: RawOpenFileCb, pub geometry_transform_handling: GeometryTransformHandling, pub inherit_mode_handling: InheritModeHandling, + pub pivot_handling: PivotHandling, pub space_conversion: SpaceConversion, pub handedness_conversion_axis: MirrorAxis, pub handedness_conversion_retain_winding: bool, @@ -2706,6 +2724,7 @@ pub struct RawBakeOpts { pub time_start_offset: f64, pub resample_rate: f64, pub minimum_sample_rate: f64, + pub maximum_sample_rate: f64, pub bake_transform_props: bool, pub skip_node_transforms: bool, pub no_resample_rotation: bool, @@ -3097,6 +3116,7 @@ pub struct LoadOpts<'a> { pub generate_missing_normals: bool, pub open_main_file_with_default: bool, pub path_separator: u8, + pub node_depth_limit: u32, pub file_size_estimate: u64, pub read_buffer_size: usize, pub filename: StringOpt<'a>, @@ -3106,6 +3126,7 @@ pub struct LoadOpts<'a> { pub open_file_cb: OpenFileCb<'a>, pub geometry_transform_handling: GeometryTransformHandling, pub inherit_mode_handling: InheritModeHandling, + pub pivot_handling: PivotHandling, pub space_conversion: SpaceConversion, pub handedness_conversion_axis: MirrorAxis, pub handedness_conversion_retain_winding: bool, @@ -3167,6 +3188,7 @@ impl<'a> FromRust for LoadOpts<'a> { generate_missing_normals: self.generate_missing_normals, open_main_file_with_default: self.open_main_file_with_default, path_separator: self.path_separator, + node_depth_limit: self.node_depth_limit, file_size_estimate: self.file_size_estimate, read_buffer_size: self.read_buffer_size, filename: self.filename.from_rust(arena), @@ -3176,6 +3198,7 @@ impl<'a> FromRust for LoadOpts<'a> { open_file_cb: self.open_file_cb.from_rust(), geometry_transform_handling: self.geometry_transform_handling, inherit_mode_handling: self.inherit_mode_handling, + pivot_handling: self.pivot_handling, space_conversion: self.space_conversion, handedness_conversion_axis: self.handedness_conversion_axis, handedness_conversion_retain_winding: self.handedness_conversion_retain_winding, @@ -3236,6 +3259,7 @@ impl<'a> FromRust for LoadOpts<'a> { generate_missing_normals: self.generate_missing_normals, open_main_file_with_default: self.open_main_file_with_default, path_separator: self.path_separator, + node_depth_limit: self.node_depth_limit, file_size_estimate: self.file_size_estimate, read_buffer_size: self.read_buffer_size, filename: self.filename.from_rust_mut(arena), @@ -3245,6 +3269,7 @@ impl<'a> FromRust for LoadOpts<'a> { open_file_cb: self.open_file_cb.from_rust_mut(), geometry_transform_handling: self.geometry_transform_handling, inherit_mode_handling: self.inherit_mode_handling, + pivot_handling: self.pivot_handling, space_conversion: self.space_conversion, handedness_conversion_axis: self.handedness_conversion_axis, handedness_conversion_retain_winding: self.handedness_conversion_retain_winding, @@ -3397,6 +3422,7 @@ pub struct BakeOpts { pub time_start_offset: f64, pub resample_rate: f64, pub minimum_sample_rate: f64, + pub maximum_sample_rate: f64, pub bake_transform_props: bool, pub skip_node_transforms: bool, pub no_resample_rotation: bool, @@ -3421,6 +3447,7 @@ impl FromRust for BakeOpts { time_start_offset: self.time_start_offset, resample_rate: self.resample_rate, minimum_sample_rate: self.minimum_sample_rate, + maximum_sample_rate: self.maximum_sample_rate, bake_transform_props: self.bake_transform_props, skip_node_transforms: self.skip_node_transforms, no_resample_rotation: self.no_resample_rotation, @@ -3444,6 +3471,7 @@ impl FromRust for BakeOpts { time_start_offset: self.time_start_offset, resample_rate: self.resample_rate, minimum_sample_rate: self.minimum_sample_rate, + maximum_sample_rate: self.maximum_sample_rate, bake_transform_props: self.bake_transform_props, skip_node_transforms: self.skip_node_transforms, no_resample_rotation: self.no_resample_rotation, @@ -3733,6 +3761,7 @@ extern "C" { pub fn ufbx_free_baked_anim(bake: *mut BakedAnim); pub fn ufbx_evaluate_baked_vec3(keyframes: List, time: f64) -> Vec3; pub fn ufbx_evaluate_baked_quat(keyframes: List, time: f64) -> Quat; + pub fn ufbx_get_bone_pose(pose: *const Pose, node: *const Node) -> *mut BonePose; pub fn ufbx_find_prop_texture_len(material: *const Material, name: *const u8, name_len: usize) -> *mut Texture; pub fn ufbx_find_shader_prop_len(shader: *const Shader, name: *const u8, name_len: usize) -> String; pub fn ufbx_find_shader_prop_bindings_len(shader: *const Shader, name: *const u8, name_len: usize) -> List; @@ -4405,6 +4434,11 @@ pub fn evaluate_baked_quat(keyframes: &[BakedQuat], time: f64) -> Quat { result } +pub fn get_bone_pose<'a>(pose: &'a Pose, node: &'a Node) -> Option<&'a BonePose> { + let result = unsafe { ufbx_get_bone_pose(pose as *const Pose, node as *const Node) }; + if result.is_null() { None } else { unsafe { Some(&*result) } } +} + pub fn find_prop_texture<'a>(material: &'a Material, name: &str) -> Option<&'a Texture> { let result = unsafe { ufbx_find_prop_texture_len(material as *const Material, name.as_ptr(), name.len()) }; if result.is_null() { None } else { unsafe { Some(&*result) } } diff --git a/ufbx/ufbx.c b/ufbx/ufbx.c index 9deff21..e6b2c91 100644 --- a/ufbx/ufbx.c +++ b/ufbx/ufbx.c @@ -484,7 +484,7 @@ typedef double ufbxi_unaligned_f64; #endif -#if defined(UFBXI_HAS_UNALIGNED) && ((defined(_M_IX86) || defined(__i386__) || defined(_M_X64) || defined(__x86_64__) || defined(_M_ARM64) || defined(__aarch64__) || defined(__wasm__) || defined(__EMSCRIPTEN__)) && !defined(UFBX_NO_UNALIGNED_LOADS) || defined(UFBX_USE_UNALIGNED_LOADS)) +#if (defined(UFBXI_HAS_UNALIGNED) && UFBX_LITTLE_ENDIAN && !defined(UFBX_NO_UNALIGNED_LOADS)) || defined(UFBX_USE_UNALIGNED_LOADS) #define ufbxi_read_u16(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u16*)(ptr)) #define ufbxi_read_u32(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u32*)(ptr)) #define ufbxi_read_u64(ptr) (*(const ufbxi_unaligned ufbxi_unaligned_u64*)(ptr)) @@ -551,7 +551,7 @@ ufbx_static_assert(sizeof_f64, sizeof(double) == 8); // -- Version -#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 8, 0) +#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 11, 1) const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION; ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEADER_VERSION/1000u); @@ -773,6 +773,8 @@ ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEAD #define ufbxi_dev_assert(cond) (void)0 #endif +#define ufbxi_unreachable(reason) do { ufbx_assert(0 && reason); } while (0) + #if defined(UFBX_REGRESSION) #define UFBXI_IS_REGRESSION 1 #else @@ -1778,8 +1780,8 @@ ufbxi_huff_build_imp(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_coun uint32_t nonzero_sym_count = sym_count - bits_counts[0]; - uint32_t total_syms[UFBXI_HUFF_MAX_BITS]; - uint32_t first_code[UFBXI_HUFF_MAX_BITS]; + uint32_t total_syms[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit + uint32_t first_code[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit tree->code_to_sorted[0] = INT16_MAX; tree->past_max_code[0] = 0; @@ -1978,7 +1980,7 @@ ufbxi_huff_build(ufbxi_huff_tree *tree, uint8_t *sym_bits, uint32_t sym_count, c { // Count the number of codes per bit length // `bits_counts[0]` contains the number of non-used symbols - uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; + uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit memset(bits_counts, 0, sizeof(bits_counts)); for (uint32_t i = 0; i < sym_count; i++) { uint32_t bits = sym_bits[i]; @@ -2038,7 +2040,7 @@ static ufbxi_noinline void ufbxi_init_static_huff(ufbxi_trees *trees, const ufbx } // 0-143: 8 bits, 144-255: 9 bits, 256-279: 7 bits, 280-287: 8 bits - uint8_t lit_length_bits[288]; + uint8_t lit_length_bits[288]; // ufbxi_uninit memset(lit_length_bits + 0, 8, 144 - 0); memset(lit_length_bits + 144, 9, 256 - 144); memset(lit_length_bits + 256, 7, 280 - 256); @@ -2046,7 +2048,7 @@ static ufbxi_noinline void ufbxi_init_static_huff(ufbxi_trees *trees, const ufbx err |= ufbxi_huff_build(&trees->lit_length, lit_length_bits, sizeof(lit_length_bits), ufbxi_deflate_length_lut, 256, trees->fast_bits); // "Distance codes 0-31 are represented by (fixed-length) 5-bit codes" - uint8_t dist_bits[32]; + uint8_t dist_bits[32]; // ufbxi_uninit memset(dist_bits + 0, 5, 32 - 0); err |= ufbxi_huff_build(&trees->dist, dist_bits, sizeof(dist_bits), ufbxi_deflate_dist_lut, 0, trees->fast_bits); @@ -2066,13 +2068,13 @@ static ufbxi_noinline void ufbxi_init_static_huff(ufbxi_trees *trees, const ufbx static ufbxi_noinline ptrdiff_t ufbxi_init_dynamic_huff_tree(ufbxi_deflate_context *dc, const ufbxi_huff_tree *huff_code_length, ufbxi_huff_tree *tree, uint32_t num_symbols, const uint32_t *sym_extra, uint32_t sym_extra_offset, uint32_t fast_bits) { - uint8_t code_lengths[UFBXI_HUFF_MAX_VALUE]; + uint8_t code_lengths[UFBXI_HUFF_MAX_VALUE]; // ufbxi_uninit ufbx_assert(num_symbols <= UFBXI_HUFF_MAX_VALUE); uint64_t bits = dc->stream.bits; size_t left = dc->stream.left; const char *data = dc->stream.chunk_ptr; - uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; + uint32_t bits_counts[UFBXI_HUFF_MAX_BITS]; // ufbxi_uninit memset(bits_counts, 0, sizeof(bits_counts)); uint32_t symbol_index = 0; @@ -2160,7 +2162,7 @@ ufbxi_init_dynamic_huff(ufbxi_deflate_context *dc, ufbxi_trees *trees) // Code lengths for the "code length" Huffman tree are represented literally // 3 bits in order of: 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 up to // `num_code_lengths`, rest of the code lengths are 0 (unused) - uint8_t code_lengths[19]; + uint8_t code_lengths[19]; // ufbxi_uninit memset(code_lengths, 0, sizeof(code_lengths)); for (size_t len_i = 0; len_i < num_code_lengths; len_i++) { if (len_i == 14) { @@ -2176,8 +2178,8 @@ ufbxi_init_dynamic_huff(ufbxi_deflate_context *dc, ufbxi_trees *trees) dc->stream.left = left; dc->stream.chunk_ptr = data; - ufbxi_huff_tree huff_code_length; - ptrdiff_t err; + ufbxi_huff_tree huff_code_length; // ufbxi_uninit + ptrdiff_t err; // ufbxi_uninit // Build the temporary "code length" Huffman tree used to encode the actual // trees used to compress the data. Use that to build the literal/length and @@ -2728,8 +2730,8 @@ ufbxi_extern_c ptrdiff_t ufbx_inflate(void *dst, size_t dst_size, const ufbx_inf dc.stream.left = left; dc.stream.chunk_ptr = data; - ufbxi_trees tree_data; - ufbxi_trees *trees; + ufbxi_trees tree_data; // ufbxi_uninit + ufbxi_trees *trees; // ufbxi_uninit if (type == 1) { // Static Huffman: Initialize the trees once and cache them in `retain`. if (!ret_imp->initialized) { @@ -2821,7 +2823,7 @@ static ufbxi_noinline int ufbxi_vsnprintf(char *buf, size_t buf_size, const char static ufbxi_noinline int ufbxi_snprintf(char *buf, size_t buf_size, const char *fmt, ...) { - va_list args; + va_list args; // ufbxi_uninit va_start(args, fmt); int result = ufbxi_vsnprintf(buf, buf_size, fmt, args); va_end(args); @@ -3017,6 +3019,8 @@ static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *d error->type = UFBX_ERROR_UNRECOGNIZED_FILE_FORMAT; } else if (!strcmp(desc, "File not found")) { error->type = UFBX_ERROR_FILE_NOT_FOUND; + } else if (!strcmp(desc, "Empty file")) { + error->type = UFBX_ERROR_EMPTY_FILE; } else if (!strcmp(desc, "External file not found")) { error->type = UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND; } else if (!strcmp(desc, "Uninitialized options")) { @@ -3033,6 +3037,8 @@ static ufbxi_noinline void ufbxi_fix_error_type(ufbx_error *error, const char *d error->type = UFBX_ERROR_BAD_NURBS; } else if (!strcmp(desc, "Bad index")) { error->type = UFBX_ERROR_BAD_INDEX; + } else if (!strcmp(desc, "Node depth limit exceeded")) { + error->type = UFBX_ERROR_NODE_DEPTH_LIMIT; } else if (!strcmp(desc, "Threaded ASCII parse error")) { error->type = UFBX_ERROR_THREADED_ASCII_PARSE; } else if (!strcmp(desc, "Unsafe options")) { @@ -3108,8 +3114,6 @@ static ufbxi_noinline void *ufbxi_alloc_size(ufbxi_allocator *ator, size_t size, } ator->num_allocs++; - ator->current_size += total; - void *ptr; if (ator->ator.allocator.alloc_fn) { ptr = ator->ator.allocator.alloc_fn(ator->ator.allocator.user, total); @@ -3126,6 +3130,8 @@ static ufbxi_noinline void *ufbxi_alloc_size(ufbxi_allocator *ator, size_t size, } ufbx_assert(((uintptr_t)ptr & ufbxi_size_align_mask(total)) == 0); + ator->current_size += total; + return ptr; } @@ -3150,9 +3156,6 @@ static ufbxi_noinline void *ufbxi_realloc_size(ufbxi_allocator *ator, size_t siz ufbxi_check_return_err_msg(ator->error, ator->num_allocs < ator->max_allocs, NULL, "Allocation limit exceeded"); ator->num_allocs++; - ator->current_size += total; - ator->current_size -= old_total; - void *ptr; if (ator->ator.allocator.realloc_fn) { ptr = ator->ator.allocator.realloc_fn(ator->ator.allocator.user, old_ptr, old_total, total); @@ -3170,6 +3173,9 @@ static ufbxi_noinline void *ufbxi_realloc_size(ufbxi_allocator *ator, size_t siz ufbxi_check_return_err_msg(ator->error, ptr, NULL, "Out of memory"); ufbx_assert(((uintptr_t)ptr & ufbxi_size_align_mask(total)) == 0); + ator->current_size += total; + ator->current_size -= old_total; + return ptr; } @@ -5445,7 +5451,6 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_wait_all(ufbxi_threa ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_init(ufbxi_thread_pool *pool, ufbx_error *error, ufbxi_allocator *ator, const ufbx_thread_opts *opts) { if (!(opts->pool.run_fn && opts->pool.wait_fn)) return 1; - if (!ufbx_is_thread_safe()) return 1; pool->enabled = true; uint32_t num_tasks = (uint32_t)ufbxi_min_sz(opts->num_tasks, INT32_MAX); @@ -5455,7 +5460,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_thread_pool_init(ufbxi_thread_po pool->opts = *opts; if (pool->opts.pool.init_fn) { - ufbx_thread_pool_info info; + ufbx_thread_pool_info info; // ufbxi_uninit info.max_concurrent_tasks = num_tasks; ufbxi_check_err(error, pool->opts.pool.init_fn(pool->opts.pool.user, (ufbx_thread_pool_context)pool, &info)); } @@ -5915,6 +5920,7 @@ typedef struct { ufbxi_buf tmp_node_ids; ufbxi_buf tmp_elements; ufbxi_buf tmp_element_offsets; + ufbxi_buf tmp_element_fbx_ids; ufbxi_buf tmp_element_ptrs; ufbxi_buf tmp_typed_element_offsets[UFBX_ELEMENT_TYPE_COUNT]; ufbxi_buf tmp_mesh_textures; @@ -6002,6 +6008,8 @@ typedef struct { ufbxi_warnings warnings; + bool deferred_failure; + bool parse_threaded; ufbxi_thread_pool thread_pool; @@ -6038,7 +6046,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_report_progress(ufbxi_context *u uint64_t read_offset = ufbxi_get_read_offset(uc); uc->latest_progress_bytes = read_offset; - ufbx_progress progress; + ufbx_progress progress = { 0 }; progress.bytes_read = read_offset; progress.bytes_total = uc->progress_bytes_total; if (progress.bytes_total < progress.bytes_read) { @@ -6069,6 +6077,7 @@ static ufbxi_noinline const char *ufbxi_refill(ufbxi_context *uc, size_t size, b ufbx_assert(uc->data_size < size); ufbxi_check_return(!uc->eof, NULL); if (require_size) { + ufbxi_check_return_msg(uc->read_fn || uc->data_size > 0, NULL, "Empty file"); ufbxi_check_return_msg(uc->read_fn, NULL, "Truncated file"); } else if (!uc->read_fn) { uc->eof = true; @@ -6117,6 +6126,9 @@ static ufbxi_noinline const char *ufbxi_refill(ufbxi_context *uc, size_t size, b } if (require_size) { + if (uc->data_offset == 0) { + ufbxi_check_return_msg(data_size > 0, NULL, "Empty file"); + } ufbxi_check_return_msg(data_size >= size, NULL, "Truncated file"); } @@ -6213,7 +6225,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_skip_bytes(ufbxi_context *uc, ui // Check that we can read at least one byte in case the file is broken // and causes us to seek indefinitely forwards as `fseek()` does not // report if we hit EOF... - char single_byte[1]; + char single_byte[1]; // ufbxi_uninit size_t num_read = uc->read_fn(uc->read_user, single_byte, 1); ufbxi_check_msg(num_read <= 1, "IO error"); ufbxi_check_msg(num_read == 1, "Truncated file"); @@ -6367,7 +6379,7 @@ static ufbxi_noinline FILE *ufbxi_fopen(const char *path, size_t path_len, ufbxi return fopen(path, "rb"); } - char copy_buf[256]; + char copy_buf[256]; // ufbxi_uninit char *copy = NULL; if (path_len < ufbxi_arraycount(copy_buf) - 1) { @@ -7007,7 +7019,7 @@ ufbxi_nodiscard ufbxi_forceinline static int ufbxi_get_val_at(ufbxi_node *node, return 1; } else return 0; default: - ufbx_assert(0 && "Bad format char"); + ufbxi_unreachable("Bad format char"); return 0; } } @@ -7857,7 +7869,7 @@ ufbxi_nodiscard static ufbxi_noinline char *ufbxi_swap_endian(ufbxi_context *uc, } break; default: - ufbx_assert(0 && "Bad endian swap size"); + ufbxi_unreachable("Bad endian swap size"); } return dst; @@ -8137,7 +8149,7 @@ static bool ufbxi_deflate_task_fn(ufbxi_task *task) { ufbxi_deflate_task *t = (ufbxi_deflate_task*)task->data; - ufbx_inflate_input input; + ufbx_inflate_input input; // ufbxi_uninit input.total_size = t->encoded_size; input.data = t->encoded_data; input.data_size = t->encoded_size; @@ -9285,7 +9297,7 @@ typedef enum { ufbxi_noinline static bool ufbxi_ascii_array_task_imp(ufbxi_ascii_array_task *t) { // Temporary buffer for parsing between spans - char buffer[128]; + char buffer[128]; // ufbxi_uninit size_t buffer_len = 0; bool buffer_value = false; @@ -9786,7 +9798,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_parse_node(ufbxi_context * ufbxi_ascii_span *spans = ufbxi_push_pop(tmp_buf, &uc->tmp_ascii_spans, ufbxi_ascii_span, num_spans); ufbxi_check(spans); - ufbxi_ascii_array_task t; + ufbxi_ascii_array_task t; // ufbxi_uninit t.arr_data = (char*)arr_data + num_values * arr_elem_size; t.arr_type = (char)arr_type; t.arr_size = deferred_size; @@ -10267,7 +10279,7 @@ static ufbxi_noinline bool ufbxi_is_format(const char *data, size_t size, ufbx_f if (ufbxi_match(&line, pattern)) return true; } } else { - ufbx_assert(0 && "Unhandled format"); + ufbxi_unreachable("Unhandled format"); } return false; @@ -10288,6 +10300,8 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_determine_format(ufbxi_context * } size_t data_size = ufbxi_min_sz(lookahead, uc->data_size); + ufbxi_check_msg(data_size > 0, "Empty file"); + for (uint32_t fmt = UFBX_FILE_FORMAT_FBX; fmt < UFBX_FILE_FORMAT_COUNT; fmt++) { if (ufbxi_is_format(uc->data, data_size, (ufbx_file_format)fmt)) { format = (ufbx_file_format)fmt; @@ -11220,7 +11234,7 @@ static bool ufbxi_match_version_string(const char *fmt, ufbx_string str, uint32_ if (len == 0) return false; p_version[num_ix++] = num; } else { - ufbx_assert(0 && "Unhandled match character"); + ufbxi_unreachable("Unhandled match character"); } } @@ -11483,6 +11497,7 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_element_size(ufbx 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); uc->tmp_element_byte_offset += aligned_size; ufbx_element *elem = (ufbx_element*)ufbxi_push_zero(&uc->tmp_elements, uint64_t, aligned_size/8); @@ -11532,6 +11547,7 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_element *ufbxi_push_synthetic_element 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_insert_fbx_id(uc, fbx_id, element_id), NULL); return elem; @@ -11747,10 +11763,6 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_model(ufbxi_context *uc, uf elem_node->inherit_mode = UFBX_INHERIT_MODE_NORMAL; } - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES) { - ufbxi_check(ufbxi_setup_geometry_transform_helper(uc, elem_node, info->fbx_id)); - } - return 1; } @@ -11810,7 +11822,7 @@ ufbxi_noinline static int ufbxi_fix_index(ufbxi_context *uc, uint32_t *p_dst, ui *p_dst = index; break; default: - ufbx_assert(0 && "Unhandled index_error_handling"); + ufbxi_unreachable("Unhandled index_error_handling"); return 0; } @@ -12547,24 +12559,25 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_mesh(ufbxi_context *uc, ufb // TODO: Should these be included in output? option? strict mode? ufbxi_node *node_vertices = ufbxi_find_child(node, ufbxi_Vertices); ufbxi_node *node_indices = ufbxi_find_child(node, ufbxi_PolygonVertexIndex); - if (!node_vertices || !node_indices) return 1; + if (!node_vertices) return 1; if (uc->opts.ignore_geometry) return 1; ufbxi_value_array *vertices = ufbxi_get_array(node_vertices, 'r'); - ufbxi_value_array *indices = ufbxi_get_array(node_indices, 'i'); + ufbxi_value_array *indices = node_indices ? ufbxi_get_array(node_indices, 'i') : NULL; ufbxi_value_array *edge_indices = ufbxi_find_array(node, ufbxi_Edges, 'i'); - ufbxi_check(vertices && indices); + ufbxi_check(vertices); + ufbxi_check(!node_indices || indices); // If node_indices exists, it must be an array ufbxi_check(vertices->size % 3 == 0); mesh->num_vertices = vertices->size / 3; - mesh->num_indices = indices->size; + mesh->num_indices = indices ? indices->size : 0; - uint32_t *index_data = (uint32_t*)indices->data; + uint32_t *index_data = indices ? (uint32_t*)indices->data : NULL; // Duplicate `index_data` for modification if we retain DOM if (uc->opts.retain_dom) { - index_data = ufbxi_push_copy(&uc->result, uint32_t, indices->size, index_data); + index_data = ufbxi_push_copy(&uc->result, uint32_t, mesh->num_indices, index_data); ufbxi_check(index_data); } @@ -14139,7 +14152,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects_threaded(ufbxi_cont uc->parse_threaded = true; bool parsed_to_end = false; - ufbxi_object_batch batches[UFBX_THREAD_GROUP_COUNT]; + ufbxi_object_batch batches[UFBX_THREAD_GROUP_COUNT]; // ufbxi_uninit memset(batches, 0, sizeof(batches)); size_t empty_count = 0; @@ -14201,7 +14214,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_objects_threaded(ufbxi_cont size_t max_memory = uc->opts.thread_opts.memory_limit / UFBX_THREAD_GROUP_COUNT; for (;;) { - ufbxi_node *node; + ufbxi_node *node = NULL; ufbxi_check(ufbxi_parse_toplevel_child(uc, &node, tmp_buf)); if (!node) { parsed_to_end = true; @@ -14983,8 +14996,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_legacy_prop(ufbxi_node *nod case '_': break; default: - ufbx_assert(0 && "Unhandled legacy fmt"); - break; + ufbxi_unreachable("Unhandled legacy fmt"); } } @@ -15495,6 +15507,16 @@ static ufbxi_forceinline size_t ufbxi_strblob_length(const ufbxi_strblob *strblo return raw ? strblob->blob.size : strblob->str.length; } +ufbxi_nodiscard ufbxi_noinline static bool ufbxi_is_absolute_path(const char *path, size_t length) +{ + if (length > 0 && (path[0] == '/' || path[0] == '\\')) { + return true; + } else if (length > 2 && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { + return true; + } + return false; +} + ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_relative_filename(ufbxi_context *uc, ufbxi_strblob *p_dst, const ufbxi_strblob *p_src, bool raw) { const char *src = ufbxi_strblob_data(p_src, raw); @@ -15520,6 +15542,11 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_relative_filename(ufbxi_ prefix_length = uc->scene.metadata.relative_root.length; } + // Retain absolute paths + if (ufbxi_is_absolute_path(src, src_length)) { + prefix_length = 0; + } + // Undo directories from `prefix` for every `..` while (prefix_length > 0 && src_length >= 3 && src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\\')) { size_t part_start = prefix_length; @@ -15608,7 +15635,7 @@ static ufbxi_noinline bool ufbxi_open_file(const ufbx_open_file_cb *cb, ufbx_str { if (!cb || !cb->fn) return false; - ufbx_open_file_info info; + ufbx_open_file_info info; // ufbxi_uninit if (ator) { ufbxi_setup_ator_allocator(&info.temp_allocator, ator); } else { @@ -15734,7 +15761,7 @@ ufbx_static_assert(obj_attrib_strides, ufbxi_arraycount(ufbxi_obj_attrib_stride) ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_pop_props(ufbxi_context *uc, ufbx_prop_list *dst, size_t count) { - ufbx_prop_list props; + ufbx_prop_list props; // ufbxi_uninit props.count = count; props.data = ufbxi_push_pop(&uc->result, &uc->obj.tmp_props, ufbx_prop, count); ufbxi_check(props.data); @@ -16054,7 +16081,7 @@ static ufbxi_noinline int ufbxi_obj_parse_vertex(ufbxi_context *uc, ufbxi_obj_at ufbxi_check(vals); for (size_t i = 0; i < read_values; i++) { ufbx_string str = uc->obj.tokens[offset + i]; - char *end; + char *end; // ufbxi_uninit double val = ufbxi_parse_double(str.data, str.length, &end, parse_flags); ufbxi_check(end == str.data + str.length); vals[i] = (ufbx_real)val; @@ -16749,7 +16776,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_prop(ufbxi_context *uc if (start + num_reals >= uc->obj.num_tokens) break; ufbx_string tok = uc->obj.tokens[start + num_reals]; - char *end; + char *end; // ufbxi_uninit double val = ufbxi_parse_double(tok.data, tok.length, &end, uc->double_parse_flags); if (end != tok.data + tok.length) break; @@ -16926,7 +16953,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_load_mtl(ufbxi_context *uc) } if (!has_stream && uc->opts.load_external_files && uc->obj.mtllib_relative_path.size > 0) { - ufbx_blob dst; + ufbx_blob dst; // ufbxi_uninit ufbxi_check(ufbxi_resolve_relative_filename(uc, (ufbxi_strblob*)&dst, (const ufbxi_strblob*)&uc->obj.mtllib_relative_path, true)); has_stream = ufbxi_open_file(&uc->opts.open_file_cb, &stream, (const char*)dst.data, dst.size, &uc->obj.mtllib_relative_path, &uc->ator_tmp, UFBX_OPEN_FILE_OBJ_MTL); stream_path = uc->obj.mtllib_relative_path; @@ -17023,6 +17050,7 @@ typedef struct { typedef struct { bool has_constant_scale; bool has_recursive_scale_helper; + bool has_skin_deformer; ufbx_vec3 constant_scale; uint32_t element_id; uint32_t first_child; @@ -17030,6 +17058,10 @@ typedef struct { uint32_t parent; } ufbxi_pre_node; +typedef struct { + bool has_skin_deformer; +} ufbxi_pre_mesh; + typedef struct { bool has_constant_value; ufbx_vec3 constant_value; @@ -17045,8 +17077,9 @@ typedef struct { ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context *uc) { bool required = false; - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) required = true; - if (uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_PRESERVE && uc->opts.inherit_mode_handling != UFBX_INHERIT_MODE_HANDLING_IGNORE) required = true; + 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.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) required = true; #if defined(UFBX_REGRESSION) required = true; #endif @@ -17080,15 +17113,20 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context ufbxi_pre_node *pre_nodes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_node, num_nodes); ufbxi_check(pre_nodes); + size_t num_meshes = uc->tmp_typed_element_offsets[UFBX_ELEMENT_MESH].num_items; + ufbxi_pre_mesh *pre_meshes = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_mesh, num_meshes); + ufbxi_check(pre_meshes); + size_t num_anim_values = uc->tmp_typed_element_offsets[UFBX_ELEMENT_ANIM_VALUE].num_items; ufbxi_pre_anim_value *pre_anim_values = ufbxi_push_zero(&uc->tmp_parse, ufbxi_pre_anim_value, num_anim_values); ufbxi_check(pre_anim_values); - uint64_t *fbx_ids = ufbxi_push_zero(&uc->tmp_parse, uint64_t, num_elements); + uint64_t *fbx_ids = ufbxi_push_pop(&uc->tmp_parse, &uc->tmp_element_fbx_ids, uint64_t, num_elements); ufbxi_check(fbx_ids); // TODO const ufbx_real scale_epsilon = 0.001f; + const ufbx_real pivot_epsilon = 0.001f; const ufbx_real compensate_epsilon = 0.01f; for (size_t i = 0; i < num_elements; i++) { @@ -17128,9 +17166,6 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context pre->dst = dst; if (!src || !dst) continue; - fbx_ids[src->element_id] = tmp->src; - fbx_ids[dst->element_id] = tmp->dst; - if (tmp->src_prop.length == 0 && tmp->dst_prop.length == 0) { // Count number of instances of each attribute if (dst->type == UFBX_ELEMENT_NODE) { @@ -17170,6 +17205,11 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context } } } + } else if (dst->type == UFBX_ELEMENT_MESH) { + if (src->type == UFBX_ELEMENT_SKIN_DEFORMER) { + ufbxi_pre_mesh *pre_mesh = &pre_meshes[dst->typed_id]; + pre_mesh->has_skin_deformer = true; + } } } else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) { const char *dst_prop = tmp->dst_prop.data; @@ -17209,6 +17249,12 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context if (dst->type == UFBX_ELEMENT_NODE) { if (src->type >= UFBX_ELEMENT_TYPE_FIRST_ATTRIB && src->type <= UFBX_ELEMENT_TYPE_LAST_ATTRIB) { instance_counts[dst->element_id] = ufbxi_max32(instance_counts[dst->element_id], instance_counts[src->element_id]); + if (src->type == UFBX_ELEMENT_MESH) { + ufbxi_pre_mesh *pre_mesh = &pre_meshes[src->typed_id]; + if (pre_mesh->has_skin_deformer) { + pre_nodes[dst->typed_id].has_skin_deformer = true; + } + } } } } else if (tmp->src_prop.length == 0 && tmp->dst_prop.length != 0) { @@ -17236,17 +17282,82 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_pre_finalize_scene(ufbxi_context } } + if (uc->opts.pivot_handling == UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT) { + for (size_t i = 0; i < num_nodes; i++) { + ufbxi_pre_node *pre_node = &pre_nodes[i]; + ufbx_node *node = (ufbx_node*)elements[pre_node->element_id]; + ufbx_vec3 rotation_pivot = ufbxi_find_vec3(&node->props, ufbxi_RotationPivot, 0.0f, 0.0f, 0.0f); + ufbx_vec3 scaling_pivot = ufbxi_find_vec3(&node->props, ufbxi_ScalingPivot, 0.0f, 0.0f, 0.0f); + if (!ufbxi_is_vec3_zero(rotation_pivot)) { + ufbx_real err = 0.0f; + err += (ufbx_real)ufbx_fabs(rotation_pivot.x - scaling_pivot.x); + err += (ufbx_real)ufbx_fabs(rotation_pivot.y - scaling_pivot.y); + err += (ufbx_real)ufbx_fabs(rotation_pivot.z - scaling_pivot.z); + + bool can_modify_geometry_transform = true; + if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY_NO_FALLBACK) { + if (instance_counts[node->element_id] > 1 || modify_not_supported[node->element_id]) { + can_modify_geometry_transform = false; + } + } + // Currently, geometry transform messes up skinning + if (pre_node->has_skin_deformer) { + can_modify_geometry_transform = false; + } + + if (err <= pivot_epsilon && can_modify_geometry_transform) { + size_t num_props = node->props.props.count; + ufbx_prop *new_props = ufbxi_push_zero(&uc->result, ufbx_prop, num_props + 3); + ufbxi_check(new_props); + memcpy(new_props, node->props.props.data, num_props * sizeof(ufbx_prop)); + + ufbx_vec3 geometric_translation = ufbxi_find_vec3(&node->props, ufbxi_GeometricTranslation, 0.0f, 0.0f, 0.0f); + geometric_translation.x -= rotation_pivot.x; + geometric_translation.y -= rotation_pivot.y; + geometric_translation.z -= rotation_pivot.z; + + ufbx_prop *dst = new_props + num_props; + ufbxi_init_synthetic_vec3_prop(&dst[0], ufbxi_RotationPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); + ufbxi_init_synthetic_vec3_prop(&dst[1], ufbxi_ScalingPivot, &ufbx_zero_vec3, UFBX_PROP_VECTOR); + ufbxi_init_synthetic_vec3_prop(&dst[2], ufbxi_GeometricTranslation, &geometric_translation, UFBX_PROP_VECTOR); + + node->props.props.data = new_props; + node->props.props.count = num_props + 3; + ufbxi_check(ufbxi_sort_properties(uc, node->props.props.data, node->props.props.count)); + ufbxi_deduplicate_properties(&node->props.props); + + node->adjust_pre_translation = ufbxi_add3(node->adjust_pre_translation, rotation_pivot); + node->has_adjust_transform = true; + uint32_t ix = pre_node->first_child; + while (ix != ~0u) { + ufbxi_pre_node *pre_child = &pre_nodes[ix]; + ufbx_node *child = (ufbx_node*)elements[pre_child->element_id]; + + child->adjust_pre_translation = ufbxi_sub3(child->adjust_pre_translation, rotation_pivot); + child->has_adjust_transform = true; + + ix = pre_child->next_child; + } + } + } + } + } + for (size_t i = 0; i < num_elements; i++) { ufbx_element *element = elements[i]; uint64_t fbx_id = fbx_ids[i]; if (element->type == UFBX_ELEMENT_NODE) { ufbx_node *node = (ufbx_node*)element; - // Setup a geometry transform helper for nodes that have instanced attributes - if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) { - if (instance_counts[i] > 1 || modify_not_supported[i]) { - ufbxi_check(ufbxi_setup_geometry_transform_helper(uc, node, fbx_id)); - } + bool requires_helper_node = false; + if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_HELPER_NODES) { + requires_helper_node = true; + } else if (uc->opts.geometry_transform_handling == UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY) { + // Setup a geometry transform helper for nodes that have instanced attributes + requires_helper_node = instance_counts[i] > 1 || modify_not_supported[i]; + } + if (requires_helper_node) { + ufbxi_check(ufbxi_setup_geometry_transform_helper(uc, node, fbx_id)); } } } @@ -17730,6 +17841,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_linearize_nodes(ufbxi_context *u ufbxi_check_msg(depth <= num_nodes, "Cyclic node hierarchy"); } + if (uc->opts.node_depth_limit > 0) { + ufbxi_check_msg(depth <= uc->opts.node_depth_limit, "Node depth limit exceeded"); + } node->node_depth = depth; // Second pass to cache the depths to avoid O(n^2) @@ -18087,6 +18201,13 @@ ufbxi_noinline static bool ufbxi_video_ptr_less(void *user, const void *va, cons 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; + const ufbx_bone_pose *a = (const ufbx_bone_pose *)va, *b = (const ufbx_bone_pose *)vb; + return a->bone_node->typed_id < b->bone_node->typed_id; +} + ufbxi_nodiscard ufbxi_noinline static 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*))); @@ -18102,6 +18223,14 @@ ufbxi_nodiscard ufbxi_noinline static ufbx_anim_prop *ufbxi_find_anim_prop_start return index != SIZE_MAX ? &layer->anim_props.data[index] : NULL; } +ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_bone_poses(ufbxi_context *uc, ufbx_pose *pose) +{ + size_t count = pose->bone_poses.count; + ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, pose->bone_poses.count * sizeof(ufbx_bone_pose))); + ufbxi_stable_sort(sizeof(ufbx_bone_pose), 16, pose->bone_poses.data, uc->tmp_arr, count, &ufbxi_bone_pose_less, NULL); + return 1; +} + ufbxi_nodiscard ufbxi_noinline static int ufbxi_sort_skin_weights(ufbxi_context *uc, ufbx_skin_deformer *skin) { ufbxi_check(ufbxi_grow_array(&uc->ator_tmp, &uc->tmp_arr, &uc->tmp_arr_size, skin->max_weights_per_vertex * sizeof(ufbx_skin_weight))); @@ -18950,8 +19079,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_add_constraint_prop(ufbxi_contex target->transform = ufbx_identity_transform; } break; default: - ufbx_assert(0 && "Unexpected constraint prop"); - break; + ufbxi_unreachable("Unexpected constraint prop"); } } @@ -20386,11 +20514,16 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc ufbx_element *elem = ufbxi_find_element_by_fbx_id(uc, tmp_poses[i].bone_fbx_id); if (!elem || elem->type != UFBX_ELEMENT_NODE) continue; + ufbx_node *node = (ufbx_node*)elem; ufbx_bone_pose *bone = &pose->bone_poses.data[pose->bone_poses.count++]; - bone->bone_node = (ufbx_node*)elem; + bone->bone_node = node; bone->bone_to_world = tmp_poses[i].bone_to_world; if (pose->is_bind_pose) { + if (node->bind_pose == NULL) { + node->bind_pose = pose; + } + ufbx_connection_list node_conns = ufbxi_find_src_connections(elem, NULL); ufbxi_for_list(ufbx_connection, conn, node_conns) { if (conn->dst->type != UFBX_ELEMENT_SKIN_CLUSTER) continue; @@ -20401,6 +20534,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc } } } + ufbxi_check(ufbxi_sort_bone_poses(uc, pose)); } // Fetch pointers that may break elements @@ -21407,6 +21541,7 @@ ufbxi_noinline static ufbx_transform ufbxi_get_transform(const ufbx_props *props ufbxi_add_translate(&t, translation); if (node->has_adjust_transform) { + ufbxi_add_translate(&t, node->adjust_pre_translation); ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation); ufbxi_mul_scale_real(&t, node->adjust_pre_scale); t.translation.x *= node->adjust_translation_scale; @@ -21637,7 +21772,7 @@ static const ufbxi_aperture_format ufbxi_aperture_formats[] = { { 2772, 2072, }, // UFBX_APERTURE_FORMAT_IMAX }; -ufbxi_noinline static void ufbxi_update_camera(ufbx_camera *camera) +ufbxi_noinline static void ufbxi_update_camera(ufbx_scene *scene, ufbx_camera *camera) { camera->projection_mode = (ufbx_projection_mode)ufbxi_find_enum(&camera->props, ufbxi_CameraProjectionType, 0, UFBX_PROJECTION_MODE_ORTHOGRAPHIC); camera->aspect_mode = (ufbx_aspect_mode)ufbxi_find_enum(&camera->props, ufbxi_AspectRatioMode, 0, UFBX_ASPECT_MODE_FIXED_HEIGHT); @@ -21688,6 +21823,11 @@ ufbxi_noinline static void ufbxi_update_camera(ufbx_camera *camera) film_size.y *= squeeze_ratio; + // TODO: Should this be done always? + ortho_extent *= scene->metadata.geometry_scale; + camera->near_plane *= scene->metadata.geometry_scale; + camera->far_plane *= scene->metadata.geometry_scale; + camera->focal_length_mm = focal_length; camera->film_size_inch = film_size; camera->squeeze_ratio = squeeze_ratio; @@ -21716,8 +21856,7 @@ ufbxi_noinline static void ufbxi_update_camera(ufbx_camera *camera) camera->resolution.y = aspect_y; break; default: - ufbx_assert(0 && "Unexpected aspect mode"); - break; + ufbxi_unreachable("Unexpected aspect mode"); } ufbx_real aspect_ratio = camera->resolution.x / camera->resolution.y; @@ -21755,7 +21894,7 @@ ufbxi_noinline static void ufbxi_update_camera(ufbx_camera *camera) camera->aperture_size_inch = camera->film_size_inch; camera->orthographic_size.x = ortho_extent; camera->orthographic_size.y = ortho_extent; - ufbx_assert(0 && "Unreachable, set to vertical/horizontal above"); + ufbxi_unreachable("Unreachable, set to vertical/horizontal above"); break; case UFBX_GATE_FIT_STRETCH: camera->aperture_size_inch = camera->film_size_inch; @@ -21764,8 +21903,7 @@ ufbxi_noinline static void ufbxi_update_camera(ufbx_camera *camera) // TODO: Not sure what to do here... break; default: - ufbx_assert(0 && "Unexpected gate fit"); - break; + ufbxi_unreachable("Unexpected gate fit"); } switch (camera->aperture_mode) { @@ -21794,8 +21932,7 @@ ufbxi_noinline static void ufbxi_update_camera(ufbx_camera *camera) camera->field_of_view_deg.y = (ufbx_real)ufbx_atan((double)camera->field_of_view_tan.y) * UFBXI_RAD_TO_DEG * 2.0f; break; default: - ufbx_assert(0 && "Unexpected aperture mode"); - break; + ufbxi_unreachable("Unexpected aperture mode"); } if (camera->projection_mode == UFBX_PROJECTION_MODE_PERSPECTIVE) { @@ -21822,6 +21959,24 @@ ufbxi_noinline static void ufbxi_update_line_curve(ufbx_line_curve *line) line->color = ufbxi_find_vec3(&line->props, ufbxi_Color, 1.0f, 1.0f, 1.0f); } +ufbxi_noinline static void ufbxi_update_pose(ufbx_pose *pose) +{ + ufbxi_for_list(ufbx_bone_pose, bone, pose->bone_poses) { + ufbx_node *node = bone->bone_node; + + const ufbx_matrix *parent_to_world = &ufbx_identity_matrix; + ufbx_bone_pose *bone_pose = ufbx_get_bone_pose(pose, node->parent); + if (bone_pose) { + parent_to_world = &bone_pose->bone_to_world; + } else if (node->parent) { + parent_to_world = &node->parent->node_to_world; + } + + ufbx_matrix world_to_parent = ufbx_matrix_invert(parent_to_world); + bone->bone_to_parent = ufbx_matrix_mul(&world_to_parent, &bone->bone_to_world); + } +} + ufbxi_noinline static void ufbxi_update_skin_cluster(ufbx_skin_cluster *cluster) { if (cluster->bone_node) { @@ -21967,8 +22122,8 @@ ufbxi_noinline static void ufbxi_update_constraint(ufbx_constraint *constraint) weight_scale = (ufbx_real)1.0; } - ufbx_prop *prop; - ufbx_string parts[2]; + ufbx_prop *prop; // ufbxi_uninit + ufbx_string parts[2]; // ufbxi_uninit parts[0] = node->name; parts[1] = ufbxi_str_c(".Weight"); prop = ufbx_find_prop_concat(props, parts, 2); @@ -22228,7 +22383,7 @@ ufbxi_noinline static void ufbxi_update_adjust_transforms(ufbxi_context *uc, ufb bool has_camera_transform = false; if (ufbx_coordinate_axes_valid(uc->opts.target_light_axes)) { - ufbx_matrix mat; + ufbx_matrix mat; // ufbxi_uninit ufbx_coordinate_axes light_axes = { UFBX_COORDINATE_AXIS_POSITIVE_X, UFBX_COORDINATE_AXIS_NEGATIVE_Z, @@ -22244,7 +22399,7 @@ ufbxi_noinline static void ufbxi_update_adjust_transforms(ufbxi_context *uc, ufb } if (ufbx_coordinate_axes_valid(uc->opts.target_camera_axes)) { - ufbx_matrix mat; + ufbx_matrix mat; // ufbxi_uninit ufbx_coordinate_axes camera_axes = { UFBX_COORDINATE_AXIS_POSITIVE_Z, UFBX_COORDINATE_AXIS_POSITIVE_Y, @@ -22342,7 +22497,7 @@ ufbxi_noinline static void ufbxi_update_scene(ufbx_scene *scene, bool initial, c } ufbxi_for_ptr_list(ufbx_camera, p_camera, scene->cameras) { - ufbxi_update_camera(*p_camera); + ufbxi_update_camera(scene, *p_camera); } ufbxi_for_ptr_list(ufbx_bone, p_bone, scene->bones) { @@ -22355,6 +22510,10 @@ ufbxi_noinline static void ufbxi_update_scene(ufbx_scene *scene, bool initial, c if (initial) { ufbxi_update_initial_clusters(scene); + + ufbxi_for_ptr_list(ufbx_pose, p_pose, scene->poses) { + ufbxi_update_pose(*p_pose); + } } ufbxi_for_ptr_list(ufbx_skin_cluster, p_cluster, scene->skin_clusters) { @@ -22604,7 +22763,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_skip(ufbxi_cache_context * // Check that we can read at least one byte in case the file is broken // and causes us to seek indefinitely forwards as `fseek()` does not // report if we hit EOF... - char single_byte[1]; + char single_byte[1]; // ufbxi_uninit size_t num_read = cc->stream.read_fn(cc->stream.user, single_byte, 1); ufbxi_check_err_msg(&cc->error, num_read <= 1, "IO error"); ufbxi_check_err_msg(&cc->error, num_read == 1, "Truncated file"); @@ -22615,7 +22774,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_skip(ufbxi_cache_context * } } else { - char skip_buf[2048]; + char skip_buf[2048]; // ufbxi_uninit while (size > 0) { size_t to_skip = (size_t)ufbxi_min64(size, sizeof(skip_buf)); size -= to_skip; @@ -22630,7 +22789,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_skip(ufbxi_cache_context * ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_tag(ufbxi_cache_context *cc, uint32_t *p_tag) { - char buf[4]; + char buf[4]; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, true)); *p_tag = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; if (*p_tag == ufbxi_cache_mc_tag('F','O','R','8')) { @@ -22641,7 +22800,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_tag(ufbxi_cache_co ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u32(ufbxi_cache_context *cc, uint32_t *p_value) { - char buf[4]; + char buf[4]; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 4, false)); *p_value = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; if (cc->mc_for8) { @@ -22653,11 +22812,11 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u32(ufbxi_cache_co ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_mc_read_u64(ufbxi_cache_context *cc, uint64_t *p_value) { if (!cc->mc_for8) { - uint32_t v32; + uint32_t v32; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_u32(cc, &v32)); *p_value = v32; } else { - char buf[8]; + char buf[8]; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, buf, 8, false)); uint32_t hi = (uint32_t)(uint8_t)buf[0]<<24u | (uint32_t)(uint8_t)buf[1]<<16 | (uint32_t)(uint8_t)buf[2]<<8u | (uint32_t)(uint8_t)buf[3]; uint32_t lo = (uint32_t)(uint8_t)buf[4]<<24u | (uint32_t)(uint8_t)buf[5]<<16 | (uint32_t)(uint8_t)buf[6]<<8u | (uint32_t)(uint8_t)buf[7]; @@ -22674,11 +22833,11 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_mc(ufbxi_cache_contex { uint32_t version = 0, time_start = 0, time_end = 0; uint32_t count = 0, time = 0; - char skip_buf[8]; + char skip_buf[8]; // ufbxi_uninit for (;;) { - uint32_t tag; - uint64_t size; + uint32_t tag; // ufbxi_uninit + uint64_t size; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_mc_read_tag(cc, &tag)); if (tag == 0) break; @@ -22755,7 +22914,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_mc(ufbxi_cache_contex ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_pc2(ufbxi_cache_context *cc) { - char header[32]; + char header[32]; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, header, sizeof(header), false)); uint32_t version = ufbxi_read_u32(header + 12); @@ -22777,7 +22936,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_cache_load_pc2(ufbxi_cache_conte // Skip almost to the end of the data and try to read one byte as there's // nothing after the data so we can't detect EOF.. if (total_points > 0) { - char last_byte[1]; + char last_byte[1]; // ufbxi_uninit ufbxi_check_err(&cc->error, ufbxi_cache_skip(cc, total_points * 12 - 1)); ufbxi_check_err(&cc->error, ufbxi_cache_read(cc, last_byte, 1, false)); } @@ -23232,7 +23391,7 @@ ufbxi_noinline static ufbx_geometry_cache *ufbxi_cache_load(ufbxi_cache_context ufbxi_noinline static ufbx_geometry_cache *ufbxi_load_geometry_cache(ufbx_string filename, const ufbx_geometry_cache_opts *user_opts, ufbx_error *p_error) { - ufbx_geometry_cache_opts opts; + ufbx_geometry_cache_opts opts; // ufbxi_uninit if (user_opts) { opts = *user_opts; } else { @@ -23617,8 +23776,6 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_skinning(ufbx_scene *sc if (num_read == num_normals) { cached_normals = true; mesh->skinned_normal.values.data = normal_data; - } else { - ufbxi_pop(buf_result, ufbx_vec3, num_normals + 1, NULL); } } } @@ -23719,6 +23876,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_resolve_warning_elements(ufbxi_c 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"); @@ -23749,6 +23909,10 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) uc->scene.metadata.creator.data = ufbxi_empty_char; uc->unit_scale = 1.0f; + if (uc->data == NULL) { + ufbxi_dev_assert(uc->data_begin == NULL); + uc->data_begin = uc->data = ufbxi_zero_size_buffer; + } ufbxi_check(ufbxi_load_strings(uc)); ufbxi_check(ufbxi_load_maps(uc)); @@ -23895,6 +24059,7 @@ static ufbxi_noinline void ufbxi_free_temp(ufbxi_context *uc) ufbxi_buf_free(&uc->tmp_node_ids); ufbxi_buf_free(&uc->tmp_elements); ufbxi_buf_free(&uc->tmp_element_offsets); + ufbxi_buf_free(&uc->tmp_element_fbx_ids); ufbxi_buf_free(&uc->tmp_element_ptrs); for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { ufbxi_buf_free(&uc->tmp_typed_element_offsets[i]); @@ -24014,6 +24179,7 @@ static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_ uc->tmp_node_ids.ator = &uc->ator_tmp; uc->tmp_elements.ator = &uc->ator_tmp; uc->tmp_element_offsets.ator = &uc->ator_tmp; + uc->tmp_element_fbx_ids.ator = &uc->ator_tmp; uc->tmp_element_ptrs.ator = &uc->ator_tmp; for (size_t i = 0; i < UFBX_ELEMENT_TYPE_COUNT; i++) { uc->tmp_typed_element_offsets[i].ator = &uc->ator_tmp; @@ -24071,6 +24237,15 @@ static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_ } } +static ufbxi_noinline ufbx_scene *ufbxi_load_not_found(const char *filename, size_t filename_len, ufbx_error *p_error) +{ + ufbxi_context uc = { UFBX_ERROR_NONE }; + ufbxi_set_err_info(&uc.error, filename, filename_len); + ufbxi_report_err_msg(&uc.error, "File not found", "File not found"); + uc.deferred_failure = true; + return ufbxi_load(&uc, NULL, p_error); +} + // -- Animation evaluation static ufbxi_forceinline bool ufbxi_override_less_than_prop(const ufbx_prop_override *over, uint32_t element_id, const ufbx_prop *prop) @@ -24384,7 +24559,7 @@ static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim * size_t name_ix = 0; - ufbxi_prop_iter iter; + ufbxi_prop_iter iter; // ufbxi_uninit ufbxi_init_prop_iter(&iter, anim, element); const ufbx_prop *prop = NULL; while ((prop = ufbxi_next_prop(&iter)) != NULL) { @@ -24551,6 +24726,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context node->camera = (ufbx_camera*)ufbxi_translate_element(ec, node->camera); node->inherit_scale_node = (ufbx_node*)ufbxi_translate_element(ec, node->inherit_scale_node); node->scale_helper = (ufbx_node*)ufbxi_translate_element(ec, node->scale_helper); + node->bind_pose = (ufbx_pose*)ufbxi_translate_element(ec, node->bind_pose); if (node->all_attribs.count > 1) { ufbxi_check_err(&ec->error, ufbxi_translate_element_list(ec, &node->all_attribs)); @@ -25234,6 +25410,38 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_finalize_bake_times(ufbxi_bake_c num_times = dst; } + // Enforce maximum sample rate + if (bc->opts.maximum_sample_rate > 0.0) { + const double epsilon = 0.0078125 / bc->opts.maximum_sample_rate; + double sample_rate = bc->opts.maximum_sample_rate; + double min_interval = 1.0 / bc->opts.maximum_sample_rate - epsilon; + size_t dst = 0, src = 0; + + double prev_time = -UFBX_INFINITY; + while (src < num_times) { + double 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) { + src++; + } + + if (src != start_src || src_time - prev_time <= min_interval) { + prev_time = next_time; + } else { + prev_time = src_time; + } + + if (dst == 0 || prev_time > times[dst - 1]) { + times[dst++] = prev_time; + } + } + + num_times = dst; + } + p_dst->data = times; p_dst->count = num_times; @@ -26120,7 +26328,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_tessellate_nurbs_surface_imp(ufb ufbx_vec3 tangent_v = ufbxi_slow_normalize3(&point.derivative_v); // Check if there's any wrapped positions that we could match - size_t neighbors[5]; + size_t neighbors[5]; // ufbxi_uninit size_t num_neighbors = 0; if ((span_v == 0 && (span_u > 0 || split_u > 0)) || (span_u == 0 && (span_v > 0 || split_v > 0))) { @@ -26550,7 +26758,7 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui normal.z = 0.0f; } - ufbx_vec3 axis; + ufbx_vec3 axis; // ufbxi_uninit if (normal.x*normal.x < 0.5f) { axis.x = 1.0f; axis.y = 0.0f; @@ -26614,7 +26822,7 @@ 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_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]]); @@ -26717,7 +26925,7 @@ ufbxi_noinline static uint32_t ufbxi_triangulate_ngon(ufbxi_ngon_context *nc, ui // them to a stack buffer and copy them over in the end. uint32_t max_triangles = face.num_indices - 2; uint32_t num_triangles = 0, num_last_triangles = 0; - uint32_t last_triangles[4*3]; + uint32_t last_triangles[4*3]; // ufbxi_uninit uint32_t index_begin = face.index_begin; for (uint32_t ix = 0; ix < face.num_indices; ix++) { @@ -27176,8 +27384,7 @@ static ufbxi_noinline int ufbxi_subdivide_layer(ufbxi_subdivide_context *sc, ufb sharp_all = true; break; default: - ufbx_assert(0 && "Bad boundary mode"); - break; + ufbxi_unreachable("Bad boundary mode"); } ufbxi_subdivide_sum_fn *sum_fn = input->sum_fn; @@ -27296,7 +27503,7 @@ static ufbxi_noinline int ufbxi_subdivide_layer(ufbxi_subdivide_context *sc, ufb size_t num_split = 0; bool on_boundary = false; bool non_manifold = false; - size_t crease_input_indices[2]; + size_t crease_input_indices[2]; // ufbxi_uninit // At start we always have two edges and a single face uint32_t start_prev = topo[start].prev; @@ -27540,7 +27747,7 @@ static ufbxi_noinline int ufbxi_subdivide_attrib(ufbxi_subdivide_context *sc, uf ufbx_assert(attrib->value_reals >= 1 && attrib->value_reals <= 4); - ufbxi_subdivide_layer_input input; + ufbxi_subdivide_layer_input input; // ufbxi_uninit input.sum_fn = ufbxi_real_sum_fns[attrib->value_reals - 1]; input.sum_user = NULL; input.values = attrib->values.data; @@ -27550,7 +27757,7 @@ static ufbxi_noinline int ufbxi_subdivide_attrib(ufbxi_subdivide_context *sc, uf input.check_split_data = check_split_data; input.ignore_indices = false; - ufbxi_subdivide_layer_output output; + ufbxi_subdivide_layer_output output; // ufbxi_uninit ufbxi_check_err(&sc->error, ufbxi_subdivide_layer(sc, &output, &input)); attrib->values.data = output.values; @@ -27623,7 +27830,7 @@ static ufbxi_noinline int ufbxi_subdivide_weights(ufbxi_subdivide_context *sc, u { ufbxi_check_err(&sc->error, src); - ufbxi_subdivide_layer_input input; + ufbxi_subdivide_layer_input input; // ufbxi_uninit input.sum_fn = ufbxi_subdivide_sum_vertex_weights; input.sum_user = sc; input.values = src; @@ -27635,7 +27842,7 @@ static ufbxi_noinline int ufbxi_subdivide_weights(ufbxi_subdivide_context *sc, u sc->total_weights = 0; - ufbxi_subdivide_layer_output output; + ufbxi_subdivide_layer_output output; // ufbxi_uninit ufbxi_check_err(&sc->error, ufbxi_subdivide_layer(sc, &output, &input)); size_t num_vertices = output.num_values; @@ -28186,8 +28393,8 @@ static ufbxi_noinline size_t ufbxi_generate_indices(const ufbx_vertex_stream *us ufbxi_allocator ator = { 0 }; ufbxi_init_ator(error, &ator, allocator, "allocator"); - ufbxi_vertex_stream local_streams[16]; - uint64_t local_packed_vertex[64]; + ufbxi_vertex_stream local_streams[16]; // ufbxi_uninit + uint64_t local_packed_vertex[64]; // ufbxi_uninit ufbxi_vertex_stream *streams = NULL; if (num_streams > ufbxi_arraycount(local_streams)) { @@ -28470,7 +28677,7 @@ ufbx_abi bool ufbx_default_open_file(void *user, ufbx_stream *stream, const char ufbx_abi bool ufbx_open_memory(ufbx_stream *stream, const void *data, size_t data_size, const ufbx_open_memory_opts *opts, ufbx_error *error) { - ufbx_open_memory_opts local_opts; + ufbx_open_memory_opts local_opts; // ufbxi_uninit if (!opts) { memset(&local_opts, 0, sizeof(local_opts)); opts = &local_opts; @@ -28561,18 +28768,7 @@ ufbx_abi ufbx_scene *ufbx_load_file_len(const char *filename, size_t filename_le if (ufbxi_open_file(&opts->open_file_cb, &stream, filename, filename_len, NULL, NULL, UFBX_OPEN_FILE_MAIN_MODEL)) { return ufbx_load_stream_prefix(&stream, NULL, 0, &opts_copy, error); } else { - // TODO: Factor this? - ufbxi_set_err_info(error, filename, filename_len); - error->stack_size = 1; - error->type = UFBX_ERROR_FILE_NOT_FOUND; - error->description.data = "File not found"; - error->description.length = strlen(error->description.data); - error->stack[0].description.data = "File not found"; - error->stack[0].description.length = strlen(error->stack[0].description.data); - error->stack[0].function.data = ufbxi_function; - error->stack[0].function.length = strlen(ufbxi_function); - error->stack[0].source_line = ufbxi_line; - return NULL; + return ufbxi_load_not_found(filename, filename_len, error); } } @@ -28582,19 +28778,7 @@ ufbx_abi ufbx_scene *ufbx_load_file_len(const char *filename, size_t filename_le FILE *file = ufbxi_fopen(filename, filename_len, &tmp_ator); if (!file) { - if (error) { - ufbxi_set_err_info(error, filename, filename_len); - error->stack_size = 1; - error->type = UFBX_ERROR_FILE_NOT_FOUND; - error->description.data = "File not found"; - error->description.length = strlen(error->description.data); - error->stack[0].description.data = "File not found"; - error->stack[0].description.length = strlen(error->stack[0].description.data); - error->stack[0].function.data = ufbxi_function; - error->stack[0].function.length = strlen(ufbxi_function); - error->stack[0].source_line = ufbxi_line; - } - return NULL; + return ufbxi_load_not_found(filename, filename_len, error); } ufbx_scene *scene = ufbx_load_stdio(file, &opts_copy, error); @@ -28623,7 +28807,7 @@ ufbx_abi ufbx_scene *ufbx_load_stdio_prefix(void *file_void, const void *prefix, if (opts && opts->progress_cb.fn && opts->file_size_estimate == 0) { uint64_t begin = ufbxi_ftell(file); if (begin < UINT64_MAX) { - fpos_t pos; + fpos_t pos; // ufbxi_uninit if (fgetpos(file, &pos) == 0) { if (fseek(file, 0, SEEK_END) == 0) { uint64_t end = ufbxi_ftell(file); @@ -28977,7 +29161,7 @@ ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time } default: - ufbx_assert(0 && "Bad interpolation mode"); + ufbxi_unreachable("Bad interpolation mode"); return 0.0f; } @@ -29066,7 +29250,7 @@ ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, co if (!element) return ret; size_t num_anim = 0; - ufbxi_prop_iter iter; + ufbxi_prop_iter iter; // ufbxi_uninit ufbxi_init_prop_iter(&iter, anim, element); const ufbx_prop *prop = NULL; while ((prop = ufbxi_next_prop(&iter)) != NULL) { @@ -29155,7 +29339,7 @@ ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform_flags(const ufbx_ } const ufbx_vec3 *translation_scale = NULL; - ufbx_prop helper_scale; + ufbx_prop helper_scale; // ufbxi_uninit ufbx_vec3 scale_factor = ufbxi_one_vec3; bool use_scale_factor = false; @@ -29192,11 +29376,11 @@ ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform_flags(const ufbx_ } } - ufbx_prop buf[ufbxi_arraycount(ufbxi_transform_props_all)]; + ufbx_prop buf[ufbxi_arraycount(ufbxi_transform_props_all)]; // ufbxi_uninit ufbx_props props = ufbxi_evaluate_selected_props(anim, &node->element, time, buf, prop_names, num_prop_names); ufbx_rotation_order order = (ufbx_rotation_order)ufbxi_find_enum(&props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC); - ufbx_transform transform; + ufbx_transform transform; // ufbxi_uninit if ((components & UFBX_TRANSFORM_FLAG_INCLUDE_TRANSLATION) != 0) { transform = ufbxi_get_transform(&props, order, node, translation_scale); } else { @@ -29227,7 +29411,7 @@ ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_ ufbxi_DeformPercent, }; - ufbx_prop buf[ufbxi_arraycount(prop_names)]; + ufbx_prop buf[ufbxi_arraycount(prop_names)]; // ufbxi_uninit ufbx_props props = ufbxi_evaluate_selected_props(anim, &channel->element, time, buf, prop_names, ufbxi_arraycount(prop_names)); return ufbxi_find_real(&props, ufbxi_DeformPercent, channel->weight * (ufbx_real)100.0) * (ufbx_real)0.01; } @@ -29419,6 +29603,15 @@ ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, doub return keyframes.data[keyframes.count - 1].value; } +ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node) +{ + if (!pose || !node) return NULL; + size_t index = SIZE_MAX; + ufbxi_macro_lower_bound_eq(ufbx_bone_pose, 8, &index, pose->bone_poses.data, 0, pose->bone_poses.count, + ( a->bone_node->typed_id < node->typed_id ), ( a->bone_node == node )); + return index < SIZE_MAX ? &pose->bone_poses.data[index] : NULL; +} + ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len) { ufbx_string name_str = ufbxi_safe_string(name, name_len); @@ -29432,12 +29625,11 @@ ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, ufbx_abi ufbx_string ufbx_find_shader_prop_len(const ufbx_shader *shader, const char *name, size_t name_len) { - ufbx_string name_str = ufbxi_safe_string(name, name_len); ufbx_shader_prop_binding_list bindings = ufbx_find_shader_prop_bindings_len(shader, name, name_len); if (bindings.count > 0) { - return bindings.data[0].shader_prop; + return bindings.data[0].material_prop; } - return name_str; + return ufbx_empty_string; } ufbx_abi ufbx_shader_prop_binding_list ufbx_find_shader_prop_bindings_len(const ufbx_shader *shader, const char *name, size_t name_len) @@ -29512,6 +29704,7 @@ ufbx_abi ufbxi_noinline ufbx_quat ufbx_quat_normalize(ufbx_quat q) { ufbx_real norm = ufbx_quat_dot(q, q); if (norm == 0.0) return ufbx_identity_quat; + norm = (ufbx_real)ufbx_sqrt(norm); q.x /= norm; q.y /= norm; q.z /= norm; @@ -30002,7 +30195,7 @@ ufbx_abi ufbxi_noinline ufbx_matrix ufbx_catch_get_skin_vertex_matrix(ufbx_panic } if (skin_vertex.dq_weight > 0.0f) { - ufbx_transform dqt; + ufbx_transform dqt; // ufbxi_uninit ufbx_real rcp_len = (ufbx_real)(1.0 / ufbx_sqrt(q0.x*q0.x + q0.y*q0.y + q0.z*q0.z + q0.w*q0.w)); ufbx_real rcp_len2x2 = 2.0f * rcp_len * rcp_len; dqt.rotation.x = q0.x * rcp_len; @@ -30176,8 +30369,8 @@ ufbx_abi ufbxi_noinline ufbx_curve_point ufbx_evaluate_nurbs_curve(const ufbx_nu ufbx_assert(curve); if (!curve) return result; - ufbx_real weights[UFBXI_MAX_NURBS_ORDER]; - ufbx_real derivs[UFBXI_MAX_NURBS_ORDER]; + ufbx_real weights[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit + ufbx_real derivs[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit size_t base = ufbx_evaluate_nurbs_basis(&curve->basis, u, weights, UFBXI_MAX_NURBS_ORDER, derivs, UFBXI_MAX_NURBS_ORDER); if (base == SIZE_MAX) return result; @@ -30222,8 +30415,8 @@ ufbx_abi ufbxi_noinline ufbx_surface_point ufbx_evaluate_nurbs_surface(const ufb ufbx_assert(surface); if (!surface) return result; - ufbx_real weights_u[UFBXI_MAX_NURBS_ORDER], weights_v[UFBXI_MAX_NURBS_ORDER]; - ufbx_real derivs_u[UFBXI_MAX_NURBS_ORDER], derivs_v[UFBXI_MAX_NURBS_ORDER]; + ufbx_real weights_u[UFBXI_MAX_NURBS_ORDER], weights_v[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit + ufbx_real derivs_u[UFBXI_MAX_NURBS_ORDER], derivs_v[UFBXI_MAX_NURBS_ORDER]; // ufbxi_uninit size_t base_u = ufbx_evaluate_nurbs_basis(&surface->basis_u, u, weights_u, UFBXI_MAX_NURBS_ORDER, derivs_u, UFBXI_MAX_NURBS_ORDER); size_t base_v = ufbx_evaluate_nurbs_basis(&surface->basis_v, v, weights_v, UFBXI_MAX_NURBS_ORDER, derivs_v, UFBXI_MAX_NURBS_ORDER); if (base_u == SIZE_MAX || base_v == SIZE_MAX) return result; @@ -30463,7 +30656,7 @@ ufbx_abi ufbxi_noinline uint32_t ufbx_catch_triangulate_face(ufbx_panic *panic, uint32_t num_indices_u32 = num_indices < UINT32_MAX ? (uint32_t)num_indices : UINT32_MAX; - uint32_t local_indices[12]; + uint32_t local_indices[12]; // ufbxi_uninit if (num_indices_u32 < 12) { uint32_t num_tris = ufbxi_triangulate_ngon(&nc, local_indices, 12); memcpy(indices, local_indices, num_tris * 3 * sizeof(uint32_t)); @@ -30716,7 +30909,7 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr ufbx_assert(data); if (!data) return 0; - ufbx_geometry_cache_data_opts opts; + ufbx_geometry_cache_data_opts opts; // ufbxi_uninit if (user_opts) { opts = *user_opts; } else { @@ -30741,7 +30934,7 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr case UFBX_CACHE_DATA_FORMAT_VEC3_FLOAT: src_count = frame->data_count * 3; break; case UFBX_CACHE_DATA_FORMAT_REAL_DOUBLE: src_count = frame->data_count; use_double = true; break; case UFBX_CACHE_DATA_FORMAT_VEC3_DOUBLE: src_count = frame->data_count * 3; use_double = true; break; - default: ufbx_assert(0 && "Bad data_format"); break; + default: ufbxi_unreachable("Bad data_format"); break; } bool src_big_endian = false; @@ -30749,7 +30942,7 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr case UFBX_CACHE_DATA_ENCODING_UNKNOWN: return 0; case UFBX_CACHE_DATA_ENCODING_LITTLE_ENDIAN: src_big_endian = false; break; case UFBX_CACHE_DATA_ENCODING_BIG_ENDIAN: src_big_endian = true; break; - default: ufbx_assert(0 && "Bad data_encoding"); break; + default: ufbxi_unreachable("Bad data_encoding"); break; } // Test endianness @@ -30778,7 +30971,7 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr offset -= to_skip; } } else { - char buffer[4096]; + char buffer[4096]; // ufbxi_uninit while (offset > 0) { size_t to_skip = (size_t)ufbxi_min64(offset, sizeof(buffer)); size_t num_read = stream.read_fn(stream.user, buffer, to_skip); @@ -30798,7 +30991,7 @@ 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]; + 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; @@ -30857,7 +31050,7 @@ ufbx_abi ufbxi_noinline size_t ufbx_read_geometry_cache_real(const ufbx_cache_fr if (num_read != to_read) break; } } else { - float buffer[1024]; + 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; @@ -31035,7 +31228,7 @@ ufbx_abi ufbx_dom_node *ufbx_dom_find_len(const ufbx_dom_node *parent, const cha 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) { - ufbx_error local_error; + ufbx_error local_error; // ufbxi_uninit if (!error) { error = &local_error; } diff --git a/ufbx/ufbx.h b/ufbx/ufbx.h index 9566fed..bb33110 100644 --- a/ufbx/ufbx.h +++ b/ufbx/ufbx.h @@ -95,11 +95,11 @@ #endif #ifndef ufbx_assert - #if !defined(UFBX_NO_ASSERT) + #if defined(UFBX_NO_ASSERT) + #define ufbx_assert(cond) (void)0 + #else #include #define ufbx_assert(cond) assert(cond) - #else - #define ufbx_assert(cond) (void)0 #endif #endif @@ -219,7 +219,7 @@ struct ufbx_converter { }; #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, 8, 0) +#define UFBX_HEADER_VERSION ufbx_pack_version(0, 11, 1) #define UFBX_VERSION UFBX_HEADER_VERSION // -- Basic types @@ -871,6 +871,7 @@ struct ufbx_node { // ufbx-specific adjustment for switching between coodrinate/unit systems. // HINT: In most cases you don't need to deal with these as these are baked // into all the transforms above and into `ufbx_evaluate_transform()`. + ufbx_vec3 adjust_pre_translation; // < Translation applied between parent and self ufbx_quat adjust_pre_rotation; // < Rotation applied between parent and self ufbx_real adjust_pre_scale; // < Scaling applied between parent and self ufbx_quat adjust_post_rotation; // < Rotation applied in local space at the end @@ -883,6 +884,9 @@ struct ufbx_node { // in the `ufbx_node` instances. ufbx_material_list materials; + // Bind pose + ufbx_nullable ufbx_pose *bind_pose; + // Visibility state. bool visible; @@ -1928,7 +1932,6 @@ struct ufbx_skin_cluster { // Matrix that specifies the rest/bind pose transform of the node, // not generally needed for skinning, use `geometry_to_bone` instead. - // NOTE: This does not account for unit scaling! ufbx_matrix bind_to_world; // Precomputed matrix/transform that accounts for the current bone transform @@ -2151,11 +2154,24 @@ struct ufbx_cache_file { 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; ufbx_cache_file_format format; @@ -2726,11 +2742,25 @@ typedef struct ufbx_texture_file { uint32_t index; // Paths to the resource. + + // 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 @@ -2753,11 +2783,25 @@ struct ufbx_texture { ufbx_texture_type type; // FILE: Paths to the resource + + // 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; // FILE: Optional embedded content blob, eg. raw .png format data @@ -2808,11 +2852,25 @@ struct ufbx_video { }; }; // Paths to the resource + + // 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 @@ -3191,9 +3249,18 @@ struct ufbx_constraint { // -- Miscellaneous typedef struct ufbx_bone_pose { + + // Node to apply the pose to. ufbx_node *bone_node; - // NOTE: This does not account for unit scaling! + + // Matrix from node local space to world space. ufbx_matrix bone_to_world; + + // Matrix from node local space to parent space. + // NOTE: FBX only stores world transformations so this is approximated from + // the parent world transform. + ufbx_matrix bone_to_parent; + } ufbx_bone_pose; UFBX_LIST_TYPE(ufbx_bone_pose_list, ufbx_bone_pose); @@ -3206,7 +3273,11 @@ struct ufbx_pose { uint32_t typed_id; }; }; + // Set if this pose is marked as a bind pose. bool is_bind_pose; + + // List of bone poses. + // Sorted by `ufbx_node.typed_id`. ufbx_bone_pose_list bone_poses; }; @@ -3853,6 +3924,9 @@ typedef enum ufbx_error_type UFBX_ENUM_REPR { // File not found. UFBX_ERROR_FILE_NOT_FOUND, + // Empty file. + UFBX_ERROR_EMPTY_FILE, + // External file not found. // See `ufbx_load_opts.load_external_files` for more information. UFBX_ERROR_EXTERNAL_FILE_NOT_FOUND, @@ -3906,6 +3980,9 @@ typedef enum ufbx_error_type UFBX_ENUM_REPR { // Out of bounds index in the file when loading with `UFBX_INDEX_ERROR_HANDLING_ABORT_LOADING`. UFBX_ERROR_BAD_INDEX, + // Node is deeper than `ufbx_load_opts.node_depth_limit` in the hierarchy. + UFBX_ERROR_NODE_DEPTH_LIMIT, + // Error parsing ASCII array in a thread. // Threaded ASCII parsing is slightly more strict than non-threaded, for cursed files, // set `ufbx_load_opts.force_single_thread_ascii_parsing` to `true`. @@ -4113,6 +4190,23 @@ typedef enum ufbx_inherit_mode_handling UFBX_ENUM_REPR { UFBX_ENUM_TYPE(ufbx_inherit_mode_handling, UFBX_INHERIT_MODE_HANDLING, UFBX_INHERIT_MODE_HANDLING_IGNORE); +// How to handle FBX transform pivots. +typedef enum ufbx_pivot_handling UFBX_ENUM_REPR { + + // Take pivots into account when computing the transform. + UFBX_PIVOT_HANDLING_RETAIN, + + // Translate objects to be located at their pivot. + // NOTE: Only applied if rotation and scaling pivots are equal. + // NOTE: Results in geometric translation. Use `ufbx_geometry_transform_handling` + // to interpret these in a standard scene graph. + UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT, + + UFBX_ENUM_FORCE_WIDTH(UFBX_PIVOT_HANDLING) +} ufbx_pivot_handling; + +UFBX_ENUM_TYPE(ufbx_pivot_handling, UFBX_PIVOT_HANDLING, UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT); + typedef struct ufbx_baked_vec3 { double time; ufbx_vec3 value; @@ -4229,7 +4323,7 @@ typedef struct ufbx_load_opts { // a bit of memory and time if not needed bool skip_skin_vertices; - // Skip computing `ufbx_mesh.material_parts[]` and `ufbx_mesh.group_parts[]`. + // Skip computing `ufbx_mesh.material_parts[]` and `ufbx_mesh.face_group_parts[]`. bool skip_mesh_parts; // Clean-up skin weights by removing negative, zero and NAN weights. @@ -4279,6 +4373,12 @@ typedef struct ufbx_load_opts { // Path separator character, defaults to '\' on Windows and '/' otherwise. char path_separator; + // Maximum depth of the node hirerachy. + // Will fail with `UFBX_ERROR_NODE_DEPTH_LIMIT` if a node is deeper than this limit. + // NOTE: The default of 0 allows arbitrarily deep hierarchies. Be careful if using + // recursive algorithms without setting this limit. + uint32_t node_depth_limit; + // Estimated file size for progress reporting uint64_t file_size_estimate; @@ -4309,6 +4409,10 @@ typedef struct ufbx_load_opts { // See `ufbx_inherit_mode_handling` for an explanation. ufbx_inherit_mode_handling inherit_mode_handling; + // How to handle pivots. + // See `ufbx_pivot_handling` for an explanation. + ufbx_pivot_handling pivot_handling; + // How to perform space conversion by `target_axes` and `target_unit_meters`. // See `ufbx_space_conversion` for an explanation. ufbx_space_conversion space_conversion; @@ -4495,6 +4599,10 @@ typedef struct ufbx_bake_opts { // Default: 19.5 double minimum_sample_rate; + // Maximum sample rate to use, this will remove keys if they are too close together. + // Default: unlimited + double maximum_sample_rate; + // Bake the raw versions of properties related to transforms. bool bake_transform_props; @@ -4532,7 +4640,7 @@ typedef struct ufbx_bake_opts { // Default: `4` size_t key_reduction_passes; - // Compensate for `UFBX_INHERIT_NO_SCALE` by adjusting child scale. + // 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; @@ -4914,6 +5022,10 @@ ufbx_abi void ufbx_free_baked_anim(ufbx_baked_anim *bake); ufbx_abi ufbx_vec3 ufbx_evaluate_baked_vec3(ufbx_baked_vec3_list keyframes, double time); ufbx_abi ufbx_quat ufbx_evaluate_baked_quat(ufbx_baked_quat_list keyframes, double time); +// Poses + +ufbx_abi ufbx_bone_pose *ufbx_get_bone_pose(const ufbx_pose *pose, const ufbx_node *node); + // Materials ufbx_abi ufbx_texture *ufbx_find_prop_texture_len(const ufbx_material *material, const char *name, size_t name_len); @@ -5074,7 +5186,6 @@ ufbx_unsafe ufbx_abi void ufbx_thread_pool_run_task(ufbx_thread_pool_context ctx 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 ufbx_abi ufbx_real ufbx_catch_get_vertex_real(ufbx_panic *panic, const ufbx_vertex_real *v, size_t index);