Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/tests/3d/test_invalid_skinned_mesh.rs
6592 views
1
//! Test that the renderer can handle various invalid skinned meshes
2
3
use 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
};
14
use core::f32::consts::TAU;
15
16
fn main() {
17
App::new()
18
.add_plugins(DefaultPlugins)
19
.insert_resource(AmbientLight {
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
28
fn 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
// Add motion blur so we can check if it's working for skinned meshes.
60
// This also exercises the renderer's prepass path.
61
MotionBlur {
62
// Use an unrealistically large shutter angle so that motion blur is clearly visible.
63
shutter_angle: 3.0,
64
samples: 2,
65
},
66
// MSAA and MotionBlur together are not compatible on WebGL.
67
#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
68
Msaa::Off,
69
));
70
71
// Add a directional light to make sure we exercise the renderer's shadow path.
72
commands.spawn((
73
Transform::from_xyz(1.0, 1.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
74
DirectionalLight {
75
shadows_enabled: true,
76
..default()
77
},
78
));
79
80
// Add a plane behind the meshes so we can see the shadows.
81
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
92
fn 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
// Create a mesh with two rectangles.
99
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
// Copy the mesh and add skinning attributes that bind each rectangle to a joint.
120
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
// Skip variations that are currently broken. See https://github.com/bevyengine/bevy/issues/16929,
174
// https://github.com/bevyengine/bevy/pull/18074.
175
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
// Add a square behind the mesh to distinguish it from the other meshes.
212
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)]
221
struct AnimatedJoint;
222
223
fn 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
}
232
233