Merge pull request #91036 from bqqbarbhg/ufbx-bind-pose-fix
Resolve bind poses from FBX clusters instead of FBX poses.
This commit is contained in:
commit
36833c6871
1 changed files with 79 additions and 14 deletions
|
@ -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));
|
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) {
|
static Color _material_color(const ufbx_material_map &p_map) {
|
||||||
if (p_map.value_components == 1) {
|
if (p_map.value_components == 1) {
|
||||||
float r = float(p_map.value_real);
|
float r = float(p_map.value_real);
|
||||||
|
@ -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;
|
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 ThreadPoolFBX {
|
||||||
struct Group {
|
struct Group {
|
||||||
ufbx_thread_pool_context ctx = {};
|
ufbx_thread_pool_context ctx = {};
|
||||||
|
@ -333,23 +354,67 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
node->transform.origin = _as_vec3(fbx_node->local_transform.translation);
|
node->transform = _as_transform(fbx_node->local_transform);
|
||||||
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) {
|
bool found_rest_xform = false;
|
||||||
ufbx_bone_pose *pose = ufbx_get_bone_pose(fbx_node->bind_pose, fbx_node);
|
bool bad_rest_xform = false;
|
||||||
ufbx_transform rest_transform = ufbx_matrix_to_transform(&pose->bone_to_parent);
|
Transform3D candidate_rest_xform;
|
||||||
|
|
||||||
Vector3 rest_position = _as_vec3(rest_transform.translation);
|
if (fbx_node->parent) {
|
||||||
Quaternion rest_rotation = _as_quaternion(rest_transform.rotation);
|
// Attempt to resolve a rest pose for bones: This uses internal FBX connections to find
|
||||||
Vector3 rest_scale = _as_vec3(rest_transform.scale);
|
// all skin clusters connected to the bone.
|
||||||
Transform3D godot_rest_xform;
|
for (const ufbx_connection &child_conn : fbx_node->element.connections_src) {
|
||||||
godot_rest_xform.basis.set_quaternion_scale(rest_rotation, rest_scale);
|
ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst);
|
||||||
godot_rest_xform.origin = rest_position;
|
if (!child_cluster)
|
||||||
node->set_additional_data("GODOT_rest_transform", godot_rest_xform);
|
continue;
|
||||||
} else {
|
ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster);
|
||||||
node->set_additional_data("GODOT_rest_transform", node->transform);
|
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()));
|
||||||
|
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) {
|
for (const ufbx_node *child : fbx_node->children) {
|
||||||
|
|
Loading…
Reference in a new issue