From d190a1caa61794470bbf406684102c2c887066c0 Mon Sep 17 00:00:00 2001 From: Di Saber <37475791+DiSaber@users.noreply.github.com> Date: Mon, 15 Jul 2024 03:46:21 -0500 Subject: [PATCH 1/7] Add `TriMeshFlags` to `ComputedColliderShape::TriMesh` (#559) --- CHANGELOG.md | 8 ++++++++ src/geometry/collider.rs | 19 ++++++++++++++----- src/geometry/collider_impl.rs | 7 +++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3147d7bd..ac5fcce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Added + +- Added a `TriMeshFlags` parameter for `ComputedColliderShape`, +its default value is `TriMeshFlags::MERGE_DUPLICATE_VERTICES`, +which was its hardcoded behaviour. + ## v0.27.0 (07 July 2024) **This is an update from rapier 0.19 to Rapier 0.21 which includes several stability improvements diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs index 72324039..a51e511e 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -1,7 +1,10 @@ use std::fmt; #[cfg(all(feature = "dim3", feature = "async-collider"))] -use {crate::geometry::VHACDParameters, bevy::utils::HashMap}; +use { + crate::geometry::{TriMeshFlags, VHACDParameters}, + bevy::utils::HashMap, +}; use bevy::prelude::*; @@ -40,7 +43,7 @@ pub struct AsyncSceneCollider { impl Default for AsyncSceneCollider { fn default() -> Self { Self { - shape: Some(ComputedColliderShape::TriMesh), + shape: Some(Default::default()), named_shapes: Default::default(), } } @@ -48,17 +51,23 @@ impl Default for AsyncSceneCollider { /// Shape type based on a Bevy mesh asset. #[cfg(all(feature = "dim3", feature = "async-collider"))] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub enum ComputedColliderShape { /// Triangle-mesh. - #[default] - TriMesh, + TriMesh(TriMeshFlags), /// Convex hull. ConvexHull, /// Convex decomposition. ConvexDecomposition(VHACDParameters), } +#[cfg(all(feature = "dim3", feature = "async-collider"))] +impl Default for ComputedColliderShape { + fn default() -> Self { + Self::TriMesh(TriMeshFlags::MERGE_DUPLICATE_VERTICES) + } +} + /// A geometric entity that can be attached to a [`RigidBody`] so it can be affected by contacts /// and intersection queries. /// diff --git a/src/geometry/collider_impl.rs b/src/geometry/collider_impl.rs index d98c980c..88ead96e 100644 --- a/src/geometry/collider_impl.rs +++ b/src/geometry/collider_impl.rs @@ -175,10 +175,9 @@ impl Collider { let (vtx, idx) = extract_mesh_vertices_indices(mesh)?; match collider_shape { - ComputedColliderShape::TriMesh => Some( - SharedShape::trimesh_with_flags(vtx, idx, TriMeshFlags::MERGE_DUPLICATE_VERTICES) - .into(), - ), + ComputedColliderShape::TriMesh(flags) => { + Some(SharedShape::trimesh_with_flags(vtx, idx, *flags).into()) + } ComputedColliderShape::ConvexHull => { SharedShape::convex_hull(&vtx).map(|shape| shape.into()) } From 188dcc277a7083f3c3b4bc928f9371f596b775c9 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 18 Jul 2024 17:32:21 +0200 Subject: [PATCH 2/7] added initial benchmarks, similar to bevy_xpbd (#551) --- Cargo.toml | 2 +- bevy_rapier3d/Cargo.toml | 10 ++++ bevy_rapier3d/benches/common.rs | 12 +++++ bevy_rapier3d/benches/cubes.rs | 52 ++++++++++++++++++++ bevy_rapier3d/benches/many_pyramids3.rs | 28 +++++++++++ bevy_rapier_benches3d/Cargo.toml | 14 ++++++ bevy_rapier_benches3d/README.md | 24 ++++++++++ bevy_rapier_benches3d/src/bin/bench.rs | 14 ++++++ bevy_rapier_benches3d/src/common.rs | 48 +++++++++++++++++++ bevy_rapier_benches3d/src/lib.rs | 42 ++++++++++++++++ bevy_rapier_benches3d/src/pyramids.rs | 64 +++++++++++++++++++++++++ 11 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 bevy_rapier3d/benches/common.rs create mode 100644 bevy_rapier3d/benches/cubes.rs create mode 100644 bevy_rapier3d/benches/many_pyramids3.rs create mode 100644 bevy_rapier_benches3d/Cargo.toml create mode 100644 bevy_rapier_benches3d/README.md create mode 100644 bevy_rapier_benches3d/src/bin/bench.rs create mode 100644 bevy_rapier_benches3d/src/common.rs create mode 100644 bevy_rapier_benches3d/src/lib.rs create mode 100644 bevy_rapier_benches3d/src/pyramids.rs diff --git a/Cargo.toml b/Cargo.toml index 69f7b7de..01b43b09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bevy_rapier2d", "bevy_rapier3d"] +members = ["bevy_rapier2d", "bevy_rapier3d", "bevy_rapier_benches3d"] resolver = "2" [profile.dev] diff --git a/bevy_rapier3d/Cargo.toml b/bevy_rapier3d/Cargo.toml index b0b5a495..58690f76 100644 --- a/bevy_rapier3d/Cargo.toml +++ b/bevy_rapier3d/Cargo.toml @@ -60,7 +60,17 @@ bevy = { version = "0.14", default-features = false, features = [ ] } approx = "0.5.1" glam = { version = "0.27", features = ["approx"] } +divan = "0.1" +bevy_rapier_benches3d = { version = "0.1", path = "../bevy_rapier_benches3d" } [package.metadata.docs.rs] # Enable all the features when building the docs on docs.rs features = ["debug-render-3d", "serde-serialize"] + +[[bench]] +name = "cubes" +harness = false + +[[bench]] +name = "many_pyramids3" +harness = false diff --git a/bevy_rapier3d/benches/common.rs b/bevy_rapier3d/benches/common.rs new file mode 100644 index 00000000..86229fc4 --- /dev/null +++ b/bevy_rapier3d/benches/common.rs @@ -0,0 +1,12 @@ +use bevy::app::App; +use bevy_rapier_benches3d::common::{default_app, wait_app_start}; + +pub fn bench_app_updates(bencher: divan::Bencher, setup: impl Fn(&mut App)) { + let mut app = default_app(); + setup(&mut app); + wait_app_start(&mut app); + + bencher.bench_local(|| { + app.update(); + }); +} diff --git a/bevy_rapier3d/benches/cubes.rs b/bevy_rapier3d/benches/cubes.rs new file mode 100644 index 00000000..6095783f --- /dev/null +++ b/bevy_rapier3d/benches/cubes.rs @@ -0,0 +1,52 @@ +//! Translated from avian benchmark. +//! +//! + +mod common; + +use common::bench_app_updates; + +use bevy::prelude::*; +use bevy_rapier3d::math::*; +use bevy_rapier3d::prelude::*; + +fn setup_cubes(app: &mut App, size: u32) { + app.add_systems(Startup, move |mut commands: Commands| { + commands.spawn(( + RigidBody::Fixed, + Transform::from_translation(-2.0 * Vect::Z), + Collider::cuboid(100.0, 1.0, 100.0), + )); + for x in 0..size { + for z in 0..size { + commands.spawn(( + RigidBody::Dynamic, + Transform::from_translation(Vec3::new(x as f32, 2.0, z as f32)), + Collider::cuboid(1.0, 1.0, 1.0), + )); + } + } + }); +} + +#[divan::bench(sample_count = 60, sample_size = 1)] +fn cubes_3x3(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_cubes(app, 3)) +} +#[divan::bench(sample_count = 60, sample_size = 1)] +fn cubes_5x5(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_cubes(app, 5)) +} +#[divan::bench(sample_count = 60, sample_size = 1)] +fn cubes_10x10(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_cubes(app, 10)) +} +#[divan::bench(sample_count = 60, sample_size = 1)] +fn cubes_20x20(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_cubes(app, 20)) +} + +fn main() { + // Run registered benchmarks. + divan::main(); +} diff --git a/bevy_rapier3d/benches/many_pyramids3.rs b/bevy_rapier3d/benches/many_pyramids3.rs new file mode 100644 index 00000000..e4ca49a4 --- /dev/null +++ b/bevy_rapier3d/benches/many_pyramids3.rs @@ -0,0 +1,28 @@ +//! Translated from rapier benchmark. +//! +//! + +mod common; + +use bevy_rapier_benches3d::pyramids::setup_pyramids; +use common::bench_app_updates; + +#[divan::bench(sample_count = 60, sample_size = 1)] +fn pyramid_1_with_height_2(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_pyramids(app, 1, 2)); +} + +#[divan::bench(sample_count = 60, sample_size = 1)] +fn pyramid_1_with_height_20(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_pyramids(app, 1, 20)); +} + +#[divan::bench(sample_count = 60, sample_size = 1)] +fn pyramid_2_with_height_20(bencher: divan::Bencher) { + bench_app_updates(bencher, |app| setup_pyramids(app, 2, 20)); +} + +fn main() { + // Run registered benchmarks. + divan::main(); +} diff --git a/bevy_rapier_benches3d/Cargo.toml b/bevy_rapier_benches3d/Cargo.toml new file mode 100644 index 00000000..2bb0caba --- /dev/null +++ b/bevy_rapier_benches3d/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "bevy_rapier_benches3d" +version = "0.1.0" +description = "Custom benchmarks for bevy_rapier." +readme = "./README.md" +license = "Apache-2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rapier3d = { features = ["profiler"], version = "0.21" } +bevy_rapier3d = { version = "0.27.0-rc.1", path = "../bevy_rapier3d" } +bevy = { version = "0.14.0-rc.3", default-features = false } diff --git a/bevy_rapier_benches3d/README.md b/bevy_rapier_benches3d/README.md new file mode 100644 index 00000000..7079cd61 --- /dev/null +++ b/bevy_rapier_benches3d/README.md @@ -0,0 +1,24 @@ +# bevy_rapier custom benches + +`bevy_rapier_benches3d` 's objective is to measure timings with detailed information +without spending too much time running multiple times expensive benchmarks. + +It is implemented as a standalone binary, running different scenes setup, gathering information +and outputs them at the end. + +```sh +cargo run --release --bin bench +``` + +## cargo bench + +For short-lived benchmarks based on statistical analysis, +benchmarks can be run through the [divan](https://github.com/nvzqz/divan) bench harness. + +```sh +cargo bench -p bevy_rapier3d +``` + +## Other resources + +- [Bevy profiling](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md) diff --git a/bevy_rapier_benches3d/src/bin/bench.rs b/bevy_rapier_benches3d/src/bin/bench.rs new file mode 100644 index 00000000..e864541b --- /dev/null +++ b/bevy_rapier_benches3d/src/bin/bench.rs @@ -0,0 +1,14 @@ +use bevy_rapier_benches3d::{custom_bencher, pyramids::setup_pyramids}; + +fn pyramid_1_with_height_2() { + custom_bencher(1000, |app| setup_pyramids(app, 1, 2)); +} + +fn pyramid_2_with_height_20() { + custom_bencher(100, |app| setup_pyramids(app, 3, 20)); +} + +fn main() { + pyramid_1_with_height_2(); + pyramid_2_with_height_20(); +} diff --git a/bevy_rapier_benches3d/src/common.rs b/bevy_rapier_benches3d/src/common.rs new file mode 100644 index 00000000..9a2f5f6f --- /dev/null +++ b/bevy_rapier_benches3d/src/common.rs @@ -0,0 +1,48 @@ +use bevy::{ + app::PluginsState, + prelude::*, + render::{ + settings::{RenderCreation, WgpuSettings}, + RenderPlugin, + }, + scene::ScenePlugin, + time::TimeUpdateStrategy, +}; +use bevy_rapier3d::prelude::*; + +pub fn default_app() -> App { + let mut app = App::new(); + + app.add_plugins(( + WindowPlugin::default(), + MinimalPlugins, + AssetPlugin::default(), + ScenePlugin, + RenderPlugin { + render_creation: RenderCreation::Automatic(WgpuSettings { + backends: None, + ..Default::default() + }), + ..Default::default() + }, + ImagePlugin::default(), + HierarchyPlugin, + TransformPlugin, + RapierPhysicsPlugin::<()>::default(), + )); + + // 60 physics + app.insert_resource(TimeUpdateStrategy::ManualDuration( + std::time::Duration::from_secs_f32(1f32 / 60f32), + )); + app +} + +pub fn wait_app_start(app: &mut App) { + while app.plugins_state() != PluginsState::Ready { + bevy::tasks::tick_global_task_pools_on_main_thread(); + } + + app.finish(); + app.cleanup(); +} diff --git a/bevy_rapier_benches3d/src/lib.rs b/bevy_rapier_benches3d/src/lib.rs new file mode 100644 index 00000000..3e3220aa --- /dev/null +++ b/bevy_rapier_benches3d/src/lib.rs @@ -0,0 +1,42 @@ +//! Translated from rapier benchmark. +//! +//! + +pub mod common; +pub mod pyramids; + +use common::default_app; +use common::wait_app_start; + +use bevy::prelude::*; +use bevy_rapier3d::plugin::RapierContext; + +pub fn custom_bencher(steps: usize, setup: impl Fn(&mut App)) { + let mut app = default_app(); + setup(&mut app); + wait_app_start(&mut app); + + let mut timer_total = rapier3d::counters::Timer::new(); + let mut timer_full_update = rapier3d::counters::Timer::new(); + let mut rapier_step_times = vec![]; + let mut total_update_times = vec![]; + timer_total.start(); + for _ in 0..steps { + timer_full_update.start(); + app.update(); + timer_full_update.pause(); + let elapsed_time = timer_full_update.time() as f32; + let rc = app.world().resource::(); + rapier_step_times.push(rc.pipeline.counters.step_time.time() as f32); + total_update_times.push(elapsed_time); + } + timer_total.pause(); + let average_total = total_update_times.iter().sum::() / total_update_times.len() as f32; + println!("average total time: {} ms", average_total); + let average_rapier_step = + rapier_step_times.iter().sum::() / rapier_step_times.len() as f32; + println!("average rapier step time: {} ms", average_rapier_step); + let average_rapier_overhead = average_total - average_rapier_step; + println!("average bevy overhead: {} ms", average_rapier_overhead); + println!("total time: {} ms", timer_total.time()); +} diff --git a/bevy_rapier_benches3d/src/pyramids.rs b/bevy_rapier_benches3d/src/pyramids.rs new file mode 100644 index 00000000..363538a9 --- /dev/null +++ b/bevy_rapier_benches3d/src/pyramids.rs @@ -0,0 +1,64 @@ +use bevy::prelude::*; +use bevy_rapier3d::dynamics::RigidBody; +use bevy_rapier3d::geometry::Collider; +use bevy_rapier3d::math::Vect; + +pub fn create_pyramid(commands: &mut Commands, offset: Vect, stack_height: usize, rad: f32) { + let shift = rad * 2.0; + + for i in 0usize..stack_height { + for j in i..stack_height { + let fj = j as f32; + let fi = i as f32; + let x = (fi * shift / 2.0) + (fj - fi) * shift; + let y = fi * shift; + + // Build the rigid body. + commands.spawn(( + RigidBody::Dynamic, + Transform::from_translation(Vec3::new(x, y, 0.0) + offset), + Collider::cuboid(1.0, 1.0, 1.0), + )); + } + } +} + +pub fn setup_pyramids(app: &mut App, pyramid_count: usize, stack_height: usize) { + app.add_systems(Startup, move |mut commands: Commands| { + let rad = 0.5; + let spacing = 4.0; + + /* + * Ground + */ + let ground_size = 50.0; + let ground_height = 0.1; + + commands.spawn(( + RigidBody::Fixed, + Transform::from_translation(Vect::new(0.0, -ground_height, 0.0)), + Collider::cuboid( + ground_size, + ground_height, + pyramid_count as f32 * spacing / 2.0 + ground_size, + ), + )); + + /* + * Create the pyramids + */ + for pyramid_index in 0..pyramid_count { + let bottomy = rad; + create_pyramid( + &mut commands, + Vect::new( + 0.0, + bottomy, + (pyramid_index as f32 - pyramid_count as f32 / 2.0) * spacing, + ), + stack_height, + rad, + ); + } + }); +} From 3c986103ca01e79cc06813e0a966b553a2071e13 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 18 Jul 2024 17:50:58 +0200 Subject: [PATCH 3/7] testbed: allow to select example (#565) --- bevy_rapier2d/Cargo.toml | 2 + bevy_rapier2d/examples/testbed2.rs | 108 +++++++++++++++++++++-------- bevy_rapier3d/Cargo.toml | 2 + bevy_rapier3d/examples/testbed3.rs | 100 ++++++++++++++++++++------ 4 files changed, 162 insertions(+), 50 deletions(-) diff --git a/bevy_rapier2d/Cargo.toml b/bevy_rapier2d/Cargo.toml index ddce9481..d6749f7b 100644 --- a/bevy_rapier2d/Cargo.toml +++ b/bevy_rapier2d/Cargo.toml @@ -59,6 +59,8 @@ bevy = { version = "0.14", default-features = false, features = [ oorandom = "11" approx = "0.5.1" glam = { version = "0.27", features = ["approx"] } +bevy-inspector-egui = "0.25.1" +bevy_egui = "0.28.0" [package.metadata.docs.rs] # Enable all the features when building the docs on docs.rs diff --git a/bevy_rapier2d/examples/testbed2.rs b/bevy_rapier2d/examples/testbed2.rs index e7a52a97..89c1c003 100644 --- a/bevy_rapier2d/examples/testbed2.rs +++ b/bevy_rapier2d/examples/testbed2.rs @@ -12,9 +12,11 @@ mod player_movement2; mod rope_joint2; use bevy::prelude::*; +use bevy_egui::{egui, EguiContexts, EguiPlugin}; +use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_rapier2d::prelude::*; -#[derive(Debug, Clone, Eq, PartialEq, Default, Hash, States)] +#[derive(Debug, Reflect, Clone, Copy, Eq, PartialEq, Default, Hash, States)] pub enum Examples { #[default] None, @@ -25,25 +27,61 @@ pub enum Examples { Events2, Joints2, JointsDespawn2, - LockedRotation2, + LockedRotations2, MultipleColliders2, PlayerMovement2, } -#[derive(Resource, Default)] +#[derive(Resource, Default, Reflect)] struct ExamplesRes { entities_before: Vec, } +#[derive(Resource, Debug, Default, Reflect)] +struct ExampleSelected(pub usize); + +#[derive(Debug, Reflect)] +struct ExampleDefinition { + pub state: Examples, + pub name: &'static str, +} + +impl From<(Examples, &'static str)> for ExampleDefinition { + fn from((state, name): (Examples, &'static str)) -> Self { + Self { state, name } + } +} + +#[derive(Resource, Debug, Reflect)] +struct ExampleSet(pub Vec); + fn main() { let mut app = App::new(); - app.init_state::() - .init_resource::() + app.init_resource::() .add_plugins(( DefaultPlugins, + EguiPlugin, RapierPhysicsPlugin::::pixels_per_meter(10.0), RapierDebugRenderPlugin::default(), + WorldInspectorPlugin::new(), )) + .register_type::() + .register_type::() + .register_type::() + .init_state::() + .insert_resource(ExampleSet(vec![ + (Examples::Boxes2, "Boxes3").into(), + (Examples::RopeJoint2, "RopeJoint2").into(), + (Examples::DebugDespawn2, "DebugDespawn2").into(), + (Examples::Despawn2, "Despawn3").into(), + (Examples::Events2, "Events3").into(), + (Examples::Joints2, "Joints3").into(), + (Examples::JointsDespawn2, "JointsDespawn3").into(), + (Examples::LockedRotations2, "LockedRotations3").into(), + (Examples::MultipleColliders2, "MultipleColliders3").into(), + (Examples::PlayerMovement2, "PlayerMovement2").into(), + ])) + .init_resource::() // //boxes2 .add_systems( @@ -116,13 +154,13 @@ fn main() { // //locked rotations .add_systems( - OnEnter(Examples::LockedRotation2), + OnEnter(Examples::LockedRotations2), ( locked_rotations2::setup_graphics, locked_rotations2::setup_physics, ), ) - .add_systems(OnExit(Examples::LockedRotation2), cleanup) + .add_systems(OnExit(Examples::LockedRotations2), cleanup) // //multiple colliders .add_systems( @@ -162,7 +200,14 @@ fn main() { }, ) .add_systems(OnExit(Examples::None), init) - .add_systems(Update, check_toggle); + .add_systems( + Update, + ( + ui_example_system, + change_example.run_if(resource_changed::), + ) + .chain(), + ); app.run(); } @@ -187,25 +232,34 @@ fn cleanup(world: &mut World) { } } -fn check_toggle( - state: Res>, +fn change_example( + example_selected: Res, + examples_available: Res, mut next_state: ResMut>, - mouse_input: Res>, ) { - if mouse_input.just_pressed(MouseButton::Left) { - let next = match *state.get() { - Examples::None => Examples::Boxes2, - Examples::Boxes2 => Examples::RopeJoint2, - Examples::RopeJoint2 => Examples::DebugDespawn2, - Examples::DebugDespawn2 => Examples::Despawn2, - Examples::Despawn2 => Examples::Events2, - Examples::Events2 => Examples::Joints2, - Examples::Joints2 => Examples::JointsDespawn2, - Examples::JointsDespawn2 => Examples::LockedRotation2, - Examples::LockedRotation2 => Examples::MultipleColliders2, - Examples::MultipleColliders2 => Examples::PlayerMovement2, - Examples::PlayerMovement2 => Examples::Boxes2, - }; - next_state.set(next); - } + next_state.set(examples_available.0[example_selected.0].state); +} + +fn ui_example_system( + mut contexts: EguiContexts, + mut current_example: ResMut, + examples_available: Res, +) { + egui::Window::new("Testbed").show(contexts.ctx_mut(), |ui| { + let mut changed = false; + egui::ComboBox::from_label("example") + .width(150.0) + .selected_text(examples_available.0[current_example.0].name) + .show_ui(ui, |ui| { + for (id, value) in examples_available.0.iter().enumerate() { + changed = ui + .selectable_value(&mut current_example.0, id, value.name) + .changed() + || changed; + } + }); + if ui.button("Next").clicked() { + current_example.0 = (current_example.0 + 1) % examples_available.0.len(); + } + }); } diff --git a/bevy_rapier3d/Cargo.toml b/bevy_rapier3d/Cargo.toml index 58690f76..11b0a43a 100644 --- a/bevy_rapier3d/Cargo.toml +++ b/bevy_rapier3d/Cargo.toml @@ -60,6 +60,8 @@ bevy = { version = "0.14", default-features = false, features = [ ] } approx = "0.5.1" glam = { version = "0.27", features = ["approx"] } +bevy-inspector-egui = "0.25.1" +bevy_egui = "0.28.0" divan = "0.1" bevy_rapier_benches3d = { version = "0.1", path = "../bevy_rapier_benches3d" } diff --git a/bevy_rapier3d/examples/testbed3.rs b/bevy_rapier3d/examples/testbed3.rs index 1d3e3db1..a8f18a48 100644 --- a/bevy_rapier3d/examples/testbed3.rs +++ b/bevy_rapier3d/examples/testbed3.rs @@ -11,9 +11,11 @@ mod ray_casting3; mod static_trimesh3; use bevy::prelude::*; +use bevy_egui::{egui, EguiContexts, EguiPlugin}; +use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_rapier3d::prelude::*; -#[derive(Debug, Clone, Eq, PartialEq, Default, Hash, States)] +#[derive(Debug, Reflect, Clone, Copy, Eq, PartialEq, Default, Hash, States)] pub enum Examples { #[default] None, @@ -28,20 +30,55 @@ pub enum Examples { StaticTrimesh3, } -#[derive(Resource, Default)] +#[derive(Resource, Default, Reflect)] struct ExamplesRes { entities_before: Vec, } +#[derive(Resource, Debug, Default, Reflect)] +struct ExampleSelected(pub usize); + +#[derive(Debug, Reflect)] +struct ExampleDefinition { + pub state: Examples, + pub name: &'static str, +} + +impl From<(Examples, &'static str)> for ExampleDefinition { + fn from((state, name): (Examples, &'static str)) -> Self { + Self { state, name } + } +} + +#[derive(Resource, Debug, Reflect)] +struct ExampleSet(pub Vec); + fn main() { let mut app = App::new(); - app.init_state::() - .init_resource::() + app.init_resource::() .add_plugins(( DefaultPlugins, + EguiPlugin, RapierPhysicsPlugin::::default(), RapierDebugRenderPlugin::default(), + WorldInspectorPlugin::new(), )) + .register_type::() + .register_type::() + .register_type::() + .init_state::() + .insert_resource(ExampleSet(vec![ + (Examples::Boxes3, "Boxes3").into(), + (Examples::Despawn3, "Despawn3").into(), + (Examples::Events3, "Events3").into(), + (Examples::Joints3, "Joints3").into(), + (Examples::JointsDespawn3, "JointsDespawn3").into(), + (Examples::LockedRotations3, "LockedRotations3").into(), + (Examples::MultipleColliders3, "MultipleColliders3").into(), + (Examples::Raycasting3, "Raycasting3").into(), + (Examples::StaticTrimesh3, "StaticTrimesh3").into(), + ])) + .init_resource::() // //boxes2 .add_systems( @@ -149,7 +186,14 @@ fn main() { }, ) .add_systems(OnExit(Examples::None), init) - .add_systems(Update, check_toggle); + .add_systems( + Update, + ( + ui_example_system, + change_example.run_if(resource_changed::), + ) + .chain(), + ); app.run(); } @@ -174,24 +218,34 @@ fn cleanup(world: &mut World) { } } -fn check_toggle( - state: Res>, +fn change_example( + example_selected: Res, + examples_available: Res, mut next_state: ResMut>, - mouse_input: Res>, ) { - if mouse_input.just_pressed(MouseButton::Left) { - let next = match *state.get() { - Examples::None => Examples::Boxes3, - Examples::Boxes3 => Examples::Despawn3, - Examples::Despawn3 => Examples::Events3, - Examples::Events3 => Examples::Joints3, - Examples::Joints3 => Examples::JointsDespawn3, - Examples::JointsDespawn3 => Examples::LockedRotations3, - Examples::LockedRotations3 => Examples::MultipleColliders3, - Examples::MultipleColliders3 => Examples::Raycasting3, - Examples::Raycasting3 => Examples::StaticTrimesh3, - Examples::StaticTrimesh3 => Examples::Boxes3, - }; - next_state.set(next); - } + next_state.set(examples_available.0[example_selected.0].state); +} + +fn ui_example_system( + mut contexts: EguiContexts, + mut current_example: ResMut, + examples_available: Res, +) { + egui::Window::new("Testbed").show(contexts.ctx_mut(), |ui| { + let mut changed = false; + egui::ComboBox::from_label("example") + .width(150.0) + .selected_text(examples_available.0[current_example.0].name) + .show_ui(ui, |ui| { + for (id, value) in examples_available.0.iter().enumerate() { + changed = ui + .selectable_value(&mut current_example.0, id, value.name) + .changed() + || changed; + } + }); + if ui.button("Next").clicked() { + current_example.0 = (current_example.0 + 1) % examples_available.0.len(); + } + }); } From c6c9bbcd256f4f2dca098be4681713a803b00af8 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 22 Jul 2024 11:32:46 +0200 Subject: [PATCH 4/7] A few doc-link fixes (#562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- src/geometry/collider.rs | 16 ++++++++-------- src/plugin/configuration.rs | 4 ++-- src/render/mod.rs | 5 ++++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs index a51e511e..2dbd7c8b 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -171,8 +171,8 @@ impl Default for Friction { } impl Friction { - /// Creates a `Friction` component from the given friction coefficient, and using the default - /// `CoefficientCombineRule::Average` coefficient combine rule. + /// Creates a [`Friction`] component from the given friction coefficient, and using the default + /// [`CoefficientCombineRule::Average`] coefficient combine rule. pub const fn new(coefficient: f32) -> Self { Self { coefficient, @@ -180,8 +180,8 @@ impl Friction { } } - /// Creates a `Friction` component from the given friction coefficient, and using the default - /// `CoefficientCombineRule::Average` coefficient combine rule. + /// Creates a [`Friction`] component from the given friction coefficient, and using the default + /// [`CoefficientCombineRule::Average`] coefficient combine rule. pub const fn coefficient(coefficient: f32) -> Self { Self { coefficient, @@ -204,8 +204,8 @@ pub struct Restitution { } impl Restitution { - /// Creates a `Restitution` component from the given restitution coefficient, and using the default - /// `CoefficientCombineRule::Average` coefficient combine rule. + /// Creates a [`Restitution`] component from the given restitution coefficient, and using the default + /// [`CoefficientCombineRule::Average`] coefficient combine rule. pub const fn new(coefficient: f32) -> Self { Self { coefficient, @@ -213,8 +213,8 @@ impl Restitution { } } - /// Creates a `Restitution` component from the given restitution coefficient, and using the default - /// `CoefficientCombineRule::Average` coefficient combine rule. + /// Creates a [`Restitution`] component from the given restitution coefficient, and using the default + /// [`CoefficientCombineRule::Average`] coefficient combine rule. pub const fn coefficient(coefficient: f32) -> Self { Self { coefficient, diff --git a/src/plugin/configuration.rs b/src/plugin/configuration.rs index b2d2c42f..fe26fe27 100644 --- a/src/plugin/configuration.rs +++ b/src/plugin/configuration.rs @@ -4,7 +4,7 @@ use crate::math::{Real, Vect}; use crate::plugin::RapierContext; #[cfg(doc)] -use rapier::dynamics::IntegrationParameters; +use {crate::prelude::TransformInterpolation, rapier::dynamics::IntegrationParameters}; /// Difference between simulation and rendering time #[derive(Resource, Default)] @@ -39,7 +39,7 @@ pub enum TimestepMode { }, /// Use a fixed timestep equal to `IntegrationParameters::dt`, but don't step if the /// physics simulation advanced by a time greater than the real-world elapsed time multiplied by `time_scale`. - /// Rigid-bodies with a component `InterpolatedTransform` attached will use interpolation to + /// Rigid-bodies with a component [`TransformInterpolation`] attached will use interpolation to /// estimate the rigid-bodies position in-between steps. Interpolated { /// The physics simulation will be advanced by this total amount at each Bevy tick, unless diff --git a/src/render/mod.rs b/src/render/mod.rs index b99e0c70..262f572c 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -6,9 +6,12 @@ use rapier::pipeline::{DebugRenderBackend, DebugRenderObject, DebugRenderPipelin pub use rapier::pipeline::{DebugRenderMode, DebugRenderStyle}; use std::fmt::Debug; +#[cfg(doc)] +use crate::prelude::Collider; + /// The color of a collider when using the debug-renderer. /// -/// Insert this component alongside the collider component to +/// Insert this component alongside the [`Collider`] component to /// force to a specific value the color used to render the /// collider. #[derive(Copy, Clone, Component, PartialEq, Debug)] From 59477d9cc29fc5fe08e753fb4e00aa8da54273b8 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 23 Jul 2024 10:10:16 +0200 Subject: [PATCH 5/7] Fix a crash when removing colliders in `TimestepMode::Interpolated` (#563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Crozet --- CHANGELOG.md | 5 +++ src/pipeline/events.rs | 80 ++++++++++++++++++++++++++++++++++++++- src/plugin/context.rs | 8 ++++ src/plugin/systems/mod.rs | 1 - 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5fcce5..368514c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fix + +- Fix a crash when using `TimestepMode::Interpolated` and removing colliders +during a frame which would not run a simulation step. + ### Added - Added a `TriMeshFlags` parameter for `ComputedColliderShape`, diff --git a/src/pipeline/events.rs b/src/pipeline/events.rs index c039b1fc..850d4a8d 100644 --- a/src/pipeline/events.rs +++ b/src/pipeline/events.rs @@ -125,7 +125,13 @@ impl<'a> EventHandler for EventQueue<'a> { #[cfg(test)] mod test { - use bevy::time::{TimePlugin, TimeUpdateStrategy}; + use bevy::{ + app::{App, Startup, Update}, + prelude::{Commands, Component, Entity, Query, With}, + time::{TimePlugin, TimeUpdateStrategy}, + transform::{bundles::TransformBundle, components::Transform, TransformPlugin}, + MinimalPlugins, + }; use systems::tests::HeadlessRenderPlugin; use crate::{plugin::*, prelude::*}; @@ -231,4 +237,76 @@ mod test { )); } } + + #[test] + pub fn spam_remove_rapier_entity_interpolated() { + let mut app = App::new(); + app.add_plugins(( + HeadlessRenderPlugin, + MinimalPlugins, + TransformPlugin, + RapierPhysicsPlugin::::default(), + )) + .insert_resource(RapierConfiguration { + timestep_mode: TimestepMode::Interpolated { + dt: 1.0 / 30.0, + time_scale: 1.0, + substeps: 2, + }, + ..RapierConfiguration::new(1f32) + }) + .add_systems(Startup, setup_physics) + .add_systems(Update, remove_collider); + // Simulates 60 updates per seconds + app.insert_resource(TimeUpdateStrategy::ManualDuration( + std::time::Duration::from_secs_f32(1f32 / 60f32), + )); + + for i in 0..100 { + dbg!(i); + app.update(); + } + return; + + #[derive(Component)] + pub struct ToRemove; + + #[cfg(feature = "dim3")] + fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider { + Collider::cuboid(hx, hy, hz) + } + #[cfg(feature = "dim2")] + fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider { + Collider::cuboid(hx, hy) + } + pub fn setup_physics(mut commands: Commands) { + for _i in 0..100 { + commands.spawn(( + TransformBundle::from(Transform::from_xyz(0.0, 0.0, 0.0)), + RigidBody::Dynamic, + cuboid(0.5, 0.5, 0.5), + ActiveEvents::all(), + ToRemove, + )); + } + /* + * Ground + */ + let ground_size = 5.1; + let ground_height = 0.1; + let starting_y = -0.5 - ground_height; + + commands.spawn(( + TransformBundle::from(Transform::from_xyz(0.0, starting_y, 0.0)), + cuboid(ground_size, ground_height, ground_size), + )); + } + + fn remove_collider(mut commands: Commands, query: Query>) { + let Some(entity) = query.iter().next() else { + return; + }; + commands.entity(entity).despawn(); + } + } } diff --git a/src/plugin/context.rs b/src/plugin/context.rs index eb7fe305..ce8bda56 100644 --- a/src/plugin/context.rs +++ b/src/plugin/context.rs @@ -225,6 +225,7 @@ impl RapierContext { .or_else(|| event_queue.as_ref().map(|q| q as &dyn EventHandler)) .unwrap_or(&() as &dyn EventHandler); + let mut executed_steps = 0; match timestep_mode { TimestepMode::Interpolated { dt, @@ -271,6 +272,7 @@ impl RapierContext { hooks, events, ); + executed_steps += 1; } sim_to_render_time.diff -= dt; @@ -302,6 +304,7 @@ impl RapierContext { hooks, events, ); + executed_steps += 1; } } TimestepMode::Fixed { dt, substeps } => { @@ -326,9 +329,14 @@ impl RapierContext { hooks, events, ); + executed_steps += 1; } } } + + if executed_steps > 0 { + self.deleted_colliders.clear(); + } } /// This method makes sure tha the rigid-body positions have been propagated to diff --git a/src/plugin/systems/mod.rs b/src/plugin/systems/mod.rs index c24571bb..658ddf0a 100644 --- a/src/plugin/systems/mod.rs +++ b/src/plugin/systems/mod.rs @@ -50,7 +50,6 @@ pub fn step_simulation( &mut sim_to_render_time, Some(interpolation_query), ); - context.deleted_colliders.clear(); } else { context.propagate_modified_body_positions_to_colliders(); } From deb0a06183572d7a6963788196fc640134de75b0 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 23 Jul 2024 11:12:04 +0200 Subject: [PATCH 6/7] rename an init function --- bevy_rapier3d/examples/multi_world3.rs | 2 +- src/plugin/plugin.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bevy_rapier3d/examples/multi_world3.rs b/bevy_rapier3d/examples/multi_world3.rs index 65e448cb..dc5fa807 100644 --- a/bevy_rapier3d/examples/multi_world3.rs +++ b/bevy_rapier3d/examples/multi_world3.rs @@ -13,7 +13,7 @@ fn main() { .add_plugins(( DefaultPlugins, RapierPhysicsPlugin::::default() - .with_default_world(RapierContextInitialization::NoAutomaticRapierContext), + .with_custom_initialization(RapierContextInitialization::NoAutomaticRapierContext), RapierDebugRenderPlugin::default(), )) .add_systems( diff --git a/src/plugin/plugin.rs b/src/plugin/plugin.rs index 4ca2cfd4..628bcd74 100644 --- a/src/plugin/plugin.rs +++ b/src/plugin/plugin.rs @@ -50,7 +50,7 @@ where /// Specifies a default world initialization strategy. /// /// The default is to initialize a [`RapierContext`] with a length unit of 1. - pub fn with_default_world( + pub fn with_custom_initialization( mut self, default_world_initialization: RapierContextInitialization, ) -> Self { @@ -215,7 +215,7 @@ where let default_world_init = app.world().get_resource::(); if let Some(world_init) = default_world_init { warn!("RapierPhysicsPlugin added but a `RapierContextInitialization` resource was already existing.\ - This might overwrite previous configuration made via `RapierPhysicsPlugin::with_default_world`\ + This might overwrite previous configuration made via `RapierPhysicsPlugin::with_custom_initialization`\ or `RapierPhysicsPlugin::with_length_unit`. The following resource will be used: {:?}", world_init); } else { @@ -272,7 +272,7 @@ where /// Specifies a default configuration for the default [`RapierContext`] /// -/// Designed to be passed as parameter to [`RapierPhysicsPlugin::with_default_world`]. +/// Designed to be passed as parameter to [`RapierPhysicsPlugin::with_custom_initialization`]. #[derive(Resource, Debug, Reflect, Clone)] pub enum RapierContextInitialization { /// [`RapierPhysicsPlugin`] will not spawn any entity containing [`RapierContext`] automatically. From cf165bdaa8190086bd1360516c274db1217dd389 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 23 Jul 2024 11:26:53 +0200 Subject: [PATCH 7/7] apply pr suggestion --- src/plugin/systems/multiple_rapier_contexts.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugin/systems/multiple_rapier_contexts.rs b/src/plugin/systems/multiple_rapier_contexts.rs index fa9944fe..062cf688 100644 --- a/src/plugin/systems/multiple_rapier_contexts.rs +++ b/src/plugin/systems/multiple_rapier_contexts.rs @@ -27,8 +27,11 @@ pub fn on_add_entity_with_parent( let mut parent = Some(parent.get()); while let Some(parent_entity) = parent { if let Ok(pw) = q_physics_world.get(parent_entity) { - commands.entity(ent).insert(*pw); - remove_old_physics(ent, &mut commands); + // Change rapier context link only if the existing link isn't the correct one. + if q_physics_world.get(ent).map(|x| x != pw).unwrap_or(true) { + remove_old_physics(ent, &mut commands); + commands.entity(ent).insert(*pw); + } break; } parent = q_parent.get(parent_entity).ok().map(|x| x.get());