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