Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/gltf/gltf_extension_animation_graph.rs
9367 views
1
//! Uses glTF extension processing to play an animation on a skinned glTF model of a fox.
2
3
use std::f32::consts::PI;
4
5
use bevy::{
6
asset::LoadContext,
7
ecs::entity::EntityHashSet,
8
gltf::extensions::{GltfExtensionHandler, GltfExtensionHandlers},
9
light::CascadeShadowConfigBuilder,
10
platform::collections::{HashMap, HashSet},
11
prelude::*,
12
scene::SceneInstanceReady,
13
};
14
15
/// An example asset that contains a mesh and animation.
16
const GLTF_PATH: &str = "models/animated/Fox.glb";
17
18
fn main() {
19
App::new()
20
.insert_resource(GlobalAmbientLight {
21
color: Color::WHITE,
22
brightness: 2000.,
23
..default()
24
})
25
.add_plugins((DefaultPlugins, GltfExtensionHandlerAnimationPlugin))
26
.add_systems(
27
Startup,
28
(setup_mesh_and_animation, setup_camera_and_environment),
29
)
30
.run();
31
}
32
33
/// A component that stores a reference to an animation we want to play. This is
34
/// created when we start loading the mesh (see `setup_mesh_and_animation`) and
35
/// read when the mesh has spawned (see `play_animation_once_loaded`).
36
#[derive(Component, Reflect)]
37
#[reflect(Component)]
38
struct AnimationToPlay {
39
graph_handle: Handle<AnimationGraph>,
40
index: AnimationNodeIndex,
41
}
42
43
fn setup_mesh_and_animation(mut commands: Commands, asset_server: Res<AssetServer>) {
44
// Spawn an entity with our components, and connect it to an observer that
45
// will trigger when the scene is loaded and spawned.
46
commands
47
.spawn(SceneRoot(
48
asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)),
49
))
50
.observe(play_animation_when_ready);
51
}
52
53
fn play_animation_when_ready(
54
scene_ready: On<SceneInstanceReady>,
55
mut commands: Commands,
56
children: Query<&Children>,
57
mut players: Query<(&mut AnimationPlayer, &AnimationToPlay)>,
58
) {
59
for child in children.iter_descendants(scene_ready.entity) {
60
let Ok((mut player, animation_to_play)) = players.get_mut(child) else {
61
continue;
62
};
63
64
// Tell the animation player to start the animation and keep
65
// repeating it.
66
//
67
// If you want to try stopping and switching animations, see the
68
// `animated_mesh_control.rs` example.
69
player.play(animation_to_play.index).repeat();
70
71
// Add the animation graph. This only needs to be done once to
72
// connect the animation player to the mesh.
73
commands
74
.entity(child)
75
.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
76
}
77
}
78
79
/// Spawn a camera and a simple environment with a ground plane and light.
80
fn setup_camera_and_environment(
81
mut commands: Commands,
82
mut meshes: ResMut<Assets<Mesh>>,
83
mut materials: ResMut<Assets<StandardMaterial>>,
84
) {
85
// Camera
86
commands.spawn((
87
Camera3d::default(),
88
Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
89
));
90
91
// Plane
92
commands.spawn((
93
Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),
94
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
95
));
96
97
// Light
98
commands.spawn((
99
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
100
DirectionalLight {
101
shadow_maps_enabled: true,
102
..default()
103
},
104
CascadeShadowConfigBuilder {
105
first_cascade_far_bound: 200.0,
106
maximum_distance: 400.0,
107
..default()
108
}
109
.build(),
110
));
111
}
112
113
struct GltfExtensionHandlerAnimationPlugin;
114
115
impl Plugin for GltfExtensionHandlerAnimationPlugin {
116
fn build(&self, app: &mut App) {
117
#[cfg(target_family = "wasm")]
118
bevy::tasks::block_on(async {
119
app.world_mut()
120
.resource_mut::<GltfExtensionHandlers>()
121
.0
122
.write()
123
.await
124
.push(Box::new(GltfExtensionHandlerAnimation::default()))
125
});
126
#[cfg(not(target_family = "wasm"))]
127
app.world_mut()
128
.resource_mut::<GltfExtensionHandlers>()
129
.0
130
.write_blocking()
131
.push(Box::new(GltfExtensionHandlerAnimation::default()));
132
}
133
}
134
135
#[derive(Default, Clone)]
136
struct GltfExtensionHandlerAnimation {
137
animation_root_indices: HashSet<usize>,
138
animation_root_entities: EntityHashSet,
139
clip: Option<Handle<AnimationClip>>,
140
}
141
142
impl GltfExtensionHandler for GltfExtensionHandlerAnimation {
143
fn dyn_clone(&self) -> Box<dyn GltfExtensionHandler> {
144
Box::new((*self).clone())
145
}
146
147
#[cfg(feature = "bevy_animation")]
148
fn on_animation(&mut self, gltf_animation: &gltf::Animation, handle: Handle<AnimationClip>) {
149
if gltf_animation.name().is_some_and(|v| v == "Walk") {
150
self.clip = Some(handle.clone());
151
}
152
}
153
#[cfg(feature = "bevy_animation")]
154
fn on_animations_collected(
155
&mut self,
156
_load_context: &mut LoadContext<'_>,
157
_animations: &[Handle<AnimationClip>],
158
_named_animations: &HashMap<Box<str>, Handle<AnimationClip>>,
159
animation_roots: &HashSet<usize>,
160
) {
161
self.animation_root_indices = animation_roots.clone();
162
}
163
164
fn on_gltf_node(
165
&mut self,
166
_load_context: &mut LoadContext<'_>,
167
gltf_node: &gltf::Node,
168
entity: &mut EntityWorldMut,
169
) {
170
if self.animation_root_indices.contains(&gltf_node.index()) {
171
self.animation_root_entities.insert(entity.id());
172
}
173
}
174
175
/// Called when an individual Scene is done processing
176
fn on_scene_completed(
177
&mut self,
178
load_context: &mut LoadContext<'_>,
179
_scene: &gltf::Scene,
180
_world_root_id: Entity,
181
world: &mut World,
182
) {
183
// Create an AnimationGraph from the desired clip
184
let (graph, index) = AnimationGraph::from_clip(self.clip.clone().unwrap());
185
// Store the animation graph as an asset with an arbitrary label
186
// We only have one graph, so this label will be unique
187
let graph_handle =
188
load_context.add_labeled_asset("MyAnimationGraphLabel".to_string(), graph);
189
190
// Create a component that stores a reference to our animation
191
let animation_to_play = AnimationToPlay {
192
graph_handle,
193
index,
194
};
195
196
// Insert the `AnimationToPlay` component on the first animation root
197
let mut entity = world.entity_mut(*self.animation_root_entities.iter().next().unwrap());
198
entity.insert(animation_to_play);
199
}
200
}
201
202