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