Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/stress_tests/many_foxes.rs
9374 views
1
//! Loads animations from a skinned glTF, spawns many of them, and plays the
2
//! animation to stress test skinned meshes.
3
4
use std::{f32::consts::PI, time::Duration};
5
6
use argh::FromArgs;
7
use bevy::{
8
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
9
light::CascadeShadowConfigBuilder,
10
prelude::*,
11
scene::SceneInstanceReady,
12
window::{PresentMode, WindowResolution},
13
winit::WinitSettings,
14
};
15
16
#[derive(FromArgs, Resource)]
17
/// `many_foxes` stress test
18
struct Args {
19
/// whether all foxes run in sync.
20
#[argh(switch)]
21
sync: bool,
22
23
/// total number of foxes.
24
#[argh(option, default = "1000")]
25
count: usize,
26
}
27
28
#[derive(Resource)]
29
struct Foxes {
30
count: usize,
31
speed: f32,
32
moving: bool,
33
sync: bool,
34
}
35
36
fn main() {
37
// `from_env` panics on the web
38
#[cfg(not(target_arch = "wasm32"))]
39
let args: Args = argh::from_env();
40
#[cfg(target_arch = "wasm32")]
41
let args = Args::from_args(&[], &[]).unwrap();
42
43
App::new()
44
.add_plugins((
45
DefaultPlugins.set(WindowPlugin {
46
primary_window: Some(Window {
47
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".into(),
48
present_mode: PresentMode::AutoNoVsync,
49
resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0),
50
..default()
51
}),
52
..default()
53
}),
54
FrameTimeDiagnosticsPlugin::default(),
55
LogDiagnosticsPlugin::default(),
56
))
57
.insert_resource(StaticTransformOptimizations::disabled())
58
.insert_resource(WinitSettings::continuous())
59
.insert_resource(Foxes {
60
count: args.count,
61
speed: 2.0,
62
moving: true,
63
sync: args.sync,
64
})
65
.add_systems(Startup, setup)
66
.add_systems(
67
Update,
68
(
69
keyboard_animation_control,
70
update_fox_rings.after(keyboard_animation_control),
71
),
72
)
73
.run();
74
}
75
76
#[derive(Resource)]
77
struct Animations {
78
node_indices: Vec<AnimationNodeIndex>,
79
graph: Handle<AnimationGraph>,
80
}
81
82
const RING_SPACING: f32 = 2.0;
83
const FOX_SPACING: f32 = 2.0;
84
85
#[derive(Component, Clone, Copy)]
86
enum RotationDirection {
87
CounterClockwise,
88
Clockwise,
89
}
90
91
impl RotationDirection {
92
fn sign(&self) -> f32 {
93
match self {
94
RotationDirection::CounterClockwise => 1.0,
95
RotationDirection::Clockwise => -1.0,
96
}
97
}
98
}
99
100
#[derive(Component)]
101
struct Ring {
102
radius: f32,
103
}
104
105
fn setup(
106
mut commands: Commands,
107
asset_server: Res<AssetServer>,
108
mut meshes: ResMut<Assets<Mesh>>,
109
mut materials: ResMut<Assets<StandardMaterial>>,
110
mut animation_graphs: ResMut<Assets<AnimationGraph>>,
111
foxes: Res<Foxes>,
112
) {
113
warn!(include_str!("warning_string.txt"));
114
115
// Insert a resource with the current scene information
116
let animation_clips = [
117
asset_server.load(GltfAssetLabel::Animation(2).from_asset("models/animated/Fox.glb")),
118
asset_server.load(GltfAssetLabel::Animation(1).from_asset("models/animated/Fox.glb")),
119
asset_server.load(GltfAssetLabel::Animation(0).from_asset("models/animated/Fox.glb")),
120
];
121
let mut animation_graph = AnimationGraph::new();
122
let node_indices = animation_graph
123
.add_clips(animation_clips, 1.0, animation_graph.root)
124
.collect();
125
commands.insert_resource(Animations {
126
node_indices,
127
graph: animation_graphs.add(animation_graph),
128
});
129
130
// Foxes
131
// Concentric rings of foxes, running in opposite directions. The rings are spaced at 2m radius intervals.
132
// The foxes in each ring are spaced at least 2m apart around its circumference.'
133
134
// NOTE: This fox model faces +z
135
let fox_handle =
136
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb"));
137
138
let ring_directions = [
139
(
140
Quat::from_rotation_y(PI),
141
RotationDirection::CounterClockwise,
142
),
143
(Quat::IDENTITY, RotationDirection::Clockwise),
144
];
145
146
let mut ring_index = 0;
147
let mut radius = RING_SPACING;
148
let mut foxes_remaining = foxes.count;
149
150
info!("Spawning {} foxes...", foxes.count);
151
152
while foxes_remaining > 0 {
153
let (base_rotation, ring_direction) = ring_directions[ring_index % 2];
154
let ring_parent = commands
155
.spawn((
156
Transform::default(),
157
Visibility::default(),
158
ring_direction,
159
Ring { radius },
160
))
161
.id();
162
163
let circumference = PI * 2. * radius;
164
let foxes_in_ring = ((circumference / FOX_SPACING) as usize).min(foxes_remaining);
165
let fox_spacing_angle = circumference / (foxes_in_ring as f32 * radius);
166
167
for fox_i in 0..foxes_in_ring {
168
let fox_angle = fox_i as f32 * fox_spacing_angle;
169
let (s, c) = ops::sin_cos(fox_angle);
170
let (x, z) = (radius * c, radius * s);
171
172
commands.entity(ring_parent).with_children(|builder| {
173
builder
174
.spawn((
175
SceneRoot(fox_handle.clone()),
176
Transform::from_xyz(x, 0.0, z)
177
.with_scale(Vec3::splat(0.01))
178
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
179
))
180
.observe(setup_scene_once_loaded);
181
});
182
}
183
184
foxes_remaining -= foxes_in_ring;
185
radius += RING_SPACING;
186
ring_index += 1;
187
}
188
189
// Camera
190
let zoom = 0.8;
191
let translation = Vec3::new(
192
radius * 1.25 * zoom,
193
radius * 0.5 * zoom,
194
radius * 1.5 * zoom,
195
);
196
commands.spawn((
197
Camera3d::default(),
198
Transform::from_translation(translation)
199
.looking_at(0.2 * Vec3::new(translation.x, 0.0, translation.z), Vec3::Y),
200
));
201
202
// Plane
203
commands.spawn((
204
Mesh3d(meshes.add(Plane3d::default().mesh().size(5000.0, 5000.0))),
205
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
206
));
207
208
// Light
209
commands.spawn((
210
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
211
DirectionalLight {
212
shadow_maps_enabled: true,
213
..default()
214
},
215
CascadeShadowConfigBuilder {
216
first_cascade_far_bound: 0.9 * radius,
217
maximum_distance: 2.8 * radius,
218
..default()
219
}
220
.build(),
221
));
222
223
println!("Animation controls:");
224
println!(" - spacebar: play / pause");
225
println!(" - arrow up / down: speed up / slow down animation playback");
226
println!(" - arrow left / right: seek backward / forward");
227
println!(" - return: change animation");
228
}
229
230
// Once the scene is loaded, start the animation
231
fn setup_scene_once_loaded(
232
scene_ready: On<SceneInstanceReady>,
233
animations: Res<Animations>,
234
foxes: Res<Foxes>,
235
mut commands: Commands,
236
children: Query<&Children>,
237
mut players: Query<&mut AnimationPlayer>,
238
) {
239
for child in children.iter_descendants(scene_ready.entity) {
240
if let Ok(mut player) = players.get_mut(child) {
241
let playing_animation = player.play(animations.node_indices[0]).repeat();
242
if !foxes.sync {
243
playing_animation.seek_to(scene_ready.entity.index_u32() as f32 / 10.0);
244
}
245
commands.entity(child).insert((
246
AnimationGraphHandle(animations.graph.clone()),
247
AnimationTransitions::default(),
248
));
249
}
250
}
251
}
252
253
fn update_fox_rings(
254
time: Res<Time>,
255
foxes: Res<Foxes>,
256
mut rings: Query<(&Ring, &RotationDirection, &mut Transform)>,
257
) {
258
if !foxes.moving {
259
return;
260
}
261
262
let dt = time.delta_secs();
263
for (ring, rotation_direction, mut transform) in &mut rings {
264
let angular_velocity = foxes.speed / ring.radius;
265
transform.rotate_y(rotation_direction.sign() * angular_velocity * dt);
266
}
267
}
268
269
fn keyboard_animation_control(
270
keyboard_input: Res<ButtonInput<KeyCode>>,
271
mut animation_player: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
272
animations: Res<Animations>,
273
mut current_animation: Local<usize>,
274
mut foxes: ResMut<Foxes>,
275
) {
276
if keyboard_input.just_pressed(KeyCode::Space) {
277
foxes.moving = !foxes.moving;
278
}
279
280
if keyboard_input.just_pressed(KeyCode::ArrowUp) {
281
foxes.speed *= 1.25;
282
}
283
284
if keyboard_input.just_pressed(KeyCode::ArrowDown) {
285
foxes.speed *= 0.8;
286
}
287
288
if keyboard_input.just_pressed(KeyCode::Enter) {
289
*current_animation = (*current_animation + 1) % animations.node_indices.len();
290
}
291
292
for (mut player, mut transitions) in &mut animation_player {
293
if keyboard_input.just_pressed(KeyCode::Space) {
294
if player.all_paused() {
295
player.resume_all();
296
} else {
297
player.pause_all();
298
}
299
}
300
301
if keyboard_input.just_pressed(KeyCode::ArrowUp) {
302
player.adjust_speeds(1.25);
303
}
304
305
if keyboard_input.just_pressed(KeyCode::ArrowDown) {
306
player.adjust_speeds(0.8);
307
}
308
309
if keyboard_input.just_pressed(KeyCode::ArrowLeft) {
310
player.seek_all_by(-0.1);
311
}
312
313
if keyboard_input.just_pressed(KeyCode::ArrowRight) {
314
player.seek_all_by(0.1);
315
}
316
317
if keyboard_input.just_pressed(KeyCode::Enter) {
318
transitions
319
.play(
320
&mut player,
321
animations.node_indices[*current_animation],
322
Duration::from_millis(250),
323
)
324
.repeat();
325
}
326
}
327
}
328
329