ufbx: Update to 0.18.0

This commit is contained in:
Jakub Marcowski
2025-04-06 15:04:27 +02:00
parent a210fe6dbd
commit 533daa5552
3 changed files with 432 additions and 63 deletions

View File

@@ -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:

415
thirdparty/ufbx/ufbx.c vendored
View File

@@ -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)); }

View File

@@ -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