1use bevy::{
4 asset::RenderAssetUsages,
5 camera::visibility::DynamicSkinnedMeshBounds,
6 mesh::{
7 skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
8 PrimitiveTopology, VertexAttributeValues,
9 },
10 prelude::*,
11 world_serialization::WorldInstanceReady,
12};
13use std::f32::consts::{FRAC_PI_2, FRAC_PI_4};
14
15fn main() {
16 App::new()
17 .add_plugins(DefaultPlugins.set(WindowPlugin {
18 primary_window: Some(Window {
19 title: "Test Skinned Mesh Bounds".into(),
20 ..default()
21 }),
22 ..default()
23 }))
24 .insert_gizmo_config(
25 SkinnedMeshBoundsGizmoConfigGroup {
26 draw_all: true,
27 ..Default::default()
28 },
29 GizmoConfig::default(),
30 )
31 .insert_gizmo_config(
32 AabbGizmoConfigGroup {
33 draw_all: true,
34 ..Default::default()
35 },
36 GizmoConfig::default(),
37 )
38 .insert_resource(GlobalAmbientLight {
39 brightness: 2000.0,
40 ..Default::default()
41 })
42 .add_systems(Startup, setup)
43 .add_systems(Startup, load_scene)
44 .add_systems(Update, spawn_scene)
45 .add_systems(Startup, spawn_custom_meshes)
46 .add_systems(Update, update_custom_mesh_animation)
47 .run();
48}
49
50fn setup(mut commands: Commands) {
51 commands.spawn((
52 Camera3d::default(),
53 Transform::from_xyz(0.0, 7.5, 18.0).looking_at(Vec3::new(0.0, 5.5, 0.0), Vec3::Y),
54 ));
55}
56
57#[derive(Component, Debug, Default)]
58struct PendingScene(Handle<Gltf>);
59
60#[derive(Component, Debug, Default)]
61struct PendingAnimation((Handle<AnimationGraph>, AnimationNodeIndex));
62
63fn load_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
64 commands.spawn((
65 PendingScene(asset_server.load("models/animated/Fox.glb")),
66 Transform::from_xyz(1.3, 4.3, 0.0)
67 .with_scale(Vec3::splat(0.08))
68 .looking_to(-Vec3::X, Vec3::Y),
69 ));
70}
71
72fn spawn_scene(
73 mut commands: Commands,
74 query: Query<(Entity, &PendingScene)>,
75 assets: Res<Assets<Gltf>>,
76 mut graphs: ResMut<Assets<AnimationGraph>>,
77) {
78 for (entity, PendingScene(asset)) in query.iter() {
79 if let Some(gltf) = assets.get(asset)
80 && let Some(scene_handle) = gltf.scenes.first()
81 && let Some(animation_handle) = gltf.named_animations.get("Run")
82 {
83 let (graph, graph_node_index) = AnimationGraph::from_clip(animation_handle.clone());
84
85 commands
86 .entity(entity)
87 .remove::<PendingScene>()
88 .insert((
89 WorldAssetRoot(scene_handle.clone()),
90 PendingAnimation((graphs.add(graph), graph_node_index)),
91 ))
92 .observe(play_animation);
93 }
94 }
95}
96
97fn play_animation(
98 trigger: On<WorldInstanceReady>,
99 mut commands: Commands,
100 children: Query<&Children>,
101 animations: Query<&PendingAnimation>,
102 mut players: Query<&mut AnimationPlayer>,
103) {
104 if let Ok(PendingAnimation((graph_handle, graph_node_index))) = animations.get(trigger.entity) {
105 for child in children.iter_descendants(trigger.entity) {
106 if let Ok(mut player) = players.get_mut(child) {
107 player.play(*graph_node_index).set_speed(0.6).repeat();
108
109 commands
110 .entity(child)
111 .insert(AnimationGraphHandle(graph_handle.clone()));
112 }
113 }
114 }
115
116 commands.entity(trigger.entity).remove::<PendingAnimation>();
117}
118
119type CustomAnimationId = i8;
120
121#[derive(Component)]
122struct CustomAnimation(CustomAnimationId);
123
124fn spawn_custom_meshes(
125 mut commands: Commands,
126 mut mesh_assets: ResMut<Assets<Mesh>>,
127 mut material_assets: ResMut<Assets<StandardMaterial>>,
128 mut inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>,
129) {
130 let mesh_handle = mesh_assets.add(
131 Mesh::new(
132 PrimitiveTopology::TriangleStrip,
133 RenderAssetUsages::RENDER_WORLD,
136 )
137 .with_inserted_attribute(
138 Mesh::ATTRIBUTE_POSITION,
139 vec![
140 [-0.5, 0.0, 0.0],
141 [0.5, 0.0, 0.0],
142 [-0.5, 0.5, 0.0],
143 [0.5, 0.5, 0.0],
144 [-0.5, 1.0, 0.0],
145 [0.5, 1.0, 0.0],
146 [-0.5, 1.5, 0.0],
147 [0.5, 1.5, 0.0],
148 [-0.5, 2.0, 0.0],
149 [0.5, 2.0, 0.0],
150 ],
151 )
152 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10])
153 .with_inserted_attribute(
154 Mesh::ATTRIBUTE_JOINT_INDEX,
155 VertexAttributeValues::Uint16x4(vec![
156 [1, 0, 0, 0],
157 [1, 0, 0, 0],
158 [1, 2, 0, 0],
159 [1, 2, 0, 0],
160 [1, 2, 0, 0],
161 [1, 2, 0, 0],
162 [2, 1, 0, 0],
163 [2, 1, 0, 0],
164 [2, 0, 0, 0],
165 [2, 0, 0, 0],
166 ]),
167 )
168 .with_inserted_attribute(
169 Mesh::ATTRIBUTE_JOINT_WEIGHT,
170 vec![
171 [1.00, 0.00, 0.0, 0.0],
172 [1.00, 0.00, 0.0, 0.0],
173 [0.75, 0.25, 0.0, 0.0],
174 [0.75, 0.25, 0.0, 0.0],
175 [0.50, 0.50, 0.0, 0.0],
176 [0.50, 0.50, 0.0, 0.0],
177 [0.75, 0.25, 0.0, 0.0],
178 [0.75, 0.25, 0.0, 0.0],
179 [1.00, 0.00, 0.0, 0.0],
180 [1.00, 0.00, 0.0, 0.0],
181 ],
182 )
183 .with_generated_skinned_mesh_bounds()
184 .unwrap(),
185 );
186
187 let inverse_bindposes_handle = inverse_bindposes_assets.add(vec![
188 Mat4::from_translation(Vec3::new(0.0, 0.0, 0.0)),
189 Mat4::from_translation(Vec3::new(0.0, 0.0, 0.0)),
190 Mat4::from_translation(Vec3::new(0.0, -1.0, 0.0)),
191 ]);
192
193 struct MeshInstance {
194 animations: [CustomAnimationId; 2],
195 }
196
197 let mesh_instances = [
198 MeshInstance { animations: [0, 1] },
200 MeshInstance { animations: [0, 2] },
201 MeshInstance { animations: [0, 3] },
202 MeshInstance { animations: [0, 4] },
203 MeshInstance { animations: [0, 5] },
204 MeshInstance { animations: [0, 6] },
205 MeshInstance { animations: [0, 7] },
206 MeshInstance { animations: [0, 8] },
207 MeshInstance { animations: [9, 1] },
209 MeshInstance { animations: [9, 2] },
210 MeshInstance { animations: [9, 3] },
211 MeshInstance { animations: [9, 4] },
212 MeshInstance { animations: [9, 5] },
213 ];
214
215 for (i, mesh_instance) in mesh_instances.iter().enumerate() {
216 let x = ((i as f32) * 2.0) - ((mesh_instances.len() - 1) as f32);
217
218 let base_entity = commands
219 .spawn((Transform::from_xyz(x, 0.0, 0.0), Visibility::default()))
220 .id();
221
222 let joints = vec![
223 commands.spawn((Transform::IDENTITY,)).id(),
224 commands
225 .spawn((
226 CustomAnimation(mesh_instance.animations[0]),
227 Transform::IDENTITY,
228 ))
229 .id(),
230 commands
231 .spawn((
232 CustomAnimation(mesh_instance.animations[1]),
233 Transform::IDENTITY,
234 ))
235 .id(),
236 ];
237
238 commands.entity(joints[0]).insert(ChildOf(base_entity));
239
240 commands.entity(joints[1]).insert(ChildOf(joints[0]));
241 commands.entity(joints[2]).insert(ChildOf(joints[1]));
242
243 let mesh_entity = commands
244 .spawn((
245 Transform::IDENTITY,
246 Mesh3d(mesh_handle.clone()),
247 MeshMaterial3d(material_assets.add(StandardMaterial {
248 base_color: Color::WHITE,
249 cull_mode: None,
250 ..default()
251 })),
252 SkinnedMesh {
253 inverse_bindposes: inverse_bindposes_handle.clone(),
254 joints: joints.clone(),
255 },
256 DynamicSkinnedMeshBounds,
257 ))
258 .id();
259
260 commands.entity(mesh_entity).insert(ChildOf(base_entity));
261 }
262}
263
264fn update_custom_mesh_animation(
265 time: Res<Time<Virtual>>,
266 mut query: Query<(&mut Transform, &CustomAnimation)>,
267) {
268 let t = time.elapsed_secs();
269 let ts = ops::sin(t);
270 let tc = ops::cos(t);
271 let ots = ops::sin(t + FRAC_PI_4);
272 let otc = ops::cos(t + FRAC_PI_4);
273
274 for (mut transform, animation) in &mut query {
275 match animation.0 {
276 1 => transform.translation = Vec3::new(0.5 * ts, 0.3 + tc, 0.0),
277 2 => transform.translation = Vec3::new(0.0, 0.5 + ts, tc),
278 3 => transform.rotation = Quat::from_rotation_x(FRAC_PI_2 * ts),
279 4 => transform.rotation = Quat::from_rotation_y(FRAC_PI_2 * ts),
280 5 => transform.rotation = Quat::from_rotation_z(FRAC_PI_2 * ts),
281 6 => transform.scale.x = ts * 1.5,
282 7 => transform.scale.y = ts * 1.5,
283 8 => transform.scale = Vec3::new(ts * 1.5, otc * 1.5, 1.0),
284 9 => transform.scale = Vec3::new(ots, 1.0 + (tc * 0.3), 1.0 - (tc * 0.5)),
285 _ => (),
286 }
287 }
288}