Path: blob/main/examples/gltf/gltf_extension_animation_graph.rs
9367 views
//! Uses glTF extension processing to play an animation on a skinned glTF model of a fox.12use std::f32::consts::PI;34use bevy::{5asset::LoadContext,6ecs::entity::EntityHashSet,7gltf::extensions::{GltfExtensionHandler, GltfExtensionHandlers},8light::CascadeShadowConfigBuilder,9platform::collections::{HashMap, HashSet},10prelude::*,11scene::SceneInstanceReady,12};1314/// An example asset that contains a mesh and animation.15const GLTF_PATH: &str = "models/animated/Fox.glb";1617fn main() {18App::new()19.insert_resource(GlobalAmbientLight {20color: Color::WHITE,21brightness: 2000.,22..default()23})24.add_plugins((DefaultPlugins, GltfExtensionHandlerAnimationPlugin))25.add_systems(26Startup,27(setup_mesh_and_animation, setup_camera_and_environment),28)29.run();30}3132/// A component that stores a reference to an animation we want to play. This is33/// created when we start loading the mesh (see `setup_mesh_and_animation`) and34/// read when the mesh has spawned (see `play_animation_once_loaded`).35#[derive(Component, Reflect)]36#[reflect(Component)]37struct AnimationToPlay {38graph_handle: Handle<AnimationGraph>,39index: AnimationNodeIndex,40}4142fn setup_mesh_and_animation(mut commands: Commands, asset_server: Res<AssetServer>) {43// Spawn an entity with our components, and connect it to an observer that44// will trigger when the scene is loaded and spawned.45commands46.spawn(SceneRoot(47asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)),48))49.observe(play_animation_when_ready);50}5152fn play_animation_when_ready(53scene_ready: On<SceneInstanceReady>,54mut commands: Commands,55children: Query<&Children>,56mut players: Query<(&mut AnimationPlayer, &AnimationToPlay)>,57) {58for child in children.iter_descendants(scene_ready.entity) {59let Ok((mut player, animation_to_play)) = players.get_mut(child) else {60continue;61};6263// Tell the animation player to start the animation and keep64// repeating it.65//66// If you want to try stopping and switching animations, see the67// `animated_mesh_control.rs` example.68player.play(animation_to_play.index).repeat();6970// Add the animation graph. This only needs to be done once to71// connect the animation player to the mesh.72commands73.entity(child)74.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));75}76}7778/// Spawn a camera and a simple environment with a ground plane and light.79fn setup_camera_and_environment(80mut commands: Commands,81mut meshes: ResMut<Assets<Mesh>>,82mut materials: ResMut<Assets<StandardMaterial>>,83) {84// Camera85commands.spawn((86Camera3d::default(),87Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),88));8990// Plane91commands.spawn((92Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),93MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),94));9596// Light97commands.spawn((98Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),99DirectionalLight {100shadow_maps_enabled: true,101..default()102},103CascadeShadowConfigBuilder {104first_cascade_far_bound: 200.0,105maximum_distance: 400.0,106..default()107}108.build(),109));110}111112struct GltfExtensionHandlerAnimationPlugin;113114impl Plugin for GltfExtensionHandlerAnimationPlugin {115fn build(&self, app: &mut App) {116#[cfg(target_family = "wasm")]117bevy::tasks::block_on(async {118app.world_mut()119.resource_mut::<GltfExtensionHandlers>()120.0121.write()122.await123.push(Box::new(GltfExtensionHandlerAnimation::default()))124});125#[cfg(not(target_family = "wasm"))]126app.world_mut()127.resource_mut::<GltfExtensionHandlers>()128.0129.write_blocking()130.push(Box::new(GltfExtensionHandlerAnimation::default()));131}132}133134#[derive(Default, Clone)]135struct GltfExtensionHandlerAnimation {136animation_root_indices: HashSet<usize>,137animation_root_entities: EntityHashSet,138clip: Option<Handle<AnimationClip>>,139}140141impl GltfExtensionHandler for GltfExtensionHandlerAnimation {142fn dyn_clone(&self) -> Box<dyn GltfExtensionHandler> {143Box::new((*self).clone())144}145146#[cfg(feature = "bevy_animation")]147fn on_animation(&mut self, gltf_animation: &gltf::Animation, handle: Handle<AnimationClip>) {148if gltf_animation.name().is_some_and(|v| v == "Walk") {149self.clip = Some(handle.clone());150}151}152#[cfg(feature = "bevy_animation")]153fn on_animations_collected(154&mut self,155_load_context: &mut LoadContext<'_>,156_animations: &[Handle<AnimationClip>],157_named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,158animation_roots: &HashSet<usize>,159) {160self.animation_root_indices = animation_roots.clone();161}162163fn on_gltf_node(164&mut self,165_load_context: &mut LoadContext<'_>,166gltf_node: &gltf::Node,167entity: &mut EntityWorldMut,168) {169if self.animation_root_indices.contains(&gltf_node.index()) {170self.animation_root_entities.insert(entity.id());171}172}173174/// Called when an individual Scene is done processing175fn on_scene_completed(176&mut self,177load_context: &mut LoadContext<'_>,178_scene: &gltf::Scene,179_world_root_id: Entity,180world: &mut World,181) {182// Create an AnimationGraph from the desired clip183let (graph, index) = AnimationGraph::from_clip(self.clip.clone().unwrap());184// Store the animation graph as an asset with an arbitrary label185// We only have one graph, so this label will be unique186let graph_handle =187load_context.add_labeled_asset("MyAnimationGraphLabel".to_string(), graph);188189// Create a component that stores a reference to our animation190let animation_to_play = AnimationToPlay {191graph_handle,192index,193};194195// Insert the `AnimationToPlay` component on the first animation root196let mut entity = world.entity_mut(*self.animation_root_entities.iter().next().unwrap());197entity.insert(animation_to_play);198}199}200201202