mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
Some comments and cleanup for new retarget system
This commit is contained in:
+61
-17
@@ -288,6 +288,7 @@ fn build_retarget_trees(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn copy_poses(
|
fn copy_poses(
|
||||||
|
// TODO: Refactor to make this a component-based approach instead of resource-based
|
||||||
r_retarget_tree: Res<RetargetTree>,
|
r_retarget_tree: Res<RetargetTree>,
|
||||||
r_retarget_map: Res<MixamoRetargetMap>,
|
r_retarget_map: Res<MixamoRetargetMap>,
|
||||||
q_ent_names: Query<(Entity, &Name), With<SkeletalJoint>>,
|
q_ent_names: Query<(Entity, &Name), With<SkeletalJoint>>,
|
||||||
@@ -298,11 +299,30 @@ fn copy_poses(
|
|||||||
mut q_xforms: Query<&mut Transform>,
|
mut q_xforms: Query<&mut Transform>,
|
||||||
q_world_xforms: Query<&GlobalTransform>,
|
q_world_xforms: Query<&GlobalTransform>,
|
||||||
) {
|
) {
|
||||||
|
// FIXME:
|
||||||
|
// This is very much not fast, and probably shouldn't be done every frame.
|
||||||
|
// Tentative plan to optimize this without adding too much additional
|
||||||
|
// complexity:
|
||||||
|
//
|
||||||
|
// 1) Add some sort of "AnimationRecorder" component to the target root
|
||||||
|
// 2) Beginning with `src_anim.seek_time == 0.0`, record each local
|
||||||
|
// rotation and translation performed on the target bones as a new
|
||||||
|
// keyframe on an `AnimationClip` held by `AnimationRecorder`
|
||||||
|
// 3) When `src_anim.seek_time == src_anim.duration`, finalize the new
|
||||||
|
// `AnimationClip` and add it to the asset server
|
||||||
|
// 4) Stop running this system and instead start playing the new animation
|
||||||
|
// with an `AnimationPlayer` component on the target root
|
||||||
r_retarget_tree.for_each(|node| {
|
r_retarget_tree.for_each(|node| {
|
||||||
|
// Look up the source bone in the retarget map
|
||||||
let Some(src_ent) = r_retarget_map
|
let Some(src_ent) = r_retarget_map
|
||||||
.tgt_to_src
|
.tgt_to_src
|
||||||
.get(node.path.leaf().as_str())
|
.get(node.path.leaf().as_str())
|
||||||
.and_then(|src_name| {
|
.and_then(|src_name| {
|
||||||
|
// FIXME: This is a slow O(n) lookup where n = the number of
|
||||||
|
// named entities in the world. We can make this O(1) on
|
||||||
|
// subsequent system runs by adding the results to a
|
||||||
|
// local hashmap as they're found, and checking that map
|
||||||
|
// before searching by name.
|
||||||
q_ent_names.iter().find_map(|(ent, name)| {
|
q_ent_names.iter().find_map(|(ent, name)| {
|
||||||
if name.as_str() == src_name {
|
if name.as_str() == src_name {
|
||||||
Some(ent)
|
Some(ent)
|
||||||
@@ -314,18 +334,40 @@ fn copy_poses(
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get the query data needed to compute the target bone's local rotation
|
||||||
let Ok(src_inv_bind_pose) = q_inv_bind_poses.get(src_ent) else {
|
let Ok(src_inv_bind_pose) = q_inv_bind_poses.get(src_ent) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Ok(src_world_xform) = q_world_xforms.get(src_ent) else {
|
let Ok(src_world_xform) = q_world_xforms.get(src_ent) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Ok(tgt_bind_pose) = q_world_bind_poses.get(node.id) else {
|
let Ok(tgt_world_bind_pose) = q_world_bind_poses.get(node.id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut local_mat = src_world_xform.affine() * (**src_inv_bind_pose);
|
// The math here was adapted from this writeup:
|
||||||
|
// https://wickedengine.net/2022/09/23/animation-retargeting/
|
||||||
|
//
|
||||||
|
// ```pseudocode
|
||||||
|
// // Apply target-local rotation
|
||||||
|
// tgt_working_xform = src_world_xform * src_inv_bind_pose
|
||||||
|
// tgt_working_xform = tgt_inv_parent_xform * local_xform * tgt_world_bind_pose
|
||||||
|
// tgt_xform.rotation = tgt_working_xform.rotation
|
||||||
|
//
|
||||||
|
// // Check if the translation component is animated
|
||||||
|
// if src_local_xform.translation != src_local_bind_pose.translation {
|
||||||
|
// // Apply target-local translation
|
||||||
|
// src_world_xlation = src_inv_bind_pose * src_local_xform.translation
|
||||||
|
// tgt_local_xlation = tgt_inv_parent_xform * src_world_xlation
|
||||||
|
// tgt_xform.translation = tgt_local_xlation
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
let mut tgt_working_xform = src_world_xform.affine() * (**src_inv_bind_pose);
|
||||||
|
|
||||||
|
// The parent bone's world transform must be computed each iteration,
|
||||||
|
// since it will be affected by the rotations applied on the previous
|
||||||
|
// iteration.
|
||||||
let parent_xform = node
|
let parent_xform = node
|
||||||
.parent
|
.parent
|
||||||
.and_then(|parent| q_xforms.get(parent).ok())
|
.and_then(|parent| q_xforms.get(parent).ok())
|
||||||
@@ -340,31 +382,33 @@ fn copy_poses(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tgt_inv_parent_mat = parent_world_xform.affine().inverse();
|
let tgt_inv_parent_xform = parent_world_xform.affine().inverse();
|
||||||
local_mat = tgt_inv_parent_mat * local_mat * (**tgt_bind_pose);
|
tgt_working_xform = tgt_inv_parent_xform * tgt_working_xform * (**tgt_world_bind_pose);
|
||||||
|
|
||||||
let src_local_xlation = q_xforms.get(src_ent).map(|xform| xform.translation);
|
// Apply the local rotation
|
||||||
|
if let Ok(mut tgt_xform) = q_xforms.get_mut(node.id) {
|
||||||
let Ok(mut tgt_xform) = q_xforms.get_mut(node.id) else {
|
tgt_xform.rotation = Quat::from_affine3(&tgt_working_xform);
|
||||||
return;
|
}
|
||||||
};
|
|
||||||
tgt_xform.rotation = Quat::from_affine3(&local_mat);
|
|
||||||
|
|
||||||
|
// Get the query data needed to compute the target bone's local translation
|
||||||
let Ok(src_local_bind_pose) = q_local_bind_poses.get(src_ent) else {
|
let Ok(src_local_bind_pose) = q_local_bind_poses.get(src_ent) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Ok(src_world_bind_pose) = q_world_bind_poses.get(src_ent) else {
|
let Ok(src_world_bind_pose) = q_world_bind_poses.get(src_ent) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Ok(src_local_xlation) = q_xforms.get(src_ent).map(|xform| xform.translation) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if let Ok(src_local_xlation) = src_local_xlation {
|
// If the source bone translation != its bind pose translation,
|
||||||
if !src_local_xlation.nearly_eq_within(src_local_bind_pose.translation, 1e-3) {
|
// this translation component is animated (likely root motion)
|
||||||
let src_world_xlation = src_world_bind_pose.transform_vector3(src_local_xlation);
|
if !src_local_xlation.nearly_eq_within(src_local_bind_pose.translation, 1e-3) {
|
||||||
let tgt_local_xlation = parent_world_xform
|
let src_world_xlation = src_world_bind_pose.transform_vector3(src_local_xlation);
|
||||||
.affine()
|
let tgt_local_xlation = tgt_inv_parent_xform.transform_vector3(src_world_xlation);
|
||||||
.inverse()
|
|
||||||
.transform_vector3(src_world_xlation);
|
|
||||||
|
|
||||||
|
// Apply the local translation
|
||||||
|
if let Ok(mut tgt_xform) = q_xforms.get_mut(node.id) {
|
||||||
tgt_xform.translation = tgt_local_xlation;
|
tgt_xform.translation = tgt_local_xlation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user