Skip to content

Commit

Permalink
Add a fun skinned mesh stress test based on the animated_fox example (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
superdump committed May 8, 2022
1 parent d867b61 commit 76829f9
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.toml
Expand Up @@ -542,6 +542,10 @@ path = "examples/stress_tests/bevymark.rs"
name = "many_cubes"
path = "examples/stress_tests/many_cubes.rs"

[[example]]
name = "many_foxes"
path = "examples/stress_tests/many_foxes.rs"

[[example]]
name = "many_lights"
path = "examples/stress_tests/many_lights.rs"
Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Expand Up @@ -263,7 +263,8 @@ cargo run --release --example <example name>
Example | File | Description
--- | --- | ---
`bevymark` | [`stress_tests/bevymark.rs`](./stress_tests/bevymark.rs) | A heavy sprite rendering workload to benchmark your system with Bevy
`many_cubes` | [`stress_tests/many_cubes.rs`](./stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead
`many_cubes` | [`stress_tests/many_cubes.rs`](./stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling.
`many_foxes` | [`stress_tests/many_foxes.rs`](./stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000.
`many_lights` | [`stress_tests/many_lights.rs`](./stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights.
`many_sprites` | [`stress_tests/many_sprites.rs`](./stress_tests/many_sprites.rs) | Displays many sprites in a grid arragement! Used for performance testing.
`transform_hierarchy.rs` | [`stress_tests/transform_hierarchy.rs`](./stress_tests/transform_hierarchy.rs) | Various test cases for hierarchy and transform propagation performance
Expand Down
271 changes: 271 additions & 0 deletions examples/stress_tests/many_foxes.rs
@@ -0,0 +1,271 @@
//! Loads animations from a skinned glTF, spawns many of them, and plays the
//! animation to stress test skinned meshes.

use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
};

struct Foxes {
count: usize,
speed: f32,
moving: bool,
}

fn main() {
App::new()
.insert_resource(WindowDescriptor {
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".to_string(),
..default()
})
.add_plugins(DefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin)
.add_plugin(LogDiagnosticsPlugin::default())
.insert_resource(Foxes {
count: std::env::args()
.nth(1)
.map_or(1000, |s| s.parse::<usize>().unwrap()),
speed: 2.0,
moving: true,
})
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.0,
})
.add_startup_system(setup)
.add_system(setup_scene_once_loaded)
.add_system(keyboard_animation_control)
.add_system(update_fox_rings.after(keyboard_animation_control))
.run();
}

struct Animations(Vec<Handle<AnimationClip>>);

const RING_SPACING: f32 = 2.0;
const FOX_SPACING: f32 = 2.0;

#[derive(Component, Clone, Copy)]
enum RotationDirection {
CounterClockwise,
Clockwise,
}

impl RotationDirection {
fn sign(&self) -> f32 {
match self {
RotationDirection::CounterClockwise => 1.0,
RotationDirection::Clockwise => -1.0,
}
}
}

#[derive(Component)]
struct Ring {
radius: f32,
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut scene_spawner: ResMut<SceneSpawner>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
foxes: Res<Foxes>,
) {
// Insert a resource with the current scene information
commands.insert_resource(Animations(vec![
asset_server.load("models/animated/Fox.glb#Animation2"),
asset_server.load("models/animated/Fox.glb#Animation1"),
asset_server.load("models/animated/Fox.glb#Animation0"),
]));

// Foxes
// Concentric rings of foxes, running in opposite directions. The rings are spaced at 2m radius intervals.
// The foxes in each ring are spaced at least 2m apart around its circumference.'

// NOTE: This fox model faces +z
let fox_handle = asset_server.load("models/animated/Fox.glb#Scene0");

let ring_directions = [
(
Quat::from_rotation_y(std::f32::consts::PI),
RotationDirection::CounterClockwise,
),
(Quat::IDENTITY, RotationDirection::Clockwise),
];

let mut ring_index = 0;
let mut radius = RING_SPACING;
let mut foxes_remaining = foxes.count;

info!("Spawning {} foxes...", foxes.count);

while foxes_remaining > 0 {
let (base_rotation, ring_direction) = ring_directions[ring_index % 2];
let ring_parent = commands
.spawn_bundle((
Transform::default(),
GlobalTransform::default(),
ring_direction,
Ring { radius },
))
.id();

let circumference = std::f32::consts::TAU * radius;
let foxes_in_ring = ((circumference / FOX_SPACING) as usize).min(foxes_remaining);
let fox_spacing_angle = circumference / (foxes_in_ring as f32 * radius);

for fox_i in 0..foxes_in_ring {
let fox_angle = fox_i as f32 * fox_spacing_angle;
let (s, c) = fox_angle.sin_cos();
let (x, z) = (radius * c, radius * s);

commands.entity(ring_parent).with_children(|builder| {
let fox_parent = builder
.spawn_bundle((
Transform::from_xyz(x as f32, 0.0, z as f32)
.with_scale(Vec3::splat(0.01))
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
GlobalTransform::default(),
))
.id();
scene_spawner.spawn_as_child(fox_handle.clone(), fox_parent);
});
}

foxes_remaining -= foxes_in_ring;
radius += RING_SPACING;
ring_index += 1;
}

// Camera
let zoom = 0.8;
let translation = Vec3::new(
radius * 1.25 * zoom,
radius * 0.5 * zoom,
radius * 1.5 * zoom,
);
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_translation(translation)
.looking_at(0.2 * Vec3::new(translation.x, 0.0, translation.z), Vec3::Y),
..Default::default()
});

// Plane
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 500000.0 })),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});

// Light
commands.spawn_bundle(DirectionalLightBundle {
transform: Transform::from_rotation(Quat::from_euler(
EulerRot::ZYX,
0.0,
1.0,
-std::f32::consts::FRAC_PI_4,
)),
directional_light: DirectionalLight {
shadows_enabled: true,
..default()
},
..default()
});

println!("Animation controls:");
println!(" - spacebar: play / pause");
println!(" - arrow up / down: speed up / slow down animation playback");
println!(" - arrow left / right: seek backward / forward");
println!(" - return: change animation");
}

// Once the scene is loaded, start the animation
fn setup_scene_once_loaded(
animations: Res<Animations>,
foxes: Res<Foxes>,
mut player: Query<&mut AnimationPlayer>,
mut done: Local<bool>,
) {
if !*done && player.iter().len() == foxes.count {
for mut player in player.iter_mut() {
player.play(animations.0[0].clone_weak()).repeat();
}
*done = true;
}
}

fn update_fox_rings(
time: Res<Time>,
foxes: Res<Foxes>,
mut rings: Query<(&Ring, &RotationDirection, &mut Transform)>,
) {
if !foxes.moving {
return;
}

let dt = time.delta_seconds();
for (ring, rotation_direction, mut transform) in rings.iter_mut() {
let angular_velocity = foxes.speed / ring.radius;
transform.rotate(Quat::from_rotation_y(
rotation_direction.sign() * angular_velocity * dt,
));
}
}

fn keyboard_animation_control(
keyboard_input: Res<Input<KeyCode>>,
mut animation_player: Query<&mut AnimationPlayer>,
animations: Res<Animations>,
mut current_animation: Local<usize>,
mut foxes: ResMut<Foxes>,
) {
if keyboard_input.just_pressed(KeyCode::Space) {
foxes.moving = !foxes.moving;
}

if keyboard_input.just_pressed(KeyCode::Up) {
foxes.speed *= 1.25;
}

if keyboard_input.just_pressed(KeyCode::Down) {
foxes.speed *= 0.8;
}

for mut player in animation_player.iter_mut() {
if keyboard_input.just_pressed(KeyCode::Space) {
if player.is_paused() {
player.resume();
} else {
player.pause();
}
}

if keyboard_input.just_pressed(KeyCode::Up) {
let speed = player.speed();
player.set_speed(speed * 1.25);
}

if keyboard_input.just_pressed(KeyCode::Down) {
let speed = player.speed();
player.set_speed(speed * 0.8);
}

if keyboard_input.just_pressed(KeyCode::Left) {
let elapsed = player.elapsed();
player.set_elapsed(elapsed - 0.1);
}

if keyboard_input.just_pressed(KeyCode::Right) {
let elapsed = player.elapsed();
player.set_elapsed(elapsed + 0.1);
}

if keyboard_input.just_pressed(KeyCode::Return) {
*current_animation = (*current_animation + 1) % animations.0.len();
player
.play(animations.0[*current_animation].clone_weak())
.repeat();
}
}
}

0 comments on commit 76829f9

Please sign in to comment.