From 533daa55526f04e4daf269fd44a4addf75215f64 Mon Sep 17 00:00:00 2001 From: Jakub Marcowski Date: Sun, 6 Apr 2025 15:04:27 +0200 Subject: [PATCH] ufbx: Update to 0.18.0 --- thirdparty/README.md | 2 +- thirdparty/ufbx/ufbx.c | 415 +++++++++++++++++++++++++++++++++++------ thirdparty/ufbx/ufbx.h | 78 +++++++- 3 files changed, 432 insertions(+), 63 deletions(-) diff --git a/thirdparty/README.md b/thirdparty/README.md index 9591ea294ab..66abf4fc7c5 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -974,7 +974,7 @@ Patches: ## ufbx - Upstream: https://github.com/ufbx/ufbx -- Version: 0.17.1 (6ca5309972f03625e6990f3084ff4c1cc55a09b6, 2025) +- Version: 0.18.0 (729ab835444f5f229e5f7cff332692ce6c00415d, 2025) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/ufbx/ufbx.c b/thirdparty/ufbx/ufbx.c index 0e6f13d2f51..f5ce84741e7 100644 --- a/thirdparty/ufbx/ufbx.c +++ b/thirdparty/ufbx/ufbx.c @@ -270,6 +270,7 @@ #define ufbx_fmax ufbxi_math_fn(fmax) #define ufbx_nextafter ufbxi_math_fn(nextafter) #define ufbx_rint ufbxi_math_fn(rint) + #define ufbx_floor ufbxi_math_fn(floor) #define ufbx_ceil ufbxi_math_fn(ceil) #define ufbx_isnan ufbxi_math_fn(isnan) #endif @@ -296,6 +297,7 @@ extern "C" { ufbx_extern_abi double ufbx_copysign(double x, double y); ufbx_extern_abi double ufbx_nextafter(double x, double y); ufbx_extern_abi double ufbx_rint(double x); + ufbx_extern_abi double ufbx_floor(double x); ufbx_extern_abi double ufbx_ceil(double x); ufbx_extern_abi int ufbx_isnan(double x); #endif @@ -532,6 +534,10 @@ extern "C" { #pragma GCC diagnostic ignored "-Wc99-c11-compat" #endif #endif + // MSC isnan() definition triggers this error on MinGW GCC + #if defined(__MINGW32__) + #pragma GCC diagnostic ignored "-Wfloat-conversion" + #endif #endif #if !defined(ufbx_static_assert) @@ -830,7 +836,7 @@ ufbx_static_assert(sizeof_f64, sizeof(double) == 8); // -- Version -#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 17, 1) +#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 18, 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); @@ -1656,7 +1662,7 @@ static ufbxi_noinline double ufbxi_parse_double(const char *str, size_t max_leng } } -static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags() +static ufbxi_noinline uint32_t ufbxi_parse_double_init_flags(void) { // We require evaluation in double precision, either for doubles (0) or always (1) // and rounding to nearest, which we can check for with `1 + eps == 1 - eps`. @@ -5198,6 +5204,7 @@ static const char ufbxi_Edges[] = "Edges"; static const char ufbxi_EmissiveColor[] = "EmissiveColor"; static const char ufbxi_Entry[] = "Entry"; static const char ufbxi_FBXHeaderExtension[] = "FBXHeaderExtension"; +static const char ufbxi_FBXHeaderVersion[] = "FBXHeaderVersion"; static const char ufbxi_FBXVersion[] = "FBXVersion"; static const char ufbxi_FKEffector[] = "FKEffector"; static const char ufbxi_FarPlane[] = "FarPlane"; @@ -5307,6 +5314,7 @@ static const char ufbxi_OriginalUnitScaleFactor[] = "OriginalUnitScaleFactor"; static const char ufbxi_OriginalUpAxis[] = "OriginalUpAxis"; static const char ufbxi_OriginalUpAxisSign[] = "OriginalUpAxisSign"; static const char ufbxi_OrthoZoom[] = "OrthoZoom"; +static const char ufbxi_OtherFlags[] = "OtherFlags"; static const char ufbxi_OuterAngle[] = "OuterAngle"; static const char ufbxi_PO[] = "PO\0"; static const char ufbxi_PP[] = "PP\0"; @@ -5317,7 +5325,9 @@ static const char ufbxi_PolygonIndexArray[] = "PolygonIndexArray"; static const char ufbxi_PolygonVertexIndex[] = "PolygonVertexIndex"; static const char ufbxi_PoseNode[] = "PoseNode"; static const char ufbxi_Pose[] = "Pose"; +static const char ufbxi_Post_Extrapolation[] = "Post-Extrapolation"; static const char ufbxi_PostRotation[] = "PostRotation"; +static const char ufbxi_Pre_Extrapolation[] = "Pre-Extrapolation"; static const char ufbxi_PreRotation[] = "PreRotation"; static const char ufbxi_PreviewDivisionLevels[] = "PreviewDivisionLevels"; static const char ufbxi_Properties60[] = "Properties60"; @@ -5330,6 +5340,7 @@ static const char ufbxi_ReferenceTime[] = "ReferenceTime"; static const char ufbxi_RelativeFileName[] = "RelativeFileName"; static const char ufbxi_RelativeFilename[] = "RelativeFilename"; static const char ufbxi_RenderDivisionLevels[] = "RenderDivisionLevels"; +static const char ufbxi_Repetition[] = "Repetition"; static const char ufbxi_RightCamera[] = "RightCamera"; static const char ufbxi_RootNode[] = "RootNode"; static const char ufbxi_Root[] = "Root"; @@ -5360,6 +5371,7 @@ static const char ufbxi_SpecularColor[] = "SpecularColor"; static const char ufbxi_Step[] = "Step"; static const char ufbxi_SubDeformer[] = "SubDeformer"; static const char ufbxi_T[] = "T\0\0"; +static const char ufbxi_TCDefinition[] = "TCDefinition"; static const char ufbxi_Take[] = "Take"; static const char ufbxi_Takes[] = "Takes"; static const char ufbxi_Tangents[] = "Tangents"; @@ -5493,6 +5505,7 @@ static const ufbx_string ufbxi_strings[] = { { ufbxi_EmissiveColor, 13 }, { ufbxi_Entry, 5 }, { ufbxi_FBXHeaderExtension, 18 }, + { ufbxi_FBXHeaderVersion, 16 }, { ufbxi_FBXVersion, 10 }, { ufbxi_FKEffector, 10 }, { ufbxi_FarPlane, 8 }, @@ -5602,6 +5615,7 @@ static const ufbx_string ufbxi_strings[] = { { ufbxi_OriginalUpAxis, 14 }, { ufbxi_OriginalUpAxisSign, 18 }, { ufbxi_OrthoZoom, 9 }, + { ufbxi_OtherFlags, 10 }, { ufbxi_OuterAngle, 10 }, { ufbxi_PO, 2 }, { ufbxi_PP, 2 }, @@ -5612,7 +5626,9 @@ static const ufbx_string ufbxi_strings[] = { { ufbxi_PolygonVertexIndex, 18 }, { ufbxi_Pose, 4 }, { ufbxi_PoseNode, 8 }, + { ufbxi_Post_Extrapolation, 18 }, { ufbxi_PostRotation, 12 }, + { ufbxi_Pre_Extrapolation, 17 }, { ufbxi_PreRotation, 11 }, { ufbxi_PreviewDivisionLevels, 21 }, { ufbxi_Properties60, 12 }, @@ -5625,6 +5641,7 @@ static const ufbx_string ufbxi_strings[] = { { ufbxi_RelativeFileName, 16 }, { ufbxi_RelativeFilename, 16 }, { ufbxi_RenderDivisionLevels, 20 }, + { ufbxi_Repetition, 10 }, { ufbxi_RightCamera, 11 }, { ufbxi_Root, 4 }, { ufbxi_RootNode, 8 }, @@ -5655,6 +5672,7 @@ static const ufbx_string ufbxi_strings[] = { { ufbxi_Step, 4 }, { ufbxi_SubDeformer, 11 }, { ufbxi_T, 1 }, + { ufbxi_TCDefinition, 12 }, { ufbxi_Take, 4 }, { ufbxi_Takes, 5 }, { ufbxi_Tangents, 8 }, @@ -7506,11 +7524,16 @@ static ufbxi_noinline ufbxi_node *ufbxi_find_child(ufbxi_node *node, const char return NULL; } +// Retrieve the type of a given value +ufbxi_forceinline static ufbxi_value_type ufbxi_get_val_type(ufbxi_node *node, size_t ix) +{ + return (ufbxi_value_type)((node->value_type_mask >> (ix*2)) & 0x3); +} + // Retrieve values from nodes with type codes: // Any: '_' (ignore) // NUMBER: 'I' int32_t 'L' int64_t 'F' float 'D' double 'R' ufbxi_real 'B' bool 'Z' size_t // STRING: 'S' ufbx_string 'C' const char* (checked) 's' ufbx_string 'c' const char * (unchecked) 'b' ufbx_blob - ufbxi_nodiscard ufbxi_forceinline static int ufbxi_get_val_at(ufbxi_node *node, size_t ix, char fmt, void *v) { ufbxi_dev_assert(ix < UFBXI_MAX_NON_ARRAY_VALUES); @@ -9542,7 +9565,7 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_ascii_next_token(ufbxi_context * if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') { token->type = UFBXI_ASCII_BARE_WORD; while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9') || c == '_') { + || (c >= '0' && c <= '9') || c == '_' || c == '-') { ufbxi_check(ufbxi_ascii_push_token_char(uc, token, c)); c = ufbxi_ascii_next(uc); } @@ -11216,6 +11239,7 @@ static const ufbxi_prop_type_name ufbxi_prop_type_names[] = { { "Integer", UFBX_PROP_INTEGER }, { "int", UFBX_PROP_INTEGER }, { "enum", UFBX_PROP_INTEGER }, + { "Enum", UFBX_PROP_INTEGER }, { "Visibility", UFBX_PROP_INTEGER }, { "Visibility Inheritance", UFBX_PROP_INTEGER }, { "KTime", UFBX_PROP_INTEGER }, @@ -11498,7 +11522,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_init_node_prop_names(ufbxi_conte return 1; } -static bool ufbxi_is_node_property(ufbxi_context *uc, const char *name) +static bool ufbxi_is_node_property_name(ufbxi_context *uc, const char *name) { // You need to call `ufbxi_init_node_prop_names()` before calling this ufbx_assert(uc->node_prop_set.size > 0); @@ -11604,8 +11628,11 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_property(ufbxi_context *uc, flags |= (uint32_t)UFBX_PROP_FLAG_VALUE_REAL << (real_ix - 1); } - // Distance properties have a string unit _after_ the real value, eg. `10, "cm"` - if (prop->type == UFBX_PROP_DISTANCE) { + // Skip one value forward in case the current value is not a string, as some properties + // contain mixed numbers and strings. Currenltly known cases: + // Lod Distance: P: "Thresholds|Level0", "Distance", "", "",64, "cm" + // User Enum: P: "User_Enum", "Enum", "", "A+U",1, "ValueA~ValueB~ValueC" + if (ufbxi_get_val_type(node, val_ix) != UFBXI_VALUE_STRING) { val_ix++; } @@ -11742,9 +11769,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_scene_info(ufbxi_context *u ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_header_extension(ufbxi_context *uc) { - // TODO: Read TCDefinition and adjust timestamps - uc->ktime_sec = 46186158000; - uc->ktime_sec_double = (double)uc->ktime_sec; + bool has_tc_definition = false; + int32_t tc_definition = 0; + int32_t header_version = 0; for (;;) { ufbxi_node *child; @@ -11764,12 +11791,33 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_read_header_extension(ufbxi_cont } } + if (child->name == ufbxi_FBXHeaderVersion) { + ufbxi_ignore(ufbxi_get_val1(child, "I", &header_version)); + } + + if (child->name == ufbxi_OtherFlags) { + if (ufbxi_find_val1(child, ufbxi_TCDefinition, "I", &tc_definition)) { + has_tc_definition = true; + } + } + if (child->name == ufbxi_SceneInfo) { ufbxi_check(ufbxi_read_scene_info(uc, child)); } } + // FBX 8000 will change the KTime units and the new units are opt-in currently via `TCDefinition`. + // `TCDefinition` seems be accounted in all versions, as long as `FBXHeaderVersion >= 1004`. + // The old KTime units are specified as the value `127` and all other values seem to use the new definition. + bool use_v7_ktime = uc->version < 8000; + if (header_version >= 1004 && has_tc_definition) { + use_v7_ktime = tc_definition == 127; + } + + uc->ktime_sec = use_v7_ktime ? 46186158000 : 141120000; + uc->ktime_sec_double = (double)uc->ktime_sec; + return 1; } @@ -13935,11 +13983,44 @@ static void ufbxi_solve_tcb(float *p_slope_left, float *p_slope_right, double te *p_slope_right = (float)(d10 * slope_left + d11 * slope_right); } +ufbxi_noinline static void ufbxi_read_extrapolation(ufbx_extrapolation *p_extrapolation, ufbxi_node *node, const char *name) +{ + ufbxi_node *child = ufbxi_find_child(node, name); + ufbx_extrapolation_mode mode = UFBX_EXTRAPOLATION_CONSTANT; + int32_t repeat_count = -1; + + if (child) { + int32_t mode_ch; + if (ufbxi_find_val1(child, ufbxi_Type, "I", &mode_ch)) { + + switch (mode_ch) { + case 'A': mode = UFBX_EXTRAPOLATION_REPEAT_RELATIVE; break; + case 'C': mode = UFBX_EXTRAPOLATION_CONSTANT; break; + case 'K': mode = UFBX_EXTRAPOLATION_SLOPE; break; + case 'M': mode = UFBX_EXTRAPOLATION_MIRROR; break; + case 'R': mode = UFBX_EXTRAPOLATION_REPEAT; break; + default: /* Unknown */ break; + } + if (ufbxi_find_val1(child, ufbxi_Repetition, "I", &repeat_count)) { + if (repeat_count < 0) { + repeat_count = -1; + } + } + } + } + + p_extrapolation->mode = mode; + p_extrapolation->repeat_count = repeat_count; +} + ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_animation_curve(ufbxi_context *uc, ufbxi_node *node, ufbxi_element_info *info) { ufbx_anim_curve *curve = ufbxi_push_element(uc, info, ufbx_anim_curve, UFBX_ELEMENT_ANIM_CURVE); ufbxi_check(curve); + ufbxi_read_extrapolation(&curve->pre_extrapolation, node, ufbxi_Pre_Extrapolation); + ufbxi_read_extrapolation(&curve->post_extrapolation, node, ufbxi_Post_Extrapolation); + if (uc->opts.ignore_animation) return 1; ufbxi_value_array *times, *values, *attr_flags, *attrs, *refs; @@ -14537,27 +14618,26 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_synthetic_attribute(ufbxi_c // 6x00: Link the node to the node attribute so property connections can be // redirected from connections if necessary. - if (uc->version < 7000) { - ufbxi_check(ufbxi_insert_fbx_attr(uc, info->fbx_id, attrib_info.fbx_id)); + ufbxi_check(ufbxi_insert_fbx_attr(uc, info->fbx_id, attrib_info.fbx_id)); - // Split properties between the node and the attribute - ufbx_prop *ps = info->props.props.data; - size_t dst = 0, src = 0, end = info->props.props.count; - while (src < end) { - if (!ufbxi_is_node_property(uc, ps[src].name.data)) { - ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, 1, &ps[src])); - src++; - } else if (dst != src) { - ps[dst++] = ps[src++]; - } else { - dst++; src++; - } + // Split properties between the node and the attribute. + // Consider all user properties as node properties. + ufbx_prop *ps = info->props.props.data; + size_t dst = 0, src = 0, end = info->props.props.count; + while (src < end) { + if (!ufbxi_is_node_property_name(uc, ps[src].name.data) && (ps[src].flags & UFBX_PROP_FLAG_USER_DEFINED) == 0) { + ufbxi_check(ufbxi_push_copy(&uc->tmp_stack, ufbx_prop, 1, &ps[src])); + src++; + } else if (dst != src) { + ps[dst++] = ps[src++]; + } else { + dst++; src++; } - attrib_info.props.props.count = end - dst; - attrib_info.props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, attrib_info.props.props.count); - ufbxi_check(attrib_info.props.props.data); - info->props.props.count = dst; } + attrib_info.props.props.count = end - dst; + attrib_info.props.props.data = ufbxi_push_pop(&uc->result, &uc->tmp_stack, ufbx_prop, attrib_info.props.props.count); + ufbxi_check(attrib_info.props.props.data); + info->props.props.count = dst; if (sub_type == ufbxi_Mesh) { ufbxi_check(ufbxi_read_mesh(uc, node, &attrib_info)); @@ -14995,6 +15075,9 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_take_anim_channel(ufbxi_con ufbxi_check(ufbxi_connect_op(uc, curve_fbx_id, value_fbx_id, curve->name)); + ufbxi_read_extrapolation(&curve->pre_extrapolation, node, ufbxi_Pre_Extrapolation); + ufbxi_read_extrapolation(&curve->post_extrapolation, node, ufbxi_Post_Extrapolation); + if (uc->opts.ignore_animation) return 1; size_t num_keys = 0; @@ -15440,6 +15523,11 @@ ufbxi_noinline static void ufbxi_setup_root_node(ufbxi_context *uc, ufbx_node *r root->is_root = true; } +static ufbxi_forceinline bool ufbxi_supports_version(uint32_t version) +{ + return version >= 3000 && version <= 7700; +} + ufbxi_nodiscard ufbxi_noinline static int ufbxi_read_root(ufbxi_context *uc) { // FBXHeaderExtension: Some metadata (optional) @@ -17335,6 +17423,8 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_obj_parse_file(ufbxi_context *uc uc->obj.mtllib_relative_path.size = lib.length; } else if (ufbxi_str_equal(cmd, ufbxi_str_c("usemtl"))) { ufbxi_check(ufbxi_obj_parse_material(uc)); + } else if (!uc->opts.disable_quirks && key == 0) { + // ZBrush exporter seems to end the files with '\0', sometimes.. } else { ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNKNOWN_OBJ_DIRECTIVE, "Unknown .obj directive, skipped line")); } @@ -18168,15 +18258,21 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_resolve_connections(ufbxi_contex uc->scene.connections_src.data = ufbxi_push(&uc->result, ufbx_connection, num_connections); ufbxi_check(uc->scene.connections_src.data); - // HACK: Translate property connections from node to attribute if - // the property name is not included in the known node properties. + // HACK: Translate property connections from node to attribute if the property name is not included + // in the known node properties and is not a property of the node. if (uc->version > 0 && uc->version < 7000) { ufbxi_for(ufbxi_tmp_connection, tmp_conn, tmp_connections, num_connections) { - if (tmp_conn->src_prop.length > 0 && !ufbxi_is_node_property(uc, tmp_conn->src_prop.data)) { - tmp_conn->src = ufbxi_find_attribute_fbx_id(uc, tmp_conn->src); + if (tmp_conn->src_prop.length > 0 && !ufbxi_is_node_property_name(uc, tmp_conn->src_prop.data)) { + ufbx_element *src = ufbxi_find_element_by_fbx_id(uc, tmp_conn->src); + if (!src || !ufbx_find_prop_len(&src->props, tmp_conn->src_prop.data, tmp_conn->src_prop.length)) { + tmp_conn->src = ufbxi_find_attribute_fbx_id(uc, tmp_conn->src); + } } - if (tmp_conn->dst_prop.length > 0 && !ufbxi_is_node_property(uc, tmp_conn->dst_prop.data)) { - tmp_conn->dst = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst); + if (tmp_conn->dst_prop.length > 0 && !ufbxi_is_node_property_name(uc, tmp_conn->dst_prop.data)) { + ufbx_element *dst = ufbxi_find_element_by_fbx_id(uc, tmp_conn->dst); + if (!dst || !ufbx_find_prop_len(&dst->props, tmp_conn->dst_prop.data, tmp_conn->dst_prop.length)) { + tmp_conn->dst = ufbxi_find_attribute_fbx_id(uc, tmp_conn->dst); + } } } } @@ -18882,6 +18978,8 @@ typedef enum { UFBXI_SHADER_MAPPING_DEFAULT_W_1 = 0x1, // Widen values to RGB if only a single value is present. UFBXI_SHADER_MAPPING_WIDEN_TO_RGB = 0x2, + // Multiply the existing value. + UFBXI_SHADER_MAPPING_MULTIPLY_VALUE = 0x4, } ufbxi_shader_mapping_flag; typedef enum { @@ -19187,6 +19285,56 @@ static const ufbxi_shader_mapping ufbxi_gltf_material_pbr_mapping[] = { { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("extension|indexOfRefraction") }, }; +static const ufbxi_shader_mapping ufbxi_openpbr_material_pbr_mapping[] = { + { UFBX_MATERIAL_PBR_BASE_FACTOR, 0, 0, ufbxi_mat_string("base_weight") }, + { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, + { UFBX_MATERIAL_PBR_ROUGHNESS, 0, 0, ufbxi_mat_string("specular_roughness") }, + { UFBX_MATERIAL_PBR_DIFFUSE_ROUGHNESS, 0, 0, ufbxi_mat_string("base_diffuse_roughness") }, + { UFBX_MATERIAL_PBR_METALNESS, 0, 0, ufbxi_mat_string("base_metalness") }, + { UFBX_MATERIAL_PBR_SPECULAR_FACTOR, 0, 0, ufbxi_mat_string("specular_weight") }, + { UFBX_MATERIAL_PBR_SPECULAR_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("specular_color") }, + { UFBX_MATERIAL_PBR_SPECULAR_ANISOTROPY, 0, 0, ufbxi_mat_string("specular_roughness_anisotropy") }, + { UFBX_MATERIAL_PBR_SPECULAR_IOR, 0, 0, ufbxi_mat_string("specular_ior") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_FACTOR, 0, 0, ufbxi_mat_string("transmission_weight") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("transmission_color") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DEPTH, 0, 0, ufbxi_mat_string("transmission_depth") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("transmission_scatter") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_SCATTER_ANISOTROPY, 0, 0, ufbxi_mat_string("transmission_scatter_anisotropy") }, + { UFBX_MATERIAL_PBR_TRANSMISSION_DISPERSION, 0, 0, ufbxi_mat_string("transmission_dispersion_scale") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_FACTOR, 0, 0, ufbxi_mat_string("subsurface_weight") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("subsurface_color") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_RADIUS, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("subsurface_radius_scale") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_SCALE, 0, 0, ufbxi_mat_string("subsurface_radius") }, + { UFBX_MATERIAL_PBR_SUBSURFACE_ANISOTROPY, 0, 0, ufbxi_mat_string("subsurface_scatter_anisotropy") }, + { UFBX_MATERIAL_PBR_COAT_FACTOR, 0, 0, ufbxi_mat_string("coat_weight") }, + { UFBX_MATERIAL_PBR_COAT_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("coat_color") }, + { UFBX_MATERIAL_PBR_COAT_ROUGHNESS, 0, 0, ufbxi_mat_string("coat_roughness") }, + { UFBX_MATERIAL_PBR_COAT_ANISOTROPY, 0, 0, ufbxi_mat_string("coat_roughness_anisotropy") }, + { UFBX_MATERIAL_PBR_COAT_IOR, 0, 0, ufbxi_mat_string("coat_ior") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_normal_map") }, + { UFBX_MATERIAL_PBR_SHEEN_FACTOR, 0, 0, ufbxi_mat_string("fuzz_weight") }, + { UFBX_MATERIAL_PBR_SHEEN_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("fuzz_color") }, + { UFBX_MATERIAL_PBR_SHEEN_ROUGHNESS, 0, 0, ufbxi_mat_string("fuzz_roughness") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, 0, 0, ufbxi_mat_string("emission_weight") }, + { UFBX_MATERIAL_PBR_EMISSION_FACTOR, UFBXI_SHADER_MAPPING_MULTIPLY_VALUE, 0, ufbxi_mat_string("emission_luminance") }, + { UFBX_MATERIAL_PBR_EMISSION_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("emission_color") }, + { UFBX_MATERIAL_PBR_THIN_FILM_FACTOR, 0, 0, ufbxi_mat_string("thin_film_weight") }, + { UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, 0, 0, ufbxi_mat_string("thin_film_thickness") }, + { UFBX_MATERIAL_PBR_THIN_FILM_IOR, 0, 0, ufbxi_mat_string("thin_film_ior") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump") }, + { UFBX_MATERIAL_PBR_NORMAL_MAP, 0, 0, ufbxi_mat_string("bump_map_amt") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement") }, + { UFBX_MATERIAL_PBR_DISPLACEMENT_MAP, 0, 0, ufbxi_mat_string("displacement_map_amt") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump") }, + { UFBX_MATERIAL_PBR_COAT_NORMAL, 0, 0, ufbxi_mat_string("coat_bump_map_amt") }, + { UFBX_MATERIAL_PBR_TANGENT_MAP, 0, 0, ufbxi_mat_string("geometry_tangent_map") }, + { UFBX_MATERIAL_PBR_OPACITY, UFBXI_SHADER_MAPPING_WIDEN_TO_RGB, 0, ufbxi_mat_string("geometry_opacity") }, +}; + +static const ufbxi_shader_mapping ufbxi_openpbr_material_features[] = { + { UFBX_MATERIAL_FEATURE_THIN_WALLED, 0, 0, ufbxi_mat_string("geometry_thin_walled") }, +}; + static const ufbxi_shader_mapping ufbxi_3ds_max_pbr_metal_rough_pbr_mapping[] = { { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("base_color") }, { UFBX_MATERIAL_PBR_BASE_COLOR, UFBXI_SHADER_MAPPING_DEFAULT_W_1, 0, ufbxi_mat_string("baseColor") }, @@ -19365,6 +19513,14 @@ static const ufbxi_shader_mapping_list ufbxi_shader_pbr_mappings[] = { { NULL, 0 }, ufbxi_string_literal("Map"), // texture_prefix/suffix { NULL, 0 }, { NULL, 0 }, // texture_enabled_prefix/suffix }, + { // UFBX_SHADER_OPENPBR_MATERIAL + ufbxi_openpbr_material_pbr_mapping, ufbxi_arraycount(ufbxi_openpbr_material_pbr_mapping), + ufbxi_openpbr_material_features, ufbxi_arraycount(ufbxi_openpbr_material_features), + (uint32_t)(UFBXI_MAT_PBR | UFBXI_MAT_METALNESS | UFBXI_MAT_DIFFUSE | UFBXI_MAT_SPECULAR | UFBXI_MAT_COAT + | UFBXI_MAT_SHEEN | UFBXI_MAT_TRANSMISSION | UFBXI_MAT_OPACITY | UFBXI_MAT_IOR | UFBXI_MAT_DIFFUSE_ROUGHNESS), + { NULL, 0 }, ufbxi_string_literal("_map"), // texture_prefix/suffix + { NULL, 0 }, ufbxi_string_literal("_map_on"), // texture_enabled_prefix/suffix + }, { // UFBX_SHADER_SHADERFX_GRAPH ufbxi_shaderfx_graph_pbr_mapping, ufbxi_arraycount(ufbxi_shaderfx_graph_pbr_mapping), NULL, 0, @@ -19468,8 +19624,13 @@ ufbxi_noinline static void ufbxi_fetch_mapping_maps(ufbx_material *material, ufb if (flags & UFBXI_MAPPING_FETCH_VALUE) { if (prop && prop->type != UFBX_PROP_REFERENCE) { - map->value_vec4 = prop->value_vec4; - map->value_int = prop->value_int; + if ((mapping->flags & UFBXI_SHADER_MAPPING_MULTIPLY_VALUE) != 0) { + map->value_vec4.x *= prop->value_vec4.x; + map->value_int = ufbxi_f64_to_i64(map->value_vec4.x); + } else { + map->value_vec4 = prop->value_vec4; + map->value_int = prop->value_int; + } map->has_value = true; if (mapping->transform) { ufbxi_mat_transform_fn transform_fn = ufbxi_mat_transform_fns[mapping->transform]; @@ -19611,6 +19772,7 @@ ufbxi_noinline static void ufbxi_fetch_maps(ufbx_scene *scene, ufbx_material *ma ufbxi_update_factor(&material->pbr.specular_factor, &material->pbr.specular_color); ufbxi_update_factor(&material->pbr.emission_factor, &material->pbr.emission_color); ufbxi_update_factor(&material->pbr.sheen_factor, &material->pbr.sheen_color); + ufbxi_update_factor(&material->pbr.thin_film_factor, &material->pbr.thin_film_thickness); ufbxi_update_factor(&material->pbr.transmission_factor, &material->pbr.transmission_color); // Patch transmission roughness if only extra roughness is defined @@ -19909,6 +20071,7 @@ static const ufbxi_file_shader ufbxi_file_shaders[] = { { UINT64_C(0x7e73161fad53b12a), "ai_image", "filename" }, { 0, "OSLBitmap", ufbxi_Filename }, { 0, "OSLBitmap2", ufbxi_Filename }, + { 0, "OSLBitmap3", ufbxi_Filename }, { 0, "UberBitmap", ufbxi_Filename }, { 0, "UberBitmap2", ufbxi_Filename }, }; @@ -21697,6 +21860,14 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc } } + ufbxi_for_ptr_list(ufbx_anim_curve, p_curve, uc->scene.anim_curves) { + ufbx_anim_curve *curve = *p_curve; + if (curve->keyframes.count > 0) { + curve->min_time = curve->keyframes.data[0].time; + curve->max_time = curve->keyframes.data[curve->keyframes.count - 1].time; + } + } + ufbxi_for_ptr_list(ufbx_shader, p_shader, uc->scene.shaders) { ufbx_shader *shader = *p_shader; ufbxi_check(ufbxi_fetch_dst_elements(uc, &shader->bindings, &shader->element, false, false, NULL, UFBX_ELEMENT_SHADER_BINDING)); @@ -21738,6 +21909,10 @@ ufbxi_nodiscard ufbxi_noinline static int ufbxi_finalize_scene(ufbxi_context *uc material->shader_type = UFBX_SHADER_3DS_MAX_PHYSICAL_MATERIAL; material->shader_prop_prefix.data = "3dsMax|Parameters|"; material->shader_prop_prefix.length = strlen("3dsMax|Parameters|"); + } else if (classid_a == 0xf1551e33u && classid_b == 0x37fb1337u) { + material->shader_type = UFBX_SHADER_OPENPBR_MATERIAL; + material->shader_prop_prefix.data = "3dsMax|Parameters|"; + material->shader_prop_prefix.length = strlen("3dsMax|Parameters|"); } else if (classid_a == 0x38420192u && classid_b == 0x45fe4e1bu) { material->shader_type = UFBX_SHADER_GLTF_MATERIAL; material->shader_prop_prefix.data = "3dsMax|"; @@ -24671,6 +24846,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_load_imp(ufbxi_context *uc) } else { ufbxi_check(ufbxi_read_root(uc)); } + if (!ufbxi_supports_version(uc->version)) { + ufbxi_check(ufbxi_warnf(UFBX_WARNING_UNSUPPORTED_VERSION, "Unsupported FBX version (%u)", uc->version)); + } ufbxi_update_scene_metadata(&uc->scene.metadata); ufbxi_check(ufbxi_init_file_paths(uc)); } else if (format == UFBX_FILE_FORMAT_OBJ) { @@ -24978,6 +25156,12 @@ static ufbxi_noinline ufbx_scene *ufbxi_load(ufbxi_context *uc, const ufbx_load_ return &uc->scene_imp->scene; } else { ufbxi_fix_error_type(&uc->error, "Failed to load", p_error); + if (p_error && p_error->type == UFBX_ERROR_UNKNOWN && uc->scene.metadata.file_format == UFBX_FILE_FORMAT_FBX && !ufbxi_supports_version(uc->version)) { + p_error->description.data = "Unsupported version"; + p_error->description.length = strlen("Unsupported version"); + p_error->type = UFBX_ERROR_UNSUPPORTED_VERSION; + ufbxi_fmt_err_info(p_error, "%u", uc->version); + } ufbxi_free_result(uc); return NULL; } @@ -25115,7 +25299,7 @@ static ufbxi_forceinline bool ufbxi_anim_layer_might_contain_id(const ufbx_anim_ return ok; } -static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, size_t num_props) +static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, size_t num_props, uint32_t flags) { ufbxi_anim_layer_combine_ctx combine_ctx = { anim, element, time }; @@ -25131,7 +25315,7 @@ static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufb if (layer->weight_is_animated && layer->blended) { ufbx_anim_prop *weight_aprop = ufbxi_find_anim_prop_start(layer, &layer->element); if (weight_aprop) { - weight = ufbx_evaluate_anim_value_real(weight_aprop->anim_value, time) / (ufbx_real)100.0; + weight = ufbx_evaluate_anim_value_real_flags(weight_aprop->anim_value, time, flags) / (ufbx_real)100.0; if (weight < 0.0f) weight = 0.0f; if (weight > 0.99999f) weight = 1.0f; } @@ -25160,7 +25344,7 @@ static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufb // This could be done by having `UFBX_PROP_FLAG_ANIMATION_EVALUATED` // that gets set for the first layer of animation that is applied. if (aprop->prop_name.data == prop->name.data) { - ufbx_vec3 v = ufbx_evaluate_anim_value_vec3(aprop->anim_value, time); + ufbx_vec3 v = ufbx_evaluate_anim_value_vec3_flags(aprop->anim_value, time, flags); if (layer_ix == 0) { prop->value_vec3 = v; } else { @@ -25178,9 +25362,9 @@ static ufbxi_noinline void ufbxi_evaluate_props(const ufbx_anim *anim, const ufb // Recursion limited by not calling `ufbx_evaluate_prop_len()` with a connected property, // meaning it will never call `ufbxi_evaluate_connected_prop()` again indirectly. -static ufbxi_noinline void ufbxi_evaluate_connected_prop(ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time) - ufbxi_recursive_function_void(ufbxi_evaluate_connected_prop, (prop, anim, element, name, time), 3, - (ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time)) +static ufbxi_noinline void ufbxi_evaluate_connected_prop(ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags) + ufbxi_recursive_function_void(ufbxi_evaluate_connected_prop, (prop, anim, element, name, time, flags), 3, + (ufbx_prop *prop, const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags)) { ufbx_connection *conn = ufbxi_find_prop_connection(element, name); @@ -25192,7 +25376,7 @@ static ufbxi_noinline void ufbxi_evaluate_connected_prop(ufbx_prop *prop, const // Found a non-cyclic connection if (conn && !ufbxi_find_prop_connection(conn->src, conn->src_prop.data)) { - ufbx_prop ep = ufbx_evaluate_prop_len(anim, conn->src, conn->src_prop.data, conn->src_prop.length, time); + ufbx_prop ep = ufbx_evaluate_prop_len_flags(anim, conn->src, conn->src_prop.data, conn->src_prop.length, time, flags); prop->value_vec4 = ep.value_vec4; prop->value_int = ep.value_int; prop->value_str = ep.value_str; @@ -25282,7 +25466,7 @@ static ufbxi_forceinline const ufbx_prop *ufbxi_next_prop(ufbxi_prop_iter *iter) } } -static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, const char *const *prop_names, size_t max_props) +static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *props, const char *const *prop_names, size_t max_props, uint32_t flags) { const char *name = prop_names[0]; uint32_t key = ufbxi_get_name_key_c(name); @@ -25306,7 +25490,7 @@ static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim * if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { ufbx_prop *dst = &props[num_props++]; *dst = *prop; - ufbxi_evaluate_connected_prop(dst, anim, element, name, time); + ufbxi_evaluate_connected_prop(dst, anim, element, name, time, flags); } else if ((prop->flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_OVERRIDDEN)) != 0) { props[num_props++] = *prop; } @@ -25323,7 +25507,7 @@ static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim * } } - ufbxi_evaluate_props(anim, element, time, props, num_props); + ufbxi_evaluate_props(anim, element, time, props, num_props, flags); ufbx_props prop_list; prop_list.props.data = props; @@ -25332,6 +25516,74 @@ static ufbxi_noinline ufbx_props ufbxi_evaluate_selected_props(const ufbx_anim * return prop_list; } +// Recursion limited by not calling `ufbx_evaluate_curve()` with `UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION`. +static ufbxi_noinline ufbx_real ufbxi_extrapolate_curve(const ufbx_anim_curve *curve, double real_time, uint32_t flags) + ufbxi_recursive_function(ufbx_real, ufbxi_extrapolate_curve, (curve, real_time, flags), 3, + (const ufbx_anim_curve *curve, double real_time, uint32_t flags)) +{ + bool pre = real_time < curve->min_time; + const ufbx_keyframe *key; + ufbx_extrapolation ext; + if (pre) { + key = &curve->keyframes.data[0]; + ext = curve->pre_extrapolation; + } else { + key = &curve->keyframes.data[curve->keyframes.count - 1]; + ext = curve->post_extrapolation; + } + + if (ext.mode == UFBX_EXTRAPOLATION_CONSTANT) { + return key->value; + } else if (ext.mode == UFBX_EXTRAPOLATION_SLOPE) { + ufbx_tangent tangent = *(pre ? &key->right : &key->left); + return key->value + (ufbx_real)(tangent.dy * ((real_time - key->time) / tangent.dx)); + } else if (ext.repeat_count == 0) { + return key->value; + } + + // Perform all operations in KTime ticks to be frame perfect + double scale = (double)curve->element.scene->metadata.ktime_second; + double min_time = ufbx_rint(curve->min_time * scale); + double max_time = ufbx_rint(curve->max_time * scale); + double time = real_time * scale; + + double delta = pre ? min_time - time : time - max_time; + double duration = max_time - min_time; + + // Require at least one KTime unit + if (!(duration >= 1.0)) return key->value; + + double rep = delta / duration; + double rep_n = ufbx_floor(rep); + double rep_d = delta - rep_n * duration; + + if (ext.repeat_count > 0 && rep_n >= (double)ext.repeat_count) { + // Clamp to the repeat count to handle mirroring + rep_n = (double)(ext.repeat_count - 1); + rep_d = duration; + } + + if (ext.mode == UFBX_EXTRAPOLATION_MIRROR) { + double rep_parity = rep_n*0.5 - ufbx_floor(rep_n*0.5); + if (rep_parity <= 0.25) { + rep_d = duration - rep_d; + } + } + + if (pre) rep_d = duration - rep_d; + double new_time = (min_time + rep_d) / scale; + + ufbx_real value = ufbx_evaluate_curve_flags(curve, new_time, key->value, flags | UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION); + + if (ext.mode == UFBX_EXTRAPOLATION_REPEAT_RELATIVE) { + ufbx_real val_delta = curve->keyframes.data[curve->keyframes.count - 1].value - curve->keyframes.data[0].value; + if (pre) val_delta = -val_delta; + value += val_delta * (ufbx_real)(rep_n + 1.0); + } + + return value; +} + #if UFBXI_FEATURE_SCENE_EVALUATION typedef struct { @@ -25686,7 +25938,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_evaluate_imp(ufbxi_eval_context ufbx_prop *props = ufbxi_push(&ec->result, ufbx_prop, num_animated); ufbxi_check_err(&ec->error, props); - elem->props = ufbx_evaluate_props(&anim, elem, ec->time, props, num_animated); + elem->props = ufbx_evaluate_props_flags(&anim, elem, ec->time, props, num_animated, ec->opts.evaluate_flags); elem->props.defaults = &ec->src_scene.elements.data[elem->element_id]->props; } @@ -26693,6 +26945,9 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_node_imp(ufbxi_bake_context } flags |= UFBX_TRANSFORM_FLAG_IGNORE_SCALE_HELPER|UFBX_TRANSFORM_FLAG_IGNORE_COMPONENTWISE_SCALE|UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES; + if (bc->opts.evaluate_flags & UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION) { + flags |= UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION; + } double eval_time = ufbxi_bake_time_sample_time(bake_time); ufbx_transform transform = ufbx_evaluate_transform_flags(bc->anim, node, eval_time, flags); @@ -26813,7 +27068,7 @@ ufbxi_nodiscard static ufbxi_noinline int ufbxi_bake_anim_prop(ufbxi_bake_contex for (size_t i = 0; i < times.count; i++) { 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); + ufbx_prop prop = ufbx_evaluate_prop_len_flags(bc->anim, element, name.data, name.length, eval_time, bc->opts.evaluate_flags); 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; @@ -30113,6 +30368,11 @@ ufbx_abi ufbxi_noinline ufbx_matrix ufbx_get_compatible_matrix_for_normals(const } ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time, ufbx_real default_value) +{ + return ufbx_evaluate_curve_flags(curve, time, default_value, 0); +} + +ufbx_abi ufbx_real ufbx_evaluate_curve_flags(const ufbx_anim_curve *curve, double time, ufbx_real default_value, uint32_t flags) { if (!curve) return default_value; if (curve->keyframes.count <= 1) { @@ -30123,6 +30383,12 @@ ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time } } + if ((flags & UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION) == 0) { + if (time < curve->min_time || time > curve->max_time) { + return ufbxi_extrapolate_curve(curve, time, flags); + } + } + size_t begin = 0; size_t end = curve->keyframes.count; const ufbx_keyframe *keys = curve->keyframes.data; @@ -30191,17 +30457,27 @@ ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time } ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real(const ufbx_anim_value *anim_value, double time) +{ + return ufbx_evaluate_anim_value_real_flags(anim_value, time, 0); +} + +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time) +{ + return ufbx_evaluate_anim_value_vec3_flags(anim_value, time, 0); +} + +ufbx_abi ufbxi_noinline ufbx_real ufbx_evaluate_anim_value_real_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags) { if (!anim_value) { return 0.0f; } ufbx_real res = anim_value->default_value.x; - if (anim_value->curves[0]) res = ufbx_evaluate_curve(anim_value->curves[0], time, res); + if (anim_value->curves[0]) res = ufbx_evaluate_curve_flags(anim_value->curves[0], time, res, flags); return res; } -ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time) +ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags) { if (!anim_value) { ufbx_vec3 zero = { 0.0f }; @@ -30209,13 +30485,18 @@ ufbx_abi ufbxi_noinline ufbx_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_ } ufbx_vec3 res = anim_value->default_value; - 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); - if (anim_value->curves[2]) res.z = ufbx_evaluate_curve(anim_value->curves[2], time, res.z); + if (anim_value->curves[0]) res.x = ufbx_evaluate_curve_flags(anim_value->curves[0], time, res.x, flags); + if (anim_value->curves[1]) res.y = ufbx_evaluate_curve_flags(anim_value->curves[1], time, res.y, flags); + if (anim_value->curves[2]) res.z = ufbx_evaluate_curve_flags(anim_value->curves[2], time, res.z, flags); return res; } ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time) +{ + return ufbx_evaluate_prop_len_flags(anim, element, name, name_len, time, 0); +} + +ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time, uint32_t flags) { ufbx_prop result; @@ -30242,15 +30523,20 @@ ufbx_abi ufbxi_noinline ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, if ((result.flags & (UFBX_PROP_FLAG_ANIMATED|UFBX_PROP_FLAG_CONNECTED)) == 0) return result; if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { - ufbxi_evaluate_connected_prop(&result, anim, element, prop->name.data, time); + ufbxi_evaluate_connected_prop(&result, anim, element, prop->name.data, time, flags); } - ufbxi_evaluate_props(anim, element, time, &result, 1); + ufbxi_evaluate_props(anim, element, time, &result, 1, flags); return result; } ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size) +{ + return ufbx_evaluate_props_flags(anim, element, time, buffer, buffer_size, 0); +} + +ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props_flags(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size, uint32_t flags) { ufbx_props ret = { NULL }; if (!element) return ret; @@ -30267,11 +30553,11 @@ ufbx_abi ufbxi_noinline ufbx_props ufbx_evaluate_props(const ufbx_anim *anim, co *dst = *prop; if ((prop->flags & UFBX_PROP_FLAG_CONNECTED) != 0 && !anim->ignore_connections) { - ufbxi_evaluate_connected_prop(dst, anim, element, prop->name.data, time); + ufbxi_evaluate_connected_prop(dst, anim, element, prop->name.data, time, flags); } } - ufbxi_evaluate_props(anim, element, time, buffer, num_anim); + ufbxi_evaluate_props(anim, element, time, buffer, num_anim, flags); ret.props.data = buffer; ret.props.count = ret.num_animated = num_anim; @@ -30382,8 +30668,13 @@ ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform_flags(const ufbx_ } } + uint32_t eval_flags = 0; + if (flags & UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION) { + eval_flags |= UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION; + } + 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_props props = ufbxi_evaluate_selected_props(anim, &node->element, time, buf, prop_names, num_prop_names, eval_flags); ufbx_rotation_order order = (ufbx_rotation_order)ufbxi_find_enum(&props, ufbxi_RotationOrder, UFBX_ROTATION_ORDER_XYZ, UFBX_ROTATION_ORDER_SPHERIC); ufbx_transform transform; // ufbxi_uninit @@ -30412,13 +30703,18 @@ ufbx_abi ufbxi_noinline ufbx_transform ufbx_evaluate_transform_flags(const ufbx_ } ufbx_abi ufbx_real ufbx_evaluate_blend_weight(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time) +{ + return ufbx_evaluate_blend_weight_flags(anim, channel, time, 0); +} + +ufbx_abi ufbx_real ufbx_evaluate_blend_weight_flags(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time, uint32_t flags) { const char *prop_names[] = { ufbxi_DeformPercent, }; 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)); + ufbx_props props = ufbxi_evaluate_selected_props(anim, &channel->element, time, buf, prop_names, ufbxi_arraycount(prop_names), flags); return ufbxi_find_real(&props, ufbxi_DeformPercent, channel->weight * (ufbx_real)100.0) * (ufbx_real)0.01; } @@ -32332,6 +32628,7 @@ ufbx_abi ufbx_anim_stack *ufbx_find_anim_stack(const ufbx_scene *scene, const ch ufbx_abi ufbx_material *ufbx_find_material(const ufbx_scene *scene, const char *name) { return ufbx_find_material_len(scene, name, strlen(name)); } ufbx_abi ufbx_anim_prop *ufbx_find_anim_prop(const ufbx_anim_layer *layer, const ufbx_element *element, const char *prop) { return ufbx_find_anim_prop_len(layer, element, prop, strlen(prop)); } ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time) { return ufbx_evaluate_prop_len(anim, element, name, strlen(name), time); } +ufbx_abi ufbx_prop ufbx_evaluate_prop_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags) { return ufbx_evaluate_prop_len_flags(anim, element, name, strlen(name), time, flags); } ufbx_abi ufbx_texture *ufbx_find_prop_texture(const ufbx_material *material, const char *name) { return ufbx_find_prop_texture_len(material, name, strlen(name)); } ufbx_abi ufbx_string ufbx_find_shader_prop(const ufbx_shader *shader, const char *name) { return ufbx_find_shader_prop_len(shader, name, strlen(name)); } ufbx_abi 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)); } diff --git a/thirdparty/ufbx/ufbx.h b/thirdparty/ufbx/ufbx.h index 8cd336fe776..37a2e651a53 100644 --- a/thirdparty/ufbx/ufbx.h +++ b/thirdparty/ufbx/ufbx.h @@ -267,7 +267,7 @@ struct ufbx_converter { }; // `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, 17, 1) +#define UFBX_HEADER_VERSION ufbx_pack_version(0, 18, 0) #define UFBX_VERSION UFBX_HEADER_VERSION // -- Basic types @@ -2353,6 +2353,9 @@ typedef enum ufbx_shader_type UFBX_ENUM_REPR { // 3ds glTF Material // https://help.autodesk.com/view/3DSMAX/2023/ENU/?guid=GUID-7ABFB805-1D9F-417E-9C22-704BFDF160FA UFBX_SHADER_GLTF_MATERIAL, + // 3ds OpenPBR Material + // https://help.autodesk.com/view/3DSMAX/2025/ENU/?guid=GUID-CD90329C-1E2B-4BBA-9285-3BB46253B9C2 + UFBX_SHADER_OPENPBR_MATERIAL, // Stingray ShaderFX shader graph. // Contains a serialized `"ShaderGraph"` in `ufbx_props`. UFBX_SHADER_SHADERFX_GRAPH, @@ -2437,6 +2440,7 @@ typedef enum ufbx_material_pbr_map UFBX_ENUM_REPR { UFBX_MATERIAL_PBR_COAT_NORMAL, UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_COLOR, UFBX_MATERIAL_PBR_COAT_AFFECT_BASE_ROUGHNESS, + UFBX_MATERIAL_PBR_THIN_FILM_FACTOR, UFBX_MATERIAL_PBR_THIN_FILM_THICKNESS, UFBX_MATERIAL_PBR_THIN_FILM_IOR, UFBX_MATERIAL_PBR_EMISSION_FACTOR, @@ -2561,6 +2565,7 @@ typedef struct ufbx_material_pbr_maps { ufbx_material_map coat_normal; ufbx_material_map coat_affect_base_color; ufbx_material_map coat_affect_base_roughness; + ufbx_material_map thin_film_factor; ufbx_material_map thin_film_thickness; ufbx_material_map thin_film_ior; ufbx_material_map emission_factor; @@ -3141,6 +3146,26 @@ typedef enum ufbx_interpolation UFBX_ENUM_REPR { UFBX_ENUM_TYPE(ufbx_interpolation, UFBX_INTERPOLATION, UFBX_INTERPOLATION_CUBIC); +typedef enum ufbx_extrapolation_mode UFBX_ENUM_REPR { + UFBX_EXTRAPOLATION_CONSTANT, // < Use the value of the first/last keyframe + UFBX_EXTRAPOLATION_REPEAT, // < Repeat the whole animation curve + UFBX_EXTRAPOLATION_MIRROR, // < Repeat with mirroring + UFBX_EXTRAPOLATION_SLOPE, // < Use the tangent of the last keyframe to linearly extrapolate + UFBX_EXTRAPOLATION_REPEAT_RELATIVE, // < Repeat the animation curve but connect the first and last keyframe values + + UFBX_ENUM_FORCE_WIDTH(UFBX_EXTRAPOLATION) +} ufbx_extrapolation_mode; + +UFBX_ENUM_TYPE(ufbx_extrapolation_mode, UFBX_EXTRAPOLATION_MODE, UFBX_EXTRAPOLATION_REPEAT_RELATIVE); + +typedef struct ufbx_extrapolation { + ufbx_extrapolation_mode mode; + + // Count used for repeating modes. + // Negative values mean infinite repetition. + int32_t repeat_count; +} ufbx_extrapolation; + // Tangent vector at a keyframe, may be split into left/right typedef struct ufbx_tangent { float dx; // < Derivative in the time axis @@ -3177,10 +3202,21 @@ struct ufbx_anim_curve { uint32_t typed_id; }; }; + // List of keyframes that define the curve. ufbx_keyframe_list keyframes; + // Extrapolation before the curve. + ufbx_extrapolation pre_extrapolation; + // Extrapolation after the curve. + ufbx_extrapolation post_extrapolation; + + // Value range for all the keyframes. ufbx_real min_value; ufbx_real max_value; + + // Time range for all the keyframes. + double min_time; + double max_time; }; // -- Collections @@ -3501,6 +3537,10 @@ typedef enum ufbx_warning_type UFBX_ENUM_REPR { // Missing polygon mapping type. UFBX_WARNING_MISSING_POLYGON_MAPPING, + // Unsupported version, loaded but may be incorrect. + // If the loading fails `UFBX_ERROR_UNSUPPORTED_VERSION` is issued instead. + UFBX_WARNING_UNSUPPORTED_VERSION, + // 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, @@ -4179,10 +4219,14 @@ typedef enum ufbx_error_type UFBX_ENUM_REPR { // Duplicated override property in `ufbx_create_anim()` UFBX_ERROR_DUPLICATE_OVERRIDE, + // Unsupported file format version. + // ufbx still tries to load files with unsupported versions, see `UFBX_WARNING_UNSUPPORTED_VERSION`. + UFBX_ERROR_UNSUPPORTED_VERSION, + UFBX_ENUM_FORCE_WIDTH(UFBX_ERROR_TYPE) } ufbx_error_type; -UFBX_ENUM_TYPE(ufbx_error_type, UFBX_ERROR_TYPE, UFBX_ERROR_DUPLICATE_OVERRIDE); +UFBX_ENUM_TYPE(ufbx_error_type, UFBX_ERROR_TYPE, UFBX_ERROR_UNSUPPORTED_VERSION); // Error description with detailed stack trace // HINT: You can use `ufbx_format_error()` for formatting the error @@ -4598,6 +4642,15 @@ typedef struct ufbx_thread_opts { } ufbx_thread_opts; +// Flags to control nanimation evaluation functions. +typedef enum ufbx_evaluate_flags UFBX_FLAG_REPR { + + // Do not extrapolate past the keyframes. + UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION = 0x1, + + UFBX_FLAG_FORCE_WIDTH(ufbx_evaluate_flags) +} ufbx_evaluate_flags; + // -- Main API // Options for `ufbx_load_file/memory/stream/stdio()` @@ -4784,7 +4837,7 @@ typedef struct ufbx_load_opts { bool use_root_transform; ufbx_transform root_transform; - // Animation keyframe clamp threhsold, only applies to specific interpolation modes. + // Animation keyframe clamp threshold, only applies to specific interpolation modes. double key_clamp_threshold; // Specify how to handle Unicode errors in strings. @@ -4860,6 +4913,10 @@ typedef struct ufbx_evaluate_opts { bool evaluate_skinning; // < Evaluate skinning (see ufbx_mesh.skinned_vertices) bool evaluate_caches; // < Evaluate vertex caches (see ufbx_mesh.skinned_vertices) + // Evaluation flags. + // See `ufbx_evaluate_flags` for information. + uint32_t evaluate_flags; + // WARNING: Potentially unsafe! Try to open external files such as geometry caches bool load_external_files; @@ -5001,6 +5058,10 @@ typedef struct ufbx_bake_opts { // `time / (1.0 + step_custom_epsilon)` and `time * (1.0 + step_custom_epsilon)`. double step_custom_epsilon; + // Flags passed to animation evaluation functions. + // See `ufbx_evaluate_flags`. + uint32_t evaluate_flags; + // Enable key reduction. bool key_reduction_enabled; @@ -5330,20 +5391,26 @@ ufbx_unsafe ufbx_abi bool ufbx_open_memory_ctx(ufbx_stream *stream, ufbx_open_fi // Evaluate a single animation `curve` at a `time`. // Returns `default_value` only if `curve == NULL` or it has no keyframes. ufbx_abi ufbx_real ufbx_evaluate_curve(const ufbx_anim_curve *curve, double time, ufbx_real default_value); +ufbx_abi ufbx_real ufbx_evaluate_curve_flags(const ufbx_anim_curve *curve, double time, ufbx_real default_value, uint32_t flags); // 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_vec3 ufbx_evaluate_anim_value_vec3(const ufbx_anim_value *anim_value, double time); +ufbx_abi ufbx_real ufbx_evaluate_anim_value_real_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags); +ufbx_abi ufbx_vec3 ufbx_evaluate_anim_value_vec3_flags(const ufbx_anim_value *anim_value, double time, uint32_t flags); // Evaluate an animated property `name` from `element` at `time`. // NOTE: If the property is not found it will have the flag `UFBX_PROP_FLAG_NOT_FOUND`. ufbx_abi ufbx_prop ufbx_evaluate_prop_len(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time); ufbx_abi ufbx_prop ufbx_evaluate_prop(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time); +ufbx_abi ufbx_prop ufbx_evaluate_prop_len_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, size_t name_len, double time, uint32_t flags); +ufbx_abi ufbx_prop ufbx_evaluate_prop_flags(const ufbx_anim *anim, const ufbx_element *element, const char *name, double time, uint32_t flags); // Evaluate all _animated_ properties of `element`. // HINT: This function returns an `ufbx_props` structure with the original properties as // `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); +ufbx_abi ufbx_props ufbx_evaluate_props_flags(const ufbx_anim *anim, const ufbx_element *element, double time, ufbx_prop *buffer, size_t buffer_size, uint32_t flags); // Flags to control `ufbx_evaluate_transform_flags()`. typedef enum ufbx_transform_flags UFBX_FLAG_REPR { @@ -5366,6 +5433,10 @@ typedef enum ufbx_transform_flags UFBX_FLAG_REPR { // If `UFBX_TRANSFORM_FLAG_EXPLICIT_INCLUDES`: Evaluate `ufbx_transform.scale`. UFBX_TRANSFORM_FLAG_INCLUDE_SCALE = 0x40, + // Do not extrapolate keyframes. + // See `UFBX_EVALUATE_FLAG_NO_EXTRAPOLATION`. + UFBX_TRANSFORM_FLAG_NO_EXTRAPOLATION = 0x80, + UFBX_FLAG_FORCE_WIDTH(UFBX_TRANSFORM_FLAGS) } ufbx_transform_flags; @@ -5378,6 +5449,7 @@ ufbx_abi ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, con // 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); +ufbx_abi ufbx_real ufbx_evaluate_blend_weight_flags(const ufbx_anim *anim, const ufbx_blend_channel *channel, double time, uint32_t flags); // Evaluate the whole `scene` at a specific `time` in the animation `anim`. // The returned scene behaves as if it had been exported at a specific time