Skip to main content

test_skinned_mesh_bounds/
test_skinned_mesh_bounds.rs

1//! Test `SkinnedMeshBounds` by showing the bounds of various animated meshes.
2
3use 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            // Test that skinned mesh bounds work even if the mesh is render
134            // world only.
135            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        // Simple cases. First joint is still, second joint is all rotation/translation/scale variations.
199        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        // Skewed cases. First joint is non-uniform scaling, second joint is rotation/translation variations.
208        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}