Mixamo animation support (#37)

This PR adds support for Mixamo FBX imports (via backend conversion to
glTF/glB) and some miscellaneous improvements to the animation systems.

### User-facing improvements
* Retargeting math has been fixed to eliminate jittering issues
* Retargeter can now handle animated bone translations when present
  (generally this is only used for root motion)

### Housekeeping / Code improvements

* Animation-related components and system logic have been moved to a
  dedicated `anim` module and `AnimPlugin`
* Retargeting stuff has been moved from the `bvh` module to
  `anim::retargeting`
* Component-based solution for marking an entity as an animation
  retargeting source makes it easy to extend the retargeting functionality
  to new types of source animations

### Limitations

Currently the code assumes that there will be at most one animation
source at a time. That single animation (if present) will be retargeted
to all other skeletons in the scene. A component-based solution for
directing a specific target skeleton to use a specific source animation
is possible, but will require some additional consideration around the
implementation design for the read-only "viewer" modes.

### Test animations:

* Hip-Hop Dancing (Mixamo): ?mode=viewer&mixamo=mocap/hip-hop-dancing.gltf
* Shuffle (MocapNET): ?mode=viewer&bvh=mocap/shuffle.bvh
* Scott (MocapNET): ?mode=viewer&bvh=mocap/scott.bvh
* Scott 1 (MocapNET): ?mode=viewer&bvh=mocap/scott1.bvh
This commit is contained in:
Danny McGee
2024-02-09 13:21:16 -05:00
committed by GitHub
parent d1b4aab8ca
commit a5aeff7a3a
13 changed files with 4109 additions and 299 deletions
+4 -73
View File
@@ -6,22 +6,18 @@ use bevy::{
tonemapping::Tonemapping,
},
gltf::Gltf,
math::{vec2, Affine3A},
math::vec2,
pbr::ShadowFilteringMethod,
prelude::*,
render::{mesh::skinning::SkinnedMesh, primitives::Aabb, view::NoFrustumCulling},
render::primitives::Aabb,
scene::InstanceId,
};
use space_editor::{
prelude::PrefabMarker,
space_prefab::{component::SceneAutoChild, editor_registry::EditorRegistryExt},
};
use space_editor::{prelude::PrefabMarker, space_prefab::component::SceneAutoChild};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{
bvh::{Bvh, CommandsSpawnBvhExt},
camera_controller::{CameraController, CameraControllerBundle},
debug::DebugPoint,
math::{Combinable, Extrema},
@@ -40,14 +36,7 @@ impl Plugin for ScenePlugin {
.add_state::<SceneState>()
.add_event::<LoadSceneEvent>()
.add_systems(Startup, initialize)
.add_systems(
Update,
(
spawn_test_bvh.run_if(resource_exists::<TestBvh>()), // TODO
init_scene_loading,
)
.in_set(SceneSpawnerSystem),
)
.add_systems(Update, init_scene_loading.in_set(SceneSpawnerSystem))
.add_systems(
OnEnter(SceneState::Active),
(tag_scene_elements, frame_scene_in_view).in_set(SceneSpawnerSystem),
@@ -71,10 +60,6 @@ impl Plugin for ScenePlugin {
//setup default light
app.add_systems(Startup, minimal_scene_setup);
app.editor_registry::<BindPoseLocal>();
app.editor_registry::<BindPoseGlobal>();
app.editor_registry::<InvBindPose>();
#[cfg(feature = "wasm")]
app.add_systems(
Update,
@@ -190,25 +175,8 @@ pub struct SceneData {
pub scene_path: String,
}
#[derive(Component)]
pub struct SkeletalJoint(pub Color);
#[derive(Component)]
pub struct SceneRoot;
#[derive(Resource, Deref, DerefMut)]
pub struct TestBvh(pub Handle<Bvh>); // TODO
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct BindPoseLocal(pub Transform);
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct BindPoseGlobal(pub Affine3A);
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct InvBindPose(pub Affine3A);
fn initialize(mut cmd: Commands) {
// Camera, graphics settings
@@ -242,23 +210,6 @@ fn initialize(mut cmd: Commands) {
));
}
// TODO
fn spawn_test_bvh(
mut commands: Commands,
mut er_bvh_events: EventReader<AssetEvent<Bvh>>,
r_bvh: Res<TestBvh>,
) {
for event in er_bvh_events.read() {
if matches!(event, AssetEvent::LoadedWithDependencies { .. }) {
commands.spawn_bvh_with_transform(
&r_bvh.0,
Transform::from_scale(Vec3::splat(0.06))
.with_translation(Vec3::new(0., 1.0, 0.524)),
);
}
}
}
fn init_scene_loading(mut cmd: Commands, mut er: EventReader<LoadSceneEvent>) {
if let Some(id) = er.read().last() {
info!("Loading scene: {}", id.0);
@@ -299,8 +250,6 @@ fn tag_scene_elements(
Without<NoMeshElement>,
),
>,
q_skinned_meshes: Query<(Entity, &SkinnedMesh), Without<InitedKointSkinnedMesh>>,
mut q_xforms: ParamSet<(TransformHelper, Query<&Transform>)>,
mesh_entities: Query<Entity, With<Handle<Mesh>>>,
) {
for (e, children) in q_named_non_primitives.iter() {
@@ -317,24 +266,6 @@ fn tag_scene_elements(
cmd.entity(e).insert(NoMeshElement);
}
}
for (ent, skinned_mesh) in q_skinned_meshes.iter() {
cmd.entity(ent)
.insert(NoFrustumCulling)
.insert(InitedKointSkinnedMesh);
for &joint in skinned_mesh.joints.iter() {
let global_bind_pose = q_xforms.p0().compute_global_transform(joint).unwrap();
let local_bind_pose = *q_xforms.p1().get(joint).unwrap();
cmd.entity(joint).insert((
SkeletalJoint(Color::BLACK),
BindPoseLocal(local_bind_pose),
BindPoseGlobal(global_bind_pose.affine()),
InvBindPose(global_bind_pose.affine().inverse()),
));
}
}
}
fn frame_scene_in_view(