From 317322b31b1ecf3732676dbb90fcfd5f90c8d0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Fri, 8 Aug 2025 18:15:34 +0200 Subject: [PATCH] feat: reduce the amount of duplicate work the broad-phase is doing for user changes and CCD + release v0.28.0 (#872) * feat: reduce the amount of duplicate work the broad-phase is doing for user changes and CCD * Release v0.28.0 * chore: fix warnings * chore: clippy fixes * chore: more clippy fixes --- CHANGELOG.md | 20 +++++ benchmarks2d/Cargo.toml | 2 +- benchmarks2d/convex_polygons2.rs | 4 +- benchmarks3d/Cargo.toml | 2 +- benchmarks3d/convex_polyhedron3.rs | 4 +- benchmarks3d/ray_cast3.rs | 5 +- crates/rapier2d-f64/Cargo.toml | 6 +- crates/rapier2d/Cargo.toml | 6 +- crates/rapier3d-f64/Cargo.toml | 6 +- crates/rapier3d-meshloader/Cargo.toml | 4 +- crates/rapier3d-urdf/Cargo.toml | 6 +- crates/rapier3d/Cargo.toml | 6 +- crates/rapier_testbed2d-f64/Cargo.toml | 10 +-- crates/rapier_testbed2d/Cargo.toml | 10 +-- crates/rapier_testbed3d-f64/Cargo.toml | 10 +-- crates/rapier_testbed3d/Cargo.toml | 10 +-- examples2d/Cargo.toml | 2 +- examples2d/convex_polygons2.rs | 4 +- examples2d/debug_intersection2.rs | 5 +- examples2d/utils/character.rs | 5 +- examples3d-f64/Cargo.toml | 2 +- examples3d-f64/debug_serialized3.rs | 2 +- examples3d/Cargo.toml | 2 +- examples3d/all_examples3.rs | 2 + examples3d/convex_polyhedron3.rs | 4 +- examples3d/debug_balls3.rs | 59 ++++++++++++++ examples3d/debug_deserialize3.rs | 2 +- examples3d/utils/character.rs | 5 +- examples3d/voxels3.rs | 5 +- publish-all.sh | 8 +- src/dynamics/ccd/ccd_solver.rs | 96 +++++++++------------- src/geometry/broad_phase.rs | 48 ----------- src/geometry/broad_phase_bvh.rs | 105 ++++++++++++------------- src/geometry/collider.rs | 38 ++++++++- src/geometry/collider_set.rs | 4 + src/geometry/mod.rs | 2 - src/geometry/narrow_phase.rs | 10 --- src/pipeline/collision_pipeline.rs | 9 +-- src/pipeline/physics_pipeline.rs | 74 +++++++++++------ src_testbed/graphics.rs | 8 +- src_testbed/harness/mod.rs | 20 ++--- src_testbed/physics/mod.rs | 6 +- src_testbed/testbed.rs | 41 ++++------ 43 files changed, 351 insertions(+), 328 deletions(-) create mode 100644 examples3d/debug_balls3.rs delete mode 100644 src/geometry/broad_phase.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 01eade6..b71410a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## v0.28.0 (08 August 2025) + +### Modified + +- Update to nalgebra 0.34 and parry 0.23. +- Only run the broad-phase once at the beginning of the physics step. +- Don’t rebuild a BVH from scratch for CCD. Instead, reuse the broad-phase bvh with localized changes. +- The public methods of `IslandSolver` has changed slightly to take the broad-phase as input. +- Removed the `BroadPhase` trait and use the BVH broad-phase directly instead of a trait-object. + +### Added + +- Add `Collider::compute_broad_phase_aabb` to compute the AABB to be used by the broad-phase, taking + into account its contact skin, prediction, and soft-ccd. + +### Fix + +- Fix issue where some solver contacts would disappear from the contact graph before the user + had a chance to read them. + ## v0.27.0 (24 July 2025) ### Modified diff --git a/benchmarks2d/Cargo.toml b/benchmarks2d/Cargo.toml index 23095f5..0dcb383 100644 --- a/benchmarks2d/Cargo.toml +++ b/benchmarks2d/Cargo.toml @@ -12,7 +12,7 @@ other-backends = ["rapier_testbed2d/other-backends"] enhanced-determinism = ["rapier2d/enhanced-determinism"] [dependencies] -rand = "0.8" +rand = "0.9" Inflector = "0.11" [dependencies.rapier_testbed2d] diff --git a/benchmarks2d/convex_polygons2.rs b/benchmarks2d/convex_polygons2.rs index 50bd843..d7f123c 100644 --- a/benchmarks2d/convex_polygons2.rs +++ b/benchmarks2d/convex_polygons2.rs @@ -1,4 +1,4 @@ -use rand::distributions::{Distribution, Standard}; +use rand::distr::{Distribution, StandardUniform}; use rand::{SeedableRng, rngs::StdRng}; use rapier_testbed2d::Testbed; use rapier2d::prelude::*; @@ -48,7 +48,7 @@ pub fn init_world(testbed: &mut Testbed) { let centery = shift / 2.0; let mut rng = StdRng::seed_from_u64(0); - let distribution = Standard; + let distribution = StandardUniform; for i in 0..num { for j in 0usize..num * 5 { diff --git a/benchmarks3d/Cargo.toml b/benchmarks3d/Cargo.toml index 8390028..d6d7cd6 100644 --- a/benchmarks3d/Cargo.toml +++ b/benchmarks3d/Cargo.toml @@ -12,7 +12,7 @@ other-backends = ["rapier_testbed3d/other-backends"] enhanced-determinism = ["rapier3d/enhanced-determinism"] [dependencies] -rand = "0.8" +rand = "0.9" Inflector = "0.11" oorandom = "11" diff --git a/benchmarks3d/convex_polyhedron3.rs b/benchmarks3d/convex_polyhedron3.rs index 2317dfe..b51d56b 100644 --- a/benchmarks3d/convex_polyhedron3.rs +++ b/benchmarks3d/convex_polyhedron3.rs @@ -1,4 +1,4 @@ -use rand::distributions::{Distribution, Standard}; +use rand::distr::{Distribution, StandardUniform}; use rand::{SeedableRng, rngs::StdRng}; use rapier_testbed3d::Testbed; use rapier3d::prelude::*; @@ -39,7 +39,7 @@ pub fn init_world(testbed: &mut Testbed) { let mut offset = -(num as f32) * shift * 0.5; let mut rng = StdRng::seed_from_u64(0); - let distribution = Standard; + let distribution = StandardUniform; for j in 0usize..47 { for i in 0..num { diff --git a/benchmarks3d/ray_cast3.rs b/benchmarks3d/ray_cast3.rs index 2f9b8bc..d38a231 100644 --- a/benchmarks3d/ray_cast3.rs +++ b/benchmarks3d/ray_cast3.rs @@ -51,10 +51,7 @@ pub fn init_world(testbed: &mut Testbed) { let t1 = std::time::Instant::now(); let max_toi = ray_ball_radius - 1.0; - let Some(broad_phase) = physics.broad_phase.downcast_ref::() else { - return; - }; - let query_pipeline = broad_phase.as_query_pipeline( + let query_pipeline = physics.broad_phase.as_query_pipeline( physics.narrow_phase.query_dispatcher(), &physics.bodies, &physics.colliders, diff --git a/crates/rapier2d-f64/Cargo.toml b/crates/rapier2d-f64/Cargo.toml index 7112aeb..357897f 100644 --- a/crates/rapier2d-f64/Cargo.toml +++ b/crates/rapier2d-f64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier2d-f64" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "2-dimensional physics engine in Rust." documentation = "https://docs.rs/rapier2d" @@ -68,8 +68,8 @@ required-features = ["dim2", "f64"] vec_map = { version = "0.8", optional = true } web-time = { version = "1.1", optional = true } num-traits = "0.2" -nalgebra = "0.33" -parry2d-f64 = "0.22.0" +nalgebra = "0.34" +parry2d-f64 = "0.23.0" simba = "0.9" approx = "0.5" rayon = { version = "1", optional = true } diff --git a/crates/rapier2d/Cargo.toml b/crates/rapier2d/Cargo.toml index 3cdd6e8..348a5b0 100644 --- a/crates/rapier2d/Cargo.toml +++ b/crates/rapier2d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier2d" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "2-dimensional physics engine in Rust." documentation = "https://docs.rs/rapier2d" @@ -69,8 +69,8 @@ required-features = ["dim2", "f32"] vec_map = { version = "0.8", optional = true } web-time = { version = "1.1", optional = true } num-traits = "0.2" -nalgebra = "0.33" -parry2d = "0.22.0" +nalgebra = "0.34" +parry2d = "0.23.0" simba = "0.9" approx = "0.5" rayon = { version = "1", optional = true } diff --git a/crates/rapier3d-f64/Cargo.toml b/crates/rapier3d-f64/Cargo.toml index f09a0da..c02377f 100644 --- a/crates/rapier3d-f64/Cargo.toml +++ b/crates/rapier3d-f64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier3d-f64" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "3-dimensional physics engine in Rust." documentation = "https://docs.rs/rapier3d" @@ -71,8 +71,8 @@ required-features = ["dim3", "f64"] vec_map = { version = "0.8", optional = true } web-time = { version = "1.1", optional = true } num-traits = "0.2" -nalgebra = "0.33" -parry3d-f64 = "0.22.0" +nalgebra = "0.34" +parry3d-f64 = "0.23.0" simba = "0.9" approx = "0.5" rayon = { version = "1", optional = true } diff --git a/crates/rapier3d-meshloader/Cargo.toml b/crates/rapier3d-meshloader/Cargo.toml index 7714c55..e0b9a12 100644 --- a/crates/rapier3d-meshloader/Cargo.toml +++ b/crates/rapier3d-meshloader/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier3d-meshloader" -version = "0.8.0" +version = "0.9.0" authors = ["Sébastien Crozet "] description = "STL file loader for the 3D rapier physics engine." documentation = "https://docs.rs/rapier3d-meshloader" @@ -29,4 +29,4 @@ thiserror = "2" profiling = "1.0" mesh-loader = "0.1.12" -rapier3d = { version = "0.27.0", path = "../rapier3d" } +rapier3d = { version = "0.28.0", path = "../rapier3d" } diff --git a/crates/rapier3d-urdf/Cargo.toml b/crates/rapier3d-urdf/Cargo.toml index 1de9365..086f471 100644 --- a/crates/rapier3d-urdf/Cargo.toml +++ b/crates/rapier3d-urdf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier3d-urdf" -version = "0.8.0" +version = "0.9.0" authors = ["Sébastien Crozet "] description = "URDF file loader for the 3D rapier physics engine." documentation = "https://docs.rs/rapier3d-urdf" @@ -31,5 +31,5 @@ anyhow = "1" bitflags = "2" urdf-rs = "0.9" -rapier3d = { version = "0.27.0", path = "../rapier3d" } -rapier3d-meshloader = { version = "0.8.0", path = "../rapier3d-meshloader", default-features = false, optional = true } +rapier3d = { version = "0.28.0", path = "../rapier3d" } +rapier3d-meshloader = { version = "0.9.0", path = "../rapier3d-meshloader", default-features = false, optional = true } diff --git a/crates/rapier3d/Cargo.toml b/crates/rapier3d/Cargo.toml index 37073fe..ac2372c 100644 --- a/crates/rapier3d/Cargo.toml +++ b/crates/rapier3d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier3d" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "3-dimensional physics engine in Rust." documentation = "https://docs.rs/rapier3d" @@ -73,8 +73,8 @@ required-features = ["dim3", "f32"] vec_map = { version = "0.8", optional = true } web-time = { version = "1.1", optional = true } num-traits = "0.2" -nalgebra = "0.33" -parry3d = "0.22.0" +nalgebra = "0.34" +parry3d = "0.23.0" simba = "0.9" approx = "0.5" rayon = { version = "1", optional = true } diff --git a/crates/rapier_testbed2d-f64/Cargo.toml b/crates/rapier_testbed2d-f64/Cargo.toml index e17deec..37f7e95 100644 --- a/crates/rapier_testbed2d-f64/Cargo.toml +++ b/crates/rapier_testbed2d-f64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed2d-f64" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 2-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -43,9 +43,9 @@ unstable-puffin-pr-235 = [] features = ["parallel", "profiler_ui"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam029"] } -rand = "0.8" -rand_pcg = "0.3" +nalgebra = { version = "0.34", features = ["rand", "glam029"] } +rand = "0.9" +rand_pcg = "0.9" web-time = { version = "1.1" } bitflags = "2" num_cpus = { version = "1", optional = true } @@ -98,5 +98,5 @@ bevy = { version = "0.15", default-features = false, features = [ [dependencies.rapier] package = "rapier2d-f64" path = "../rapier2d-f64" -version = "0.27.0" +version = "0.28.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/crates/rapier_testbed2d/Cargo.toml b/crates/rapier_testbed2d/Cargo.toml index 840770b..af4d34f 100644 --- a/crates/rapier_testbed2d/Cargo.toml +++ b/crates/rapier_testbed2d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed2d" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 2-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -43,9 +43,9 @@ unstable-puffin-pr-235 = [] features = ["parallel", "other-backends", "profiler_ui"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam029"] } -rand = "0.8" -rand_pcg = "0.3" +nalgebra = { version = "0.34", features = ["rand", "glam029"] } +rand = "0.9" +rand_pcg = "0.9" web-time = { version = "1.1" } bitflags = "2" num_cpus = { version = "1", optional = true } @@ -98,5 +98,5 @@ bevy = { version = "0.15", default-features = false, features = [ [dependencies.rapier] package = "rapier2d" path = "../rapier2d" -version = "0.27.0" +version = "0.28.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/crates/rapier_testbed3d-f64/Cargo.toml b/crates/rapier_testbed3d-f64/Cargo.toml index 1776dfd..9088a4a 100644 --- a/crates/rapier_testbed3d-f64/Cargo.toml +++ b/crates/rapier_testbed3d-f64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed3d-f64" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 3-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -45,9 +45,9 @@ unstable-puffin-pr-235 = [] features = ["parallel", "profiler_ui"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam029"] } -rand = "0.8" -rand_pcg = "0.3" +nalgebra = { version = "0.34", features = ["rand", "glam029"] } +rand = "0.9" +rand_pcg = "0.9" web-time = { version = "1.1" } bitflags = "2" num_cpus = { version = "1", optional = true } @@ -99,5 +99,5 @@ bevy = { version = "0.15", default-features = false, features = [ [dependencies.rapier] package = "rapier3d-f64" path = "../rapier3d-f64" -version = "0.27.0" +version = "0.28.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/crates/rapier_testbed3d/Cargo.toml b/crates/rapier_testbed3d/Cargo.toml index 23177ce..ba2ed0d 100644 --- a/crates/rapier_testbed3d/Cargo.toml +++ b/crates/rapier_testbed3d/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rapier_testbed3d" -version = "0.27.0" +version = "0.28.0" authors = ["Sébastien Crozet "] description = "Testbed for the Rapier 3-dimensional physics engine in Rust." homepage = "http://rapier.rs" @@ -43,9 +43,9 @@ unstable-puffin-pr-235 = [] features = ["parallel", "other-backends", "profiler_ui"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam029"] } -rand = "0.8" -rand_pcg = "0.3" +nalgebra = { version = "0.34", features = ["rand", "glam029"] } +rand = "0.9" +rand_pcg = "0.9" web-time = { version = "1.1" } bitflags = "2" glam = { version = "0.27", optional = true } # For Physx @@ -97,5 +97,5 @@ bevy = { version = "0.15", default-features = false, features = [ [dependencies.rapier] package = "rapier3d" path = "../rapier3d" -version = "0.27.0" +version = "0.28.0" features = ["serde-serialize", "debug-render", "profiler"] diff --git a/examples2d/Cargo.toml b/examples2d/Cargo.toml index 202d8f6..9bd0efe 100644 --- a/examples2d/Cargo.toml +++ b/examples2d/Cargo.toml @@ -13,7 +13,7 @@ other-backends = ["rapier_testbed2d/other-backends"] enhanced-determinism = ["rapier2d/enhanced-determinism"] [dependencies] -rand = "0.8" +rand = "0.9" lyon = "0.17" usvg = "0.14" dot_vox = "5" diff --git a/examples2d/convex_polygons2.rs b/examples2d/convex_polygons2.rs index cc20484..27356b0 100644 --- a/examples2d/convex_polygons2.rs +++ b/examples2d/convex_polygons2.rs @@ -1,4 +1,4 @@ -use rand::distributions::{Distribution, Standard}; +use rand::distr::{Distribution, StandardUniform}; use rand::{SeedableRng, rngs::StdRng}; use rapier_testbed2d::Testbed; use rapier2d::prelude::*; @@ -48,7 +48,7 @@ pub fn init_world(testbed: &mut Testbed) { let centery = shift / 2.0; let mut rng = StdRng::seed_from_u64(0); - let distribution = Standard; + let distribution = StandardUniform; for i in 0..num { for j in 0usize..num * 4 { diff --git a/examples2d/debug_intersection2.rs b/examples2d/debug_intersection2.rs index c61cb5d..6e9ec0b 100644 --- a/examples2d/debug_intersection2.rs +++ b/examples2d/debug_intersection2.rs @@ -42,10 +42,7 @@ pub fn init_world(testbed: &mut Testbed) { testbed.add_callback(move |mut graphics, physics, _, run| { let slow_time = run.timestep_id as f32 / 3.0; - let Some(broad_phase) = physics.broad_phase.downcast_ref::() else { - return; - }; - let query_pipeline = broad_phase.as_query_pipeline( + let query_pipeline = physics.broad_phase.as_query_pipeline( physics.narrow_phase.query_dispatcher(), &physics.bodies, &physics.colliders, diff --git a/examples2d/utils/character.rs b/examples2d/utils/character.rs index 5592e08..88a5e63 100644 --- a/examples2d/utils/character.rs +++ b/examples2d/utils/character.rs @@ -137,10 +137,7 @@ fn update_kinematic_controller( let character_shape = character_collider.shared_shape().clone(); let character_mass = character_body.mass(); - let Some(broad_phase) = phx.broad_phase.downcast_ref::() else { - return; - }; - let mut query_pipeline = broad_phase.as_query_pipeline_mut( + let mut query_pipeline = phx.broad_phase.as_query_pipeline_mut( phx.narrow_phase.query_dispatcher(), &mut phx.bodies, &mut phx.colliders, diff --git a/examples3d-f64/Cargo.toml b/examples3d-f64/Cargo.toml index fa7e2b6..630c75f 100644 --- a/examples3d-f64/Cargo.toml +++ b/examples3d-f64/Cargo.toml @@ -12,7 +12,7 @@ simd-nightly = ["rapier3d-f64/simd-nightly"] enhanced-determinism = ["rapier3d-f64/enhanced-determinism"] [dependencies] -rand = "0.8" +rand = "0.9" getrandom = { version = "0.2", features = ["js"] } wasm-bindgen = "0.2" obj-rs = { version = "0.7", default-features = false } diff --git a/examples3d-f64/debug_serialized3.rs b/examples3d-f64/debug_serialized3.rs index c046f51..0ab1487 100644 --- a/examples3d-f64/debug_serialized3.rs +++ b/examples3d-f64/debug_serialized3.rs @@ -27,7 +27,7 @@ pub fn init_world(testbed: &mut Testbed) { state.multibody_joints, ); testbed.harness_mut().physics.islands = state.islands; - testbed.harness_mut().physics.broad_phase = Box::new(state.broad_phase); + testbed.harness_mut().physics.broad_phase = state.broad_phase; testbed.harness_mut().physics.narrow_phase = state.narrow_phase; testbed.harness_mut().physics.ccd_solver = state.ccd_solver; diff --git a/examples3d/Cargo.toml b/examples3d/Cargo.toml index d361608..0d522a9 100644 --- a/examples3d/Cargo.toml +++ b/examples3d/Cargo.toml @@ -13,7 +13,7 @@ other-backends = ["rapier_testbed3d/other-backends"] enhanced-determinism = ["rapier3d/enhanced-determinism"] [dependencies] -rand = "0.8" +rand = "0.9" getrandom = { version = "0.2", features = ["js"] } wasm-bindgen = "0.2" obj-rs = { version = "0.7", default-features = false } diff --git a/examples3d/all_examples3.rs b/examples3d/all_examples3.rs index 1a6909a..5aa3a68 100644 --- a/examples3d/all_examples3.rs +++ b/examples3d/all_examples3.rs @@ -17,6 +17,7 @@ mod convex_polyhedron3; mod damping3; mod debug_add_remove_collider3; mod debug_articulations3; +mod debug_balls3; mod debug_big_colliders3; mod debug_boxes3; mod debug_cylinder3; @@ -101,6 +102,7 @@ pub fn main() { ), ("(Debug) big colliders", debug_big_colliders3::init_world), ("(Debug) boxes", debug_boxes3::init_world), + ("(Debug) balls", debug_balls3::init_world), ("(Debug) pop", debug_pop3::init_world), ( "(Debug) dyn. coll. add", diff --git a/examples3d/convex_polyhedron3.rs b/examples3d/convex_polyhedron3.rs index 25e777a..2908b57 100644 --- a/examples3d/convex_polyhedron3.rs +++ b/examples3d/convex_polyhedron3.rs @@ -1,4 +1,4 @@ -use rand::distributions::{Distribution, Standard}; +use rand::distr::{Distribution, StandardUniform}; use rand::{SeedableRng, rngs::StdRng}; use rapier_testbed3d::Testbed; use rapier3d::prelude::*; @@ -36,7 +36,7 @@ pub fn init_world(testbed: &mut Testbed) { let centerz = shift * (num / 2) as f32; let mut rng = StdRng::seed_from_u64(0); - let distribution = Standard; + let distribution = StandardUniform; for j in 0usize..25 { for i in 0..num { diff --git a/examples3d/debug_balls3.rs b/examples3d/debug_balls3.rs new file mode 100644 index 0000000..124bb47 --- /dev/null +++ b/examples3d/debug_balls3.rs @@ -0,0 +1,59 @@ +use rapier_testbed3d::Testbed; +use rapier3d::prelude::*; + +pub fn init_world(testbed: &mut Testbed) { + /* + * World + */ + let mut bodies = RigidBodySet::new(); + let mut colliders = ColliderSet::new(); + let impulse_joints = ImpulseJointSet::new(); + let multibody_joints = MultibodyJointSet::new(); + + /* + * Create the balls + */ + let num_j = 10; + let num_ik = 10; + let rad = 0.5; + + let shift = rad * 2.0; + let centerx = shift * (num_ik as f32) / 2.0; + let centery = shift / 2.0; + let centerz = shift * (num_ik as f32) / 2.0; + + for i in 0..num_ik { + for j in 0usize..num_j { + for k in 0..num_ik { + let x = i as f32 * shift - centerx; + let z = k as f32 * shift - centerz; + + let status = if j == 0 || i == 0 || k == 0 || i == num_ik - 1 || k == num_ik - 1 { + RigidBodyType::Fixed + } else { + RigidBodyType::Dynamic + }; + + let y = if status.is_fixed() { + j as f32 * shift + centery + } else { + j as f32 * shift * 2.0 + centery + }; + + // Build the rigid body. + let rigid_body = RigidBodyBuilder::new(status) + .translation(vector![x, y, z]) + .can_sleep(false); + let handle = bodies.insert(rigid_body); + let collider = ColliderBuilder::ball(rad).friction(0.0); + colliders.insert_with_parent(collider, handle, &mut bodies); + } + } + } + + /* + * Set up the testbed. + */ + testbed.set_world(bodies, colliders, impulse_joints, multibody_joints); + testbed.look_at(point![100.0, 100.0, 100.0], Point::origin()); +} diff --git a/examples3d/debug_deserialize3.rs b/examples3d/debug_deserialize3.rs index 3ba590b..3f05bbc 100644 --- a/examples3d/debug_deserialize3.rs +++ b/examples3d/debug_deserialize3.rs @@ -54,7 +54,7 @@ pub fn init_world(testbed: &mut Testbed) { ); testbed.harness_mut().physics.islands = state.islands; - testbed.harness_mut().physics.broad_phase = Box::new(state.broad_phase); + testbed.harness_mut().physics.broad_phase = state.broad_phase; testbed.harness_mut().physics.narrow_phase = state.narrow_phase; testbed.harness_mut().physics.integration_parameters = state.integration_parameters; testbed.harness_mut().physics.gravity = state.gravity; diff --git a/examples3d/utils/character.rs b/examples3d/utils/character.rs index fdcd9f7..f69a22f 100644 --- a/examples3d/utils/character.rs +++ b/examples3d/utils/character.rs @@ -147,10 +147,7 @@ fn update_kinematic_controller( let character_shape = character_collider.shared_shape().clone(); let character_mass = character_body.mass(); - let Some(broad_phase) = phx.broad_phase.downcast_ref::() else { - return; - }; - let mut query_pipeline = broad_phase.as_query_pipeline_mut( + let mut query_pipeline = phx.broad_phase.as_query_pipeline_mut( phx.narrow_phase.query_dispatcher(), &mut phx.bodies, &mut phx.colliders, diff --git a/examples3d/voxels3.rs b/examples3d/voxels3.rs index a2e0ddf..d202e6a 100644 --- a/examples3d/voxels3.rs +++ b/examples3d/voxels3.rs @@ -211,10 +211,7 @@ pub fn init_world(testbed: &mut Testbed) { predicate: Some(&|_, co: &Collider| co.shape().as_voxels().is_some()), ..Default::default() }; - let Some(broad_phase) = physics.broad_phase.downcast_ref::() else { - return; - }; - let query_pipeline = broad_phase.as_query_pipeline( + let query_pipeline = physics.broad_phase.as_query_pipeline( physics.narrow_phase.query_dispatcher(), &physics.bodies, &physics.colliders, diff --git a/publish-all.sh b/publish-all.sh index d773b6b..6d79539 100755 --- a/publish-all.sh +++ b/publish-all.sh @@ -3,12 +3,12 @@ if [[ "$PUBLISH_MODE" == 1 ]] then ./scripts/publish-rapier.sh && - ./scripts/publish-testbeds.sh && - ./scripts/publish-extra-formats.sh + ./scripts/publish-extra-formats.sh && + ./scripts/publish-testbeds.sh else echo "Running in dry mode, re-run with \`PUBLISH_MODE=1 publish-all.sh\` to actually publish." DRY_RUN="--dry-run" ./scripts/publish-rapier.sh && - DRY_RUN="--dry-run" ./scripts/publish-testbeds.sh && - DRY_RUN="--dry-run" ./scripts/publish-extra-formats.sh + DRY_RUN="--dry-run" ./scripts/publish-extra-formats.sh && + DRY_RUN="--dry-run" ./scripts/publish-testbeds.sh fi \ No newline at end of file diff --git a/src/dynamics/ccd/ccd_solver.rs b/src/dynamics/ccd/ccd_solver.rs index e8834c7..9bfaa6e 100644 --- a/src/dynamics/ccd/ccd_solver.rs +++ b/src/dynamics/ccd/ccd_solver.rs @@ -1,11 +1,10 @@ use super::TOIEntry; -use crate::dynamics::{IslandManager, RigidBodyHandle, RigidBodySet}; -use crate::geometry::{ColliderParent, ColliderSet, CollisionEvent, NarrowPhase}; +use crate::dynamics::{IntegrationParameters, IslandManager, RigidBodyHandle, RigidBodySet}; +use crate::geometry::{BroadPhaseBvh, ColliderParent, ColliderSet, CollisionEvent, NarrowPhase}; use crate::math::Real; use crate::parry::utils::SortedPair; -use crate::pipeline::{EventHandler, QueryFilter, QueryPipeline}; +use crate::pipeline::{EventHandler, QueryFilter}; use crate::prelude::{ActiveEvents, CollisionEventFlags}; -use parry::partitioning::{Bvh, BvhBuildStrategy}; use parry::utils::hashmap::HashMap; use std::collections::BinaryHeap; @@ -16,26 +15,14 @@ pub enum PredictedImpacts { } /// Solver responsible for performing motion-clamping on fast-moving bodies. -#[derive(Clone)] +#[derive(Clone, Default)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] -pub struct CCDSolver { - // TODO PERF: for now the CCD solver maintains its own bvh for CCD queries. - // At each frame it get rebuilt. - // We should consider an alternative to directly use the broad-phase’s. - #[cfg_attr(feature = "serde-serialize", serde(skip))] - bvh: Bvh, -} - -impl Default for CCDSolver { - fn default() -> Self { - Self::new() - } -} +pub struct CCDSolver; impl CCDSolver { /// Initializes a new CCD solver pub fn new() -> Self { - Self { bvh: Bvh::new() } + Self } /// Apply motion-clamping to the bodies affected by the given `impacts`. @@ -101,42 +88,35 @@ impl CCDSolver { #[profiling::function] pub fn find_first_impact( &mut self, - dt: Real, + dt: Real, // NOTE: this doesn’t necessarily match the `params.dt`. + params: &IntegrationParameters, islands: &IslandManager, bodies: &RigidBodySet, colliders: &ColliderSet, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &NarrowPhase, ) -> Option { // Update the query pipeline with the colliders’ predicted positions. - self.bvh = Bvh::from_iter( - BvhBuildStrategy::Binned, - colliders.iter_enabled().map(|(co_handle, co)| { - let id = co_handle.into_raw_parts().0; - if let Some(co_parent) = co.parent { - let rb = &bodies[co_parent.handle]; + for (handle, co) in colliders.iter_enabled() { + if let Some(co_parent) = co.parent { + let rb = &bodies[co_parent.handle]; + if rb.is_ccd_active() { let predicted_pos = rb .pos .integrate_forces_and_velocities(dt, &rb.forces, &rb.vels, &rb.mprops); - let next_position = predicted_pos * co_parent.pos_wrt_parent; - ( - id as usize, - co.shape.compute_swept_aabb(&co.pos, &next_position), - ) - } else { - (id as usize, co.shape.compute_aabb(&co.pos)) + let swept_aabb = co.shape.compute_swept_aabb(&co.pos, &next_position); + broad_phase.set_aabb(params, handle, swept_aabb); } - }), - ); + } + } - let query_pipeline = QueryPipeline { - // NOTE: the upcast needs at least rust 1.86 - dispatcher: narrow_phase.query_dispatcher(), - bvh: &self.bvh, + let query_pipeline = broad_phase.as_query_pipeline( + narrow_phase.query_dispatcher(), bodies, colliders, - filter: QueryFilter::default(), - }; + QueryFilter::default(), + ); let mut pairs_seen = HashMap::default(); let mut min_toi = dt; @@ -234,43 +214,39 @@ impl CCDSolver { #[profiling::function] pub fn predict_impacts_at_next_positions( &mut self, - dt: Real, + params: &IntegrationParameters, islands: &IslandManager, bodies: &RigidBodySet, colliders: &ColliderSet, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &NarrowPhase, events: &dyn EventHandler, ) -> PredictedImpacts { + let dt = params.dt; let mut frozen = HashMap::<_, Real>::default(); let mut all_toi = BinaryHeap::new(); let mut pairs_seen = HashMap::default(); let mut min_overstep = dt; // Update the query pipeline with the colliders’ `next_position`. - self.bvh = Bvh::from_iter( - BvhBuildStrategy::Binned, - colliders.iter_enabled().map(|(co_handle, co)| { - let id = co_handle.into_raw_parts().0; - if let Some(co_parent) = co.parent { + for (handle, co) in colliders.iter_enabled() { + if let Some(co_parent) = co.parent { + let rb = &bodies[co_parent.handle]; + if rb.is_ccd_active() { let rb_next_pos = &bodies[co_parent.handle].pos.next_position; let next_position = rb_next_pos * co_parent.pos_wrt_parent; - ( - id as usize, - co.shape.compute_swept_aabb(&co.pos, &next_position), - ) - } else { - (id as usize, co.shape.compute_aabb(&co.pos)) + let swept_aabb = co.shape.compute_swept_aabb(&co.pos, &next_position); + broad_phase.set_aabb(params, handle, swept_aabb); } - }), - ); + } + } - let query_pipeline = QueryPipeline { - dispatcher: narrow_phase.query_dispatcher(), - bvh: &self.bvh, + let query_pipeline = broad_phase.as_query_pipeline( + narrow_phase.query_dispatcher(), bodies, colliders, - filter: QueryFilter::default(), - }; + QueryFilter::default(), + ); /* * diff --git a/src/geometry/broad_phase.rs b/src/geometry/broad_phase.rs deleted file mode 100644 index 03ea613..0000000 --- a/src/geometry/broad_phase.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::dynamics::RigidBodySet; -use crate::geometry::{BroadPhasePairEvent, ColliderHandle, ColliderSet}; -use crate::prelude::IntegrationParameters; -use downcast_rs::DowncastSync; - -/// Trait implemented by broad-phase algorithms supported by Rapier. -/// -/// The task of a broad-phase algorithm is to detect potential collision pairs, usually based on -/// bounding volumes. The pairs must be conservative: it is OK to create a collision pair if -/// two objects don’t actually touch, but it is incorrect to remove a pair between two objects -/// that are still touching. In other words, it can have false-positive (though these induce -/// some computational overhead on the narrow-phase), but cannot have false-negative. -pub trait BroadPhase: Send + Sync + 'static + DowncastSync { - /// Updates the broad-phase. - /// - /// The results must be output through the `events` struct. The broad-phase algorithm is only - /// required to generate new events (i.e. no need to re-send an `AddPair` event if it was already - /// sent previously and no `RemovePair` happened since then). Sending redundant events is allowed - /// but can result in a slight computational overhead. - /// - /// The `colliders` set is mutable only to provide access to - /// [`collider.set_internal_broad_phase_proxy_index`]. Other properties of the collider should - /// **not** be modified during the broad-phase update. - /// - /// # Parameters - /// - `params`: the integration parameters governing the simulation. - /// - `colliders`: the set of colliders. Change detection with `collider.needs_broad_phase_update()` - /// can be relied on at this stage. - /// - `modified_colliders`: colliders that are know to be modified since the last update. - /// - `removed_colliders`: colliders that got removed since the last update. Any associated data - /// in the broad-phase should be removed by this call to `update`. - /// - `events`: the broad-phase’s output. They indicate what collision pairs need to be created - /// and what pairs need to be removed. It is OK to create pairs for colliders that don’t - /// actually collide (though this can increase computational overhead in the narrow-phase) - /// but it is important not to indicate removal of a collision pair if the underlying colliders - /// are still touching or closer than `prediction_distance`. - fn update( - &mut self, - params: &IntegrationParameters, - colliders: &ColliderSet, - bodies: &RigidBodySet, - modified_colliders: &[ColliderHandle], - removed_colliders: &[ColliderHandle], - events: &mut Vec, - ); -} - -downcast_rs::impl_downcast!(sync BroadPhase); diff --git a/src/geometry/broad_phase_bvh.rs b/src/geometry/broad_phase_bvh.rs index 196f60a..691b33f 100644 --- a/src/geometry/broad_phase_bvh.rs +++ b/src/geometry/broad_phase_bvh.rs @@ -1,6 +1,6 @@ use crate::dynamics::{IntegrationParameters, RigidBodySet}; -use crate::geometry::{BroadPhase, BroadPhasePairEvent, ColliderHandle, ColliderPair, ColliderSet}; -use parry::bounding_volume::BoundingVolume; +use crate::geometry::{Aabb, BroadPhasePairEvent, ColliderHandle, ColliderPair, ColliderSet}; +use crate::math::Real; use parry::partitioning::{Bvh, BvhWorkspace}; use parry::utils::hashmap::{Entry, HashMap}; @@ -36,6 +36,9 @@ pub enum BvhOptimizationStrategy { const ENABLE_TREE_VALIDITY_CHECK: bool = false; impl BroadPhaseBvh { + const CHANGE_DETECTION_ENABLED: bool = true; + const CHANGE_DETECTION_FACTOR: Real = 1.0e-2; + /// Initializes a new empty broad-phase. pub fn new() -> Self { Self::default() @@ -50,7 +53,30 @@ impl BroadPhaseBvh { } } - fn update_with_strategy( + /// Updates the broad-phase. + /// + /// The results are output through the `events` struct. The broad-phase algorithm is only + /// required to generate new events (i.e. no need to re-send an `AddPair` event if it was already + /// sent previously and no `RemovePair` happened since then). Sending redundant events is allowed + /// but can result in a slight computational overhead. + /// + /// The `colliders` set is mutable only to provide access to + /// [`collider.set_internal_broad_phase_proxy_index`]. Other properties of the collider should + /// **not** be modified during the broad-phase update. + /// + /// # Parameters + /// - `params`: the integration parameters governing the simulation. + /// - `colliders`: the set of colliders. Change detection with `collider.needs_broad_phase_update()` + /// can be relied on at this stage. + /// - `modified_colliders`: colliders that are know to be modified since the last update. + /// - `removed_colliders`: colliders that got removed since the last update. Any associated data + /// in the broad-phase should be removed by this call to `update`. + /// - `events`: the broad-phase’s output. They indicate what collision pairs need to be created + /// and what pairs need to be removed. It is OK to create pairs for colliders that don’t + /// actually collide (though this can increase computational overhead in the narrow-phase) + /// but it is important not to indicate removal of a collision pair if the underlying colliders + /// are still touching or closer than `prediction_distance`. + pub fn update( &mut self, params: &IntegrationParameters, colliders: &ColliderSet, @@ -58,10 +84,7 @@ impl BroadPhaseBvh { modified_colliders: &[ColliderHandle], removed_colliders: &[ColliderHandle], events: &mut Vec, - strategy: BvhOptimizationStrategy, ) { - const CHANGE_DETECTION_ENABLED: bool = true; - self.frame_index = self.frame_index.overflowing_add(1).0; // Removals must be handled first, in case another collider in @@ -70,9 +93,9 @@ impl BroadPhaseBvh { self.tree.remove(handle.into_raw_parts().0); } - if modified_colliders.is_empty() { - return; - } + // if modified_colliders.is_empty() { + // return; + // } let first_pass = self.tree.is_empty(); @@ -83,29 +106,10 @@ impl BroadPhaseBvh { continue; } - // Take soft-ccd into account by growing the aabb. - let next_pose = collider.parent.and_then(|p| { - let parent = bodies.get(p.handle)?; - (parent.soft_ccd_prediction() > 0.0).then(|| { - parent.predict_position_using_velocity_and_forces_with_max_dist( - params.dt, - parent.soft_ccd_prediction(), - ) * p.pos_wrt_parent - }) - }); + let aabb = collider.compute_broad_phase_aabb(params, bodies); - let prediction_distance = params.prediction_distance(); - let mut aabb = collider.compute_collision_aabb(prediction_distance / 2.0); - if let Some(next_pose) = next_pose { - let next_aabb = collider - .shape - .compute_aabb(&next_pose) - .loosened(collider.contact_skin() + prediction_distance / 2.0); - aabb.merge(&next_aabb); - } - - let change_detection_skin = if CHANGE_DETECTION_ENABLED { - 1.0e-2 * params.length_unit + let change_detection_skin = if Self::CHANGE_DETECTION_ENABLED { + Self::CHANGE_DETECTION_FACTOR * params.length_unit } else { 0.0 }; @@ -127,7 +131,7 @@ impl BroadPhaseBvh { } // let t0 = std::time::Instant::now(); - match strategy { + match self.optimization_strategy { BvhOptimizationStrategy::SubtreeOptimizer => { self.tree.optimize_incremental(&mut self.workspace); } @@ -190,7 +194,7 @@ impl BroadPhaseBvh { // let t0 = std::time::Instant::now(); self.tree - .traverse_bvtt_single_tree::( + .traverse_bvtt_single_tree::<{ Self::CHANGE_DETECTION_ENABLED }>( &mut self.workspace, &mut pairs_collector, ); @@ -220,7 +224,7 @@ impl BroadPhaseBvh { return false; }; - if (!CHANGE_DETECTION_ENABLED || node0.is_changed() || node1.is_changed()) + if (!Self::CHANGE_DETECTION_ENABLED || node0.is_changed() || node1.is_changed()) && !node0.intersects(node1) { events.push(BroadPhasePairEvent::DeletePair(ColliderPair::new(*h0, *h1))); @@ -245,26 +249,21 @@ impl BroadPhaseBvh { // removed_pairs // ); } -} -impl BroadPhase for BroadPhaseBvh { - fn update( - &mut self, - params: &IntegrationParameters, - colliders: &ColliderSet, - bodies: &RigidBodySet, - modified_colliders: &[ColliderHandle], - removed_colliders: &[ColliderHandle], - events: &mut Vec, - ) { - self.update_with_strategy( - params, - colliders, - bodies, - modified_colliders, - removed_colliders, - events, - self.optimization_strategy, + /// Sets the AABB associated to the given collider. + /// + /// The AABB change will be immediately applied and propagated through the underlying BVH. + /// Change detection will automatically take it into account during the next broad-phase update. + pub fn set_aabb(&mut self, params: &IntegrationParameters, handle: ColliderHandle, aabb: Aabb) { + let change_detection_skin = if Self::CHANGE_DETECTION_ENABLED { + Self::CHANGE_DETECTION_FACTOR * params.length_unit + } else { + 0.0 + }; + self.tree.insert_with_change_detection( + aabb, + handle.into_raw_parts().0, + change_detection_skin, ); } } diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs index 7c6589c..eeb1e11 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -1,4 +1,4 @@ -use crate::dynamics::{CoefficientCombineRule, MassProperties, RigidBodyHandle}; +use crate::dynamics::{CoefficientCombineRule, MassProperties, RigidBodyHandle, RigidBodySet}; #[cfg(feature = "dim3")] use crate::geometry::HeightFieldFlags; use crate::geometry::{ @@ -9,7 +9,7 @@ use crate::geometry::{ use crate::math::{AngVector, DIM, Isometry, Point, Real, Rotation, Vector}; use crate::parry::transformation::vhacd::VHACDParameters; use crate::pipeline::{ActiveEvents, ActiveHooks}; -use crate::prelude::ColliderEnabled; +use crate::prelude::{ColliderEnabled, IntegrationParameters}; use na::Unit; use parry::bounding_volume::{Aabb, BoundingVolume}; use parry::shape::{Shape, TriMeshBuilderError, TriMeshFlags}; @@ -457,6 +457,40 @@ impl Collider { self.shape.compute_swept_aabb(&self.pos, next_position) } + // TODO: we have a lot of different AABB computation functions + // We should group them somehow. + /// Computes the collider’s AABB for usage in a broad-phase. + /// + /// It takes into account soft-ccd, the contact skin, and the contact prediction. + pub fn compute_broad_phase_aabb( + &self, + params: &IntegrationParameters, + bodies: &RigidBodySet, + ) -> Aabb { + // Take soft-ccd into account by growing the aabb. + let next_pose = self.parent.and_then(|p| { + let parent = bodies.get(p.handle)?; + (parent.soft_ccd_prediction() > 0.0).then(|| { + parent.predict_position_using_velocity_and_forces_with_max_dist( + params.dt, + parent.soft_ccd_prediction(), + ) * p.pos_wrt_parent + }) + }); + + let prediction_distance = params.prediction_distance(); + let mut aabb = self.compute_collision_aabb(prediction_distance / 2.0); + if let Some(next_pose) = next_pose { + let next_aabb = self + .shape + .compute_aabb(&next_pose) + .loosened(self.contact_skin() + prediction_distance / 2.0); + aabb.merge(&next_aabb); + } + + aabb + } + /// Compute the local-space mass properties of this collider. pub fn mass_properties(&self) -> MassProperties { self.mprops.mass_properties(&*self.shape) diff --git a/src/geometry/collider_set.rs b/src/geometry/collider_set.rs index 658ff05..30ff144 100644 --- a/src/geometry/collider_set.rs +++ b/src/geometry/collider_set.rs @@ -53,6 +53,10 @@ impl ColliderSet { std::mem::take(&mut self.modified_colliders) } + pub(crate) fn set_modified(&mut self, modified: ModifiedColliders) { + self.modified_colliders = modified; + } + pub(crate) fn take_removed(&mut self) -> Vec { std::mem::take(&mut self.removed_colliders) } diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index a947c9e..296e71b 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -1,6 +1,5 @@ //! Structures related to geometry: colliders, shapes, etc. -pub use self::broad_phase::BroadPhase; pub use self::broad_phase_bvh::{BroadPhaseBvh, BvhOptimizationStrategy}; pub use self::broad_phase_pair_event::{BroadPhasePairEvent, ColliderPair}; pub use self::collider::{Collider, ColliderBuilder}; @@ -199,7 +198,6 @@ mod interaction_graph; mod interaction_groups; mod narrow_phase; -mod broad_phase; mod broad_phase_bvh; mod broad_phase_pair_event; mod collider; diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index 9a05389..202612c 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -697,14 +697,9 @@ impl NarrowPhase { &mut self, bodies: &RigidBodySet, colliders: &ColliderSet, - modified_colliders: &[ColliderHandle], hooks: &dyn PhysicsHooks, events: &dyn EventHandler, ) { - if modified_colliders.is_empty() { - return; - } - let nodes = &self.intersection_graph.graph.nodes; let query_dispatcher = &*self.query_dispatcher; @@ -806,14 +801,9 @@ impl NarrowPhase { colliders: &ColliderSet, impulse_joints: &ImpulseJointSet, multibody_joints: &MultibodyJointSet, - modified_colliders: &[ColliderHandle], hooks: &dyn PhysicsHooks, events: &dyn EventHandler, ) { - if modified_colliders.is_empty() { - return; - } - let query_dispatcher = &*self.query_dispatcher; // TODO: don't iterate on all the edges. diff --git a/src/pipeline/collision_pipeline.rs b/src/pipeline/collision_pipeline.rs index b064669..84cb3c1 100644 --- a/src/pipeline/collision_pipeline.rs +++ b/src/pipeline/collision_pipeline.rs @@ -2,7 +2,7 @@ use crate::dynamics::{ImpulseJointSet, IntegrationParameters, MultibodyJointSet}; use crate::geometry::{ - BroadPhase, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair, + BroadPhaseBvh, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair, ModifiedColliders, NarrowPhase, }; use crate::math::Real; @@ -44,7 +44,7 @@ impl CollisionPipeline { fn detect_collisions( &mut self, prediction_distance: Real, - broad_phase: &mut dyn BroadPhase, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &mut NarrowPhase, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, @@ -93,11 +93,10 @@ impl CollisionPipeline { colliders, &ImpulseJointSet::new(), &MultibodyJointSet::new(), - modified_colliders, hooks, events, ); - narrow_phase.compute_intersections(bodies, colliders, modified_colliders, hooks, events); + narrow_phase.compute_intersections(bodies, colliders, hooks, events); } fn clear_modified_colliders( @@ -118,7 +117,7 @@ impl CollisionPipeline { pub fn step( &mut self, prediction_distance: Real, - broad_phase: &mut dyn BroadPhase, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &mut NarrowPhase, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index 0fee709..a47dcf1 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -10,7 +10,7 @@ use crate::dynamics::{ RigidBodyChanges, RigidBodyPosition, RigidBodyType, }; use crate::geometry::{ - BroadPhase, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair, + BroadPhaseBvh, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair, ContactManifoldIndex, ModifiedColliders, NarrowPhase, TemporaryInteractionIndex, }; use crate::math::{Real, Vector}; @@ -71,11 +71,14 @@ impl PhysicsPipeline { colliders: &mut ColliderSet, modified_colliders: &mut ModifiedColliders, ) { - for handle in modified_colliders.iter() { - if let Some(co) = colliders.get_mut_internal(*handle) { - co.changes = ColliderChanges::empty(); - } + for co in colliders.colliders.iter_mut() { + co.1.changes = ColliderChanges::empty(); } + // for handle in modified_colliders.iter() { + // if let Some(co) = colliders.get_mut_internal(*handle) { + // co.changes = ColliderChanges::empty(); + // } + // } modified_colliders.clear(); } @@ -98,7 +101,7 @@ impl PhysicsPipeline { &mut self, integration_parameters: &IntegrationParameters, islands: &mut IslandManager, - broad_phase: &mut dyn BroadPhase, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &mut NarrowPhase, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, @@ -153,11 +156,10 @@ impl PhysicsPipeline { colliders, impulse_joints, multibody_joints, - modified_colliders, hooks, events, ); - narrow_phase.compute_intersections(bodies, colliders, modified_colliders, hooks, events); + narrow_phase.compute_intersections(bodies, colliders, hooks, events); self.counters.cd.narrow_phase_time.pause(); self.counters.stages.collision_detection_time.pause(); @@ -340,6 +342,7 @@ impl PhysicsPipeline { islands: &IslandManager, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &NarrowPhase, ccd_solver: &mut CCDSolver, events: &dyn EventHandler, @@ -347,10 +350,11 @@ impl PhysicsPipeline { self.counters.ccd.toi_computation_time.start(); // Handle CCD let impacts = ccd_solver.predict_impacts_at_next_positions( - integration_parameters.dt, + integration_parameters, islands, bodies, colliders, + broad_phase, narrow_phase, events, ); @@ -414,7 +418,7 @@ impl PhysicsPipeline { gravity: &Vector, integration_parameters: &IntegrationParameters, islands: &mut IslandManager, - broad_phase: &mut dyn BroadPhase, + broad_phase: &mut BroadPhaseBvh, narrow_phase: &mut NarrowPhase, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, @@ -535,9 +539,11 @@ impl PhysicsPipeline { let first_impact = if ccd_active { ccd_solver.find_first_impact( remaining_time, + &integration_parameters, islands, bodies, colliders, + broad_phase, narrow_phase, ) } else { @@ -605,6 +611,7 @@ impl PhysicsPipeline { islands, bodies, colliders, + broad_phase, narrow_phase, ccd_solver, events, @@ -616,23 +623,35 @@ impl PhysicsPipeline { self.advance_to_final_positions(islands, bodies, colliders, &mut modified_colliders); self.counters.stages.update_time.pause(); - self.detect_collisions( - &integration_parameters, - islands, - broad_phase, - narrow_phase, - bodies, - colliders, - impulse_joints, - multibody_joints, - &modified_colliders, - &[], - hooks, - events, - false, - ); + if remaining_substeps > 0 { + self.detect_collisions( + &integration_parameters, + islands, + broad_phase, + narrow_phase, + bodies, + colliders, + impulse_joints, + multibody_joints, + &modified_colliders, + &[], + hooks, + events, + false, + ); - self.clear_modified_colliders(colliders, &mut modified_colliders); + self.clear_modified_colliders(colliders, &mut modified_colliders); + } else { + // If we ran the last substep, just update the broad-phase bvh instead + // of a full collision-detection step. + for handle in modified_colliders.iter() { + let co = &colliders[*handle]; + let aabb = co.compute_broad_phase_aabb(&integration_parameters, bodies); + broad_phase.set_aabb(&integration_parameters, *handle, aabb); + } + modified_colliders.clear(); + // self.clear_modified_colliders(colliders, &mut modified_colliders); + } } // Finally, make sure we update the world mass-properties of the rigid-bodies @@ -648,6 +667,9 @@ impl PhysicsPipeline { } self.counters.stages.update_time.pause(); + // Re-insert the modified vector we extracted for the borrow-checker. + colliders.set_modified(modified_colliders); + self.counters.step_completed(); } } diff --git a/src_testbed/graphics.rs b/src_testbed/graphics.rs index 3c7ed4f..6e642f7 100644 --- a/src_testbed/graphics.rs +++ b/src_testbed/graphics.rs @@ -14,7 +14,6 @@ use rapier::math::{Isometry, Real, Vector}; //use crate::objects::polyline::Polyline; // use crate::objects::mesh::Mesh; use crate::testbed::TestbedStateFlags; -use rand::{Rng, SeedableRng}; use rand_pcg::Pcg32; use std::collections::HashMap; @@ -97,7 +96,7 @@ pub struct GraphicsManager { impl GraphicsManager { pub fn new() -> GraphicsManager { GraphicsManager { - rand: Pcg32::seed_from_u64(0), + rand: Pcg32::new(0, 1), b2sn: HashMap::new(), b2color: HashMap::new(), c2color: HashMap::new(), @@ -128,7 +127,7 @@ impl GraphicsManager { self.c2color.clear(); self.b2color.clear(); self.b2wireframe.clear(); - self.rand = Pcg32::seed_from_u64(0); + self.rand = Pcg32::new(0, 1); } pub fn remove_collider_nodes( @@ -236,7 +235,8 @@ impl GraphicsManager { } fn gen_color(rng: &mut Pcg32) -> Point3 { - let mut color: Point3 = rng.r#gen(); + use rand::Rng; + let mut color: Point3 = rng.random(); // Quantize the colors a bit to get some amount of auto-instancing from bevy. color.x = (color.x * 5.0).round() / 5.0; diff --git a/src_testbed/harness/mod.rs b/src_testbed/harness/mod.rs index 79b134a..211be9c 100644 --- a/src_testbed/harness/mod.rs +++ b/src_testbed/harness/mod.rs @@ -9,9 +9,7 @@ use rapier::dynamics::{ CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, RigidBodySet, }; -use rapier::geometry::{ - BroadPhase, BroadPhaseBvh, BvhOptimizationStrategy, ColliderSet, NarrowPhase, -}; +use rapier::geometry::{BroadPhaseBvh, BvhOptimizationStrategy, ColliderSet, NarrowPhase}; use rapier::math::{Real, Vector}; use rapier::pipeline::{ChannelEventCollector, PhysicsHooks, PhysicsPipeline}; @@ -25,16 +23,14 @@ pub enum RapierBroadPhaseType { } impl RapierBroadPhaseType { - pub fn init_broad_phase(self) -> Box { + pub fn init_broad_phase(self) -> BroadPhaseBvh { match self { RapierBroadPhaseType::BvhSubtreeOptimizer => { - Box::new(BroadPhaseBvh::with_optimization_strategy( - BvhOptimizationStrategy::SubtreeOptimizer, - )) + BroadPhaseBvh::with_optimization_strategy(BvhOptimizationStrategy::SubtreeOptimizer) + } + RapierBroadPhaseType::BvhWithoutOptimization => { + BroadPhaseBvh::with_optimization_strategy(BvhOptimizationStrategy::None) } - RapierBroadPhaseType::BvhWithoutOptimization => Box::new( - BroadPhaseBvh::with_optimization_strategy(BvhOptimizationStrategy::None), - ), } } } @@ -250,7 +246,7 @@ impl Harness { &physics.gravity, &physics.integration_parameters, &mut physics.islands, - &mut *physics.broad_phase, + &mut physics.broad_phase, &mut physics.narrow_phase, &mut physics.bodies, &mut physics.colliders, @@ -268,7 +264,7 @@ impl Harness { &self.physics.gravity, &self.physics.integration_parameters, &mut self.physics.islands, - &mut *self.physics.broad_phase, + &mut self.physics.broad_phase, &mut self.physics.narrow_phase, &mut self.physics.bodies, &mut self.physics.colliders, diff --git a/src_testbed/physics/mod.rs b/src_testbed/physics/mod.rs index 3882964..0bc67bc 100644 --- a/src_testbed/physics/mod.rs +++ b/src_testbed/physics/mod.rs @@ -3,7 +3,7 @@ use rapier::dynamics::{ RigidBodySet, }; use rapier::geometry::{ - BroadPhase, ColliderSet, CollisionEvent, ContactForceEvent, DefaultBroadPhase, NarrowPhase, + BroadPhaseBvh, ColliderSet, CollisionEvent, ContactForceEvent, DefaultBroadPhase, NarrowPhase, }; use rapier::math::{Real, Vector}; use rapier::pipeline::{PhysicsHooks, PhysicsPipeline}; @@ -89,7 +89,7 @@ impl PhysicsSnapshot { pub struct PhysicsState { pub islands: IslandManager, - pub broad_phase: Box, + pub broad_phase: BroadPhaseBvh, pub narrow_phase: NarrowPhase, pub bodies: RigidBodySet, pub colliders: ColliderSet, @@ -112,7 +112,7 @@ impl PhysicsState { pub fn new() -> Self { Self { islands: IslandManager::new(), - broad_phase: Box::new(DefaultBroadPhase::default()), + broad_phase: DefaultBroadPhase::default(), narrow_phase: NarrowPhase::new(), bodies: RigidBodySet::new(), colliders: ColliderSet::new(), diff --git a/src_testbed/testbed.rs b/src_testbed/testbed.rs index 1af1344..1432565 100644 --- a/src_testbed/testbed.rs +++ b/src_testbed/testbed.rs @@ -23,7 +23,7 @@ use rapier::dynamics::{ RigidBodyHandle, RigidBodySet, }; #[cfg(feature = "dim3")] -use rapier::geometry::{BroadPhaseBvh, Ray}; +use rapier::geometry::Ray; use rapier::geometry::{ColliderHandle, ColliderSet, NarrowPhase}; use rapier::math::{Real, Vector}; use rapier::pipeline::PhysicsHooks; @@ -814,21 +814,12 @@ impl Testbed<'_, '_, '_, '_, '_, '_, '_, '_, '_, '_, '_, '_, '_> { wheels[1].engine_force = engine_force; wheels[1].steering = steering_angle; - let query_pipeline = if let Some(bf) = self - .harness - .physics - .broad_phase - .downcast_ref::() - { - bf.as_query_pipeline_mut( - self.harness.physics.narrow_phase.query_dispatcher(), - &mut self.harness.physics.bodies, - &mut self.harness.physics.colliders, - QueryFilter::exclude_dynamic().exclude_rigid_body(vehicle.chassis), - ) - } else { - return; - }; + let query_pipeline = self.harness.physics.broad_phase.as_query_pipeline_mut( + self.harness.physics.narrow_phase.query_dispatcher(), + &mut self.harness.physics.bodies, + &mut self.harness.physics.colliders, + QueryFilter::exclude_dynamic().exclude_rigid_body(vehicle.chassis), + ); vehicle.update_vehicle( self.harness.physics.integration_parameters.dt, @@ -1353,7 +1344,7 @@ fn update_testbed( } harness.state.timestep_id = timestep_id; - harness.physics.broad_phase = Box::new(broad_phase); + harness.physics.broad_phase = broad_phase; harness.physics.narrow_phase = narrow_phase; harness.physics.islands = island_manager; harness.physics.bodies = bodies; @@ -1630,16 +1621,12 @@ fn highlight_hovered_body( let ray_dir = Vector3::new(ray_dir.x as Real, ray_dir.y as Real, ray_dir.z as Real); let ray = Ray::new(ray_origin, ray_dir); - let query_pipeline = if let Some(bf) = physics.broad_phase.downcast_ref::() { - bf.as_query_pipeline( - physics.narrow_phase.query_dispatcher(), - &physics.bodies, - &physics.colliders, - QueryFilter::only_dynamic(), - ) - } else { - return; - }; + let query_pipeline = physics.broad_phase.as_query_pipeline( + physics.narrow_phase.query_dispatcher(), + &physics.bodies, + &physics.colliders, + QueryFilter::only_dynamic(), + ); let hit = query_pipeline.cast_ray(&ray, Real::MAX, true);