test_invalid_skinned_mesh/
test_invalid_skinned_mesh.rs1use bevy::{
4 asset::RenderAssetUsages,
5 camera::ScalingMode,
6 math::ops,
7 mesh::{
8 skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
9 Indices, PrimitiveTopology, VertexAttributeValues,
10 },
11 post_process::motion_blur::MotionBlur,
12 prelude::*,
13};
14use core::f32::consts::TAU;
15
16fn main() {
17 App::new()
18 .add_plugins(DefaultPlugins)
19 .insert_resource(GlobalAmbientLight {
20 brightness: 20_000.0,
21 ..default()
22 })
23 .add_systems(Startup, (setup_environment, setup_meshes))
24 .add_systems(Update, update_animated_joints)
25 .run();
26}
27
28fn setup_environment(
29 mut commands: Commands,
30 mut mesh_assets: ResMut<Assets<Mesh>>,
31 mut material_assets: ResMut<Assets<StandardMaterial>>,
32) {
33 let description = "(left to right)\n\
34 0: Normal skinned mesh.\n\
35 1: Mesh asset is missing skinning attributes.\n\
36 2: One joint entity is missing.\n\
37 3: Mesh entity is missing SkinnedMesh component.";
38
39 commands.spawn((
40 Text::new(description),
41 Node {
42 position_type: PositionType::Absolute,
43 top: px(12),
44 left: px(12),
45 ..default()
46 },
47 ));
48
49 commands.spawn((
50 Camera3d::default(),
51 Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
52 Projection::Orthographic(OrthographicProjection {
53 scaling_mode: ScalingMode::AutoMin {
54 min_width: 19.0,
55 min_height: 6.0,
56 },
57 ..OrthographicProjection::default_3d()
58 }),
59 MotionBlur {
62 shutter_angle: 3.0,
64 samples: 2,
65 },
66 #[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
68 Msaa::Off,
69 ));
70
71 commands.spawn((
73 Transform::from_xyz(1.0, 1.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
74 DirectionalLight {
75 shadow_maps_enabled: true,
76 ..default()
77 },
78 ));
79
80 commands.spawn((
82 Transform::from_xyz(0.0, 0.0, -1.0),
83 Mesh3d(mesh_assets.add(Plane3d::default().mesh().size(100.0, 100.0).normal(Dir3::Z))),
84 MeshMaterial3d(material_assets.add(StandardMaterial {
85 base_color: Color::srgb(0.05, 0.05, 0.15),
86 reflectance: 0.2,
87 ..default()
88 })),
89 ));
90}
91
92fn setup_meshes(
93 mut commands: Commands,
94 mut mesh_assets: ResMut<Assets<Mesh>>,
95 mut material_assets: ResMut<Assets<StandardMaterial>>,
96 mut inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>,
97) {
98 let unskinned_mesh = Mesh::new(
100 PrimitiveTopology::TriangleList,
101 RenderAssetUsages::default(),
102 )
103 .with_inserted_attribute(
104 Mesh::ATTRIBUTE_POSITION,
105 vec![
106 [-0.3, -0.3, 0.0],
107 [0.3, -0.3, 0.0],
108 [-0.3, 0.3, 0.0],
109 [0.3, 0.3, 0.0],
110 [-0.4, 0.8, 0.0],
111 [0.4, 0.8, 0.0],
112 [-0.4, 1.8, 0.0],
113 [0.4, 1.8, 0.0],
114 ],
115 )
116 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 8])
117 .with_inserted_indices(Indices::U16(vec![0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6]));
118
119 let skinned_mesh = unskinned_mesh
121 .clone()
122 .with_inserted_attribute(
123 Mesh::ATTRIBUTE_JOINT_INDEX,
124 VertexAttributeValues::Uint16x4(vec![
125 [0, 0, 0, 0],
126 [0, 0, 0, 0],
127 [0, 0, 0, 0],
128 [0, 0, 0, 0],
129 [1, 0, 0, 0],
130 [1, 0, 0, 0],
131 [1, 0, 0, 0],
132 [1, 0, 0, 0],
133 ]),
134 )
135 .with_inserted_attribute(
136 Mesh::ATTRIBUTE_JOINT_WEIGHT,
137 vec![[1.00, 0.00, 0.0, 0.0]; 8],
138 );
139
140 let unskinned_mesh_handle = mesh_assets.add(unskinned_mesh);
141 let skinned_mesh_handle = mesh_assets.add(skinned_mesh);
142
143 let inverse_bindposes_handle = inverse_bindposes_assets.add(vec![
144 Mat4::IDENTITY,
145 Mat4::from_translation(Vec3::new(0.0, -1.3, 0.0)),
146 ]);
147
148 let mesh_material_handle = material_assets.add(StandardMaterial::default());
149
150 let background_material_handle = material_assets.add(StandardMaterial {
151 base_color: Color::srgb(0.05, 0.15, 0.05),
152 reflectance: 0.2,
153 ..default()
154 });
155
156 #[derive(PartialEq)]
157 enum Variation {
158 Normal,
159 MissingMeshAttributes,
160 MissingJointEntity,
161 MissingSkinnedMeshComponent,
162 }
163
164 for (index, variation) in [
165 Variation::Normal,
166 Variation::MissingMeshAttributes,
167 Variation::MissingJointEntity,
168 Variation::MissingSkinnedMeshComponent,
169 ]
170 .into_iter()
171 .enumerate()
172 {
173 if (variation == Variation::MissingSkinnedMeshComponent)
176 || (variation == Variation::MissingMeshAttributes)
177 {
178 continue;
179 }
180
181 let transform = Transform::from_xyz(((index as f32) - 1.5) * 4.5, 0.0, 0.0);
182
183 let joint_0 = commands.spawn(transform).id();
184
185 let joint_1 = commands
186 .spawn((ChildOf(joint_0), AnimatedJoint, Transform::IDENTITY))
187 .id();
188
189 if variation == Variation::MissingJointEntity {
190 commands.entity(joint_1).despawn();
191 }
192
193 let mesh_handle = match variation {
194 Variation::MissingMeshAttributes => &unskinned_mesh_handle,
195 _ => &skinned_mesh_handle,
196 };
197
198 let mut entity_commands = commands.spawn((
199 Mesh3d(mesh_handle.clone()),
200 MeshMaterial3d(mesh_material_handle.clone()),
201 transform,
202 ));
203
204 if variation != Variation::MissingSkinnedMeshComponent {
205 entity_commands.insert(SkinnedMesh {
206 inverse_bindposes: inverse_bindposes_handle.clone(),
207 joints: vec![joint_0, joint_1],
208 });
209 }
210
211 commands.spawn((
213 Transform::from_xyz(transform.translation.x, transform.translation.y, -0.8),
214 Mesh3d(mesh_assets.add(Plane3d::default().mesh().size(4.3, 4.3).normal(Dir3::Z))),
215 MeshMaterial3d(background_material_handle.clone()),
216 ));
217 }
218}
219
220#[derive(Component)]
221struct AnimatedJoint;
222
223fn update_animated_joints(time: Res<Time>, query: Query<&mut Transform, With<AnimatedJoint>>) {
224 for mut transform in query {
225 let angle = TAU * 4.0 * ops::cos((time.elapsed_secs() / 8.0) * TAU);
226 let rotation = Quat::from_rotation_z(angle);
227
228 transform.rotation = rotation;
229 transform.translation = rotation.mul_vec3(Vec3::new(0.0, 1.3, 0.0));
230 }
231}