Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve bind poses from FBX clusters instead of FBX poses. #91036

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 81 additions & 16 deletions modules/fbx/fbx_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ static Quaternion _as_quaternion(const ufbx_quat &p_quat) {
return Quaternion(real_t(p_quat.x), real_t(p_quat.y), real_t(p_quat.z), real_t(p_quat.w));
}

static Transform3D _as_transform(const ufbx_transform &p_xform) {
Transform3D result;
result.origin = FBXDocument::_as_vec3(p_xform.translation);
result.basis.set_quaternion_scale(_as_quaternion(p_xform.rotation), FBXDocument::_as_vec3(p_xform.scale));
return result;
}

static real_t _relative_error(const Vector3 &p_a, const Vector3 &p_b) {
return p_a.distance_to(p_b) / MAX(p_a.length(), p_b.length());
}

static Color _material_color(const ufbx_material_map &p_map) {
if (p_map.value_components == 1) {
float r = float(p_map.value_real);
Expand Down Expand Up @@ -196,6 +207,16 @@ static uint32_t _decode_vertex_index(const Vector3 &p_vertex) {
return uint32_t(p_vertex.x) | uint32_t(p_vertex.y) << 16;
}

static ufbx_skin_deformer *_find_skin_deformer(ufbx_skin_cluster *p_cluster) {
for (const ufbx_connection &conn : p_cluster->element.connections_src) {
ufbx_skin_deformer *deformer = ufbx_as_skin_deformer(conn.dst);
if (deformer) {
return deformer;
}
}
return nullptr;
}

struct ThreadPoolFBX {
struct Group {
ufbx_thread_pool_context ctx = {};
Expand Down Expand Up @@ -333,23 +354,67 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) {
}

{
node->transform.origin = _as_vec3(fbx_node->local_transform.translation);
node->transform.basis.set_quaternion_scale(_as_quaternion(fbx_node->local_transform.rotation), _as_vec3(fbx_node->local_transform.scale));

if (fbx_node->bind_pose) {
ufbx_bone_pose *pose = ufbx_get_bone_pose(fbx_node->bind_pose, fbx_node);
ufbx_transform rest_transform = ufbx_matrix_to_transform(&pose->bone_to_parent);

Vector3 rest_position = _as_vec3(rest_transform.translation);
Quaternion rest_rotation = _as_quaternion(rest_transform.rotation);
Vector3 rest_scale = _as_vec3(rest_transform.scale);
Transform3D godot_rest_xform;
godot_rest_xform.basis.set_quaternion_scale(rest_rotation, rest_scale);
godot_rest_xform.origin = rest_position;
node->set_additional_data("GODOT_rest_transform", godot_rest_xform);
} else {
node->set_additional_data("GODOT_rest_transform", node->transform);
node->transform = _as_transform(fbx_node->local_transform);

bool found_rest_xform = false;
bool bad_rest_xform = false;
Transform3D candidate_rest_xform;

if (fbx_node->parent) {
// Attempt to resolve a rest pose for bones: This uses internal FBX connections to find
// all skin clusters connected to the bone.
for (const ufbx_connection &child_conn : fbx_node->element.connections_src) {
ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst);
if (!child_cluster)
continue;
ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster);
if (!child_deformer)
continue;

// Found a skin cluster: Now iterate through all the skin clusters of the parent and
// try to find one that used by the same deformer.
for (const ufbx_connection &parent_conn : fbx_node->parent->element.connections_src) {
ufbx_skin_cluster *parent_cluster = ufbx_as_skin_cluster(parent_conn.dst);
if (!parent_cluster)
continue;
ufbx_skin_deformer *parent_deformer = _find_skin_deformer(parent_cluster);
if (parent_deformer != child_deformer)
continue;

// Success: Found two skin clusters from the same deformer, now we can resolve the
// local bind pose from the difference between the two world-space bind poses.
ufbx_matrix child_to_world = child_cluster->bind_to_world;
ufbx_matrix world_to_parent = ufbx_matrix_invert(&parent_cluster->bind_to_world);
ufbx_matrix child_to_parent = ufbx_matrix_mul(&world_to_parent, &child_to_world);
Transform3D xform = _as_transform(ufbx_matrix_to_transform(&child_to_parent));

if (!found_rest_xform) {
// Found the first bind pose for the node, assume that this one is good
found_rest_xform = true;
candidate_rest_xform = xform;
} else if (!bad_rest_xform) {
// Found another: Let's hope it's similar to the previous one, if not warn and
// use the initial pose, which is used by default if rest pose is not found.
real_t error = 0.0f;
error += _relative_error(candidate_rest_xform.origin, xform.origin);
for (int i = 0; i < 3; i++) {
error += _relative_error(candidate_rest_xform.basis.rows[i], xform.basis.rows[i]);
}
const real_t max_error = 0.01f;
if (error >= max_error) {
WARN_PRINT(vformat("FBX: Node '%s' has multiple bind poses, using initial pose as rest pose.", node->get_name()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little worried this will logspam but let's keep it and it will help us diagnose issues during the beta test.

Not something we have to fix here, but we should figure out a better approach for import warnings. I want to show warnings and errors per-file in the import dock or in some dedicated place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah was a little concerned about this as well, but it feels like something that should be informed to the user. Of course collecting them would be ideal.

On that note, we should also probably expose ufbx_metadata.warnings[] there, as ufbx can report a bunch of suspicious stuff in the file.

bad_rest_xform = true;
}
}
}
}
}

Transform3D godot_rest_xform = node->transform;
if (found_rest_xform && !bad_rest_xform) {
godot_rest_xform = candidate_rest_xform;
}
node->set_additional_data("GODOT_rest_transform", godot_rest_xform);
}

for (const ufbx_node *child : fbx_node->children) {
Expand Down
Loading