Auto-play animations in imported scenes (#91)

This PR adds a system to find animation clips in imported glTF scenes
and auto-play them on their associated entities.

The new system runs only in "Viewer" mode, for the purposes of
previewing a scene import on its Storyteller.ai Media File page. In
"Editor" mode, we will eventually expose animation clips to the user
through some other mechanism (TDB -- needs design work, grooming,
etc.).
This commit is contained in:
Danny McGee
2024-03-21 22:40:04 -04:00
committed by GitHub
parent 701ba04765
commit 97a4aa5716
+74 -3
View File
@@ -11,12 +11,13 @@ use bevy::{
prelude::*,
render::{camera::RenderTarget, primitives::Aabb},
scene::InstanceId,
utils::{EntityHashSet, HashSet},
};
#[cfg(feature = "wasm")]
use serde_repr::{Deserialize_repr, Serialize_repr};
use space_editor::{
prelude::{EditorEvent, EditorPrefabPath, PrefabMarker},
space_prefab::component::SceneAutoChild,
space_prefab::component::{GltfPrefab, SceneAutoChild},
};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
@@ -28,6 +29,7 @@ use crate::{
change_tracking::{ChangeTracker, ChangeTrackingPlugin},
debug::DebugPoint,
headless::{is_headless, is_not_headless, HeadlessSettings},
is_editor,
math::{Combinable, Extrema},
screenshot::ScreenCapture,
MainCamera,
@@ -72,7 +74,13 @@ impl Plugin for ScenePlugin {
.in_set(SceneSpawnerSystem),
);
app.add_systems(Update, tag_scene_elements);
app.add_systems(
Update,
(
tag_scene_elements,
autoplay_animations.run_if(not(is_editor)),
),
);
//Auto loading screen
//Register handles on which we will look to show loading screen
@@ -360,7 +368,11 @@ fn init_object_loading(mut cmd: Commands, mut er: EventReader<LoadLibraryObjectE
}
}
fn init_scene_import(mut cmd: Commands, mut er: EventReader<ImportSceneEvent>) {
fn init_scene_import(
mut cmd: Commands,
r_assets: Res<AssetServer>,
mut er: EventReader<ImportSceneEvent>,
) {
if let Some(path) = er.read().last() {
info!("Importing asset: {}", path.0);
cmd.spawn((
@@ -370,6 +382,7 @@ fn init_scene_import(mut cmd: Commands, mut er: EventReader<ImportSceneEvent>) {
path: path.0.clone(),
scene: "Scene0".to_string(),
},
r_assets.load::<Gltf>(&path.0),
PrefabMarker,
SceneRoot,
));
@@ -415,6 +428,64 @@ fn tag_scene_elements(
}
}
fn autoplay_animations(
mut cmd: Commands,
mut l_done: Local<HashSet<String>>,
mut l_animating: Local<EntityHashSet<Entity>>,
ra_gltfs: Res<Assets<Gltf>>,
ra_anims: Res<Assets<AnimationClip>>,
q_prefabs: Query<(&GltfPrefab, &Handle<Gltf>)>,
q_named_ents: Query<(Entity, &Name)>,
mut q_players: Query<&mut AnimationPlayer>,
) {
for (prefab, handle) in q_prefabs.iter() {
if l_done.contains(&prefab.path) {
continue;
}
let Some(gltf) = ra_gltfs.get(handle) else {
continue;
};
let mut playing_anims = 0_usize;
for (anim_name, anim_handle) in gltf.named_animations.iter() {
let Some(anim) = ra_anims.get(anim_handle.clone()) else {
continue;
};
let Some((target, name)) = q_named_ents
.iter()
.find(|(_, name)| anim.compatible_with(name))
else {
continue;
};
if !l_animating.contains(&target) {
info!(
"Playing animation '{}' on entity {:?} '{}'",
anim_name, target, name,
);
if let Ok(mut player) = q_players.get_mut(target) {
if !player.is_playing_clip(anim_handle) {
player.play(anim_handle.clone()).repeat();
}
} else {
let mut player = AnimationPlayer::default();
player.play(anim_handle.clone()).repeat();
cmd.entity(target).insert(player);
}
l_animating.insert(target);
}
playing_anims += 1;
}
if playing_anims == gltf.named_animations.len() {
l_done.insert(prefab.path.clone());
}
}
}
fn frame_scene_in_view(
mut cmd: Commands,
q_aabbs: Query<(&Aabb, &GlobalTransform), With<SceneAutoChild>>,