feat: switch to the new Bvh from parry for the broad-phase (#853)

* feat: switch to the new Bvh from parry for the broad-phase

* chore: cargo fmt + update testbed

* chore: remove the multi-grid SAP broad-phase

* fix soft-ccd handling in broad-phase

* Fix contact cleanup in broad-phase after collider removal

* chore: clippy fixes

* fix CCD regression

* chore: update changelog

* fix build with the parallel feature enabled

* chore: remove the now useless broad-phase proxy index from colliders

* fix tests
This commit is contained in:
Sébastien Crozet
2025-07-11 22:36:40 +02:00
committed by GitHub
parent 86a257d4f1
commit 95bd6fcfeb
212 changed files with 2140 additions and 3953 deletions

View File

@@ -2,7 +2,7 @@
name = "rapier-benchmarks-3d"
version = "0.1.0"
authors = ["Sébastien Crozet <sebcrozet@dimforge.com>"]
edition = "2021"
edition = "2024"
[features]
parallel = ["rapier3d/parallel", "rapier_testbed3d/parallel"]
@@ -14,6 +14,7 @@ enhanced-determinism = ["rapier3d/enhanced-determinism"]
[dependencies]
rand = "0.8"
Inflector = "0.11"
oorandom = "11"
[dependencies.rapier_testbed3d]
path = "../crates/rapier_testbed3d"

View File

@@ -20,10 +20,12 @@ mod joint_fixed3;
mod joint_prismatic3;
mod joint_revolute3;
mod keva3;
mod many_kinematics3;
mod many_pyramids3;
mod many_sleep3;
mod many_static3;
mod pyramid3;
mod ray_cast3;
mod stacks3;
mod trimesh3;
@@ -47,9 +49,8 @@ fn parse_command_line() -> Command {
Command::RunAll
}
pub fn main() {
let command = parse_command_line();
#[allow(clippy::type_complexity)]
pub fn demo_builders() -> Vec<(&'static str, fn(&mut Testbed))> {
let mut builders: Vec<(_, fn(&mut Testbed))> = vec![
("Balls", balls3::init_world),
("Boxes", boxes3::init_world),
@@ -57,6 +58,7 @@ pub fn main() {
("CCD", ccd3::init_world),
("Compound", compound3::init_world),
("Convex polyhedron", convex_polyhedron3::init_world),
("Many kinematics", many_kinematics3::init_world),
("Many static", many_static3::init_world),
("Many sleep", many_sleep3::init_world),
("Heightfield", heightfield3::init_world),
@@ -69,6 +71,7 @@ pub fn main() {
("ImpulseJoint prismatic", joint_prismatic3::init_world),
("Many pyramids", many_pyramids3::init_world),
("Keva tower", keva3::init_world),
("Ray cast", ray_cast3::init_world),
];
// Lexicographic sort, with stress tests moved at the end of the list.
@@ -77,6 +80,12 @@ pub fn main() {
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
});
builders
}
pub fn main() {
let command = parse_command_line();
let builders = demo_builders();
match command {
Command::Run(demo) => {
@@ -86,7 +95,7 @@ pub fn main() {
{
TestbedApp::from_builders(vec![builders[i]]).run()
} else {
eprintln!("Invalid example to run provided: '{}'", demo);
eprintln!("Invalid example to run provided: '{demo}'");
}
}
Command::RunAll => TestbedApp::from_builders(builders).run(),

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*
@@ -24,21 +24,21 @@ pub fn init_world(testbed: &mut Testbed) {
/*
* Create the cubes
*/
let num = 8;
let num = 10;
let rad = 1.0;
let shift = rad * 2.0 + rad;
let shift = rad * 2.0;
let centerx = shift * (num / 2) as f32;
let centery = shift / 2.0;
let centerz = shift * (num / 2) as f32;
let mut offset = -(num as f32) * (rad * 2.0 + rad) * 0.5;
let mut offset = -(num as f32) * (rad * 2.0) * 0.5;
for j in 0usize..47 {
for j in 0usize..num {
for i in 0..num {
for k in 0usize..num {
let x = i as f32 * shift - centerx + offset;
let y = j as f32 * shift + centery + 3.0;
let y = j as f32 * shift + centery;
let z = k as f32 * shift - centerz + offset;
// Build the rigid body.

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,7 +1,7 @@
use rand::distributions::{Distribution, Standard};
use rand::{rngs::StdRng, SeedableRng};
use rapier3d::prelude::*;
use rand::{SeedableRng, rngs::StdRng};
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,6 +1,6 @@
use rapier_testbed3d::Testbed;
use rapier3d::na::ComplexField;
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn build_block(
testbed: &mut Testbed,
@@ -99,7 +99,6 @@ pub fn init_world(testbed: &mut Testbed) {
// These should only be set to odd values otherwise
// the blocks won't align in the nicest way.
let numy = [0, 9, 13, 17, 21, 41];
let mut num_blocks_built = 0;
for i in (1..=5).rev() {
let numx = i;
@@ -115,11 +114,8 @@ pub fn init_world(testbed: &mut Testbed) {
(numx, numy, numz),
);
block_height += numy as f32 * half_extents.y * 2.0 + half_extents.x * 2.0;
num_blocks_built += numx * numy * numz;
}
println!("Num keva blocks: {}", num_blocks_built);
/*
* Set up the testbed.
*/

View File

@@ -0,0 +1,68 @@
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 = 30;
let rad = 1.0;
let shift = rad * 6.0 + 1.0;
let centerx = shift * (num as f32) / 2.0;
let centery = shift * (num as f32) / 2.0;
let centerz = shift * (num as f32) / 2.0;
for i in 0..num {
for j in 0usize..num {
for k in 0..num {
let x = i as f32 * shift - centerx;
let y = j as f32 * shift - centery;
let z = k as f32 * shift - centerz;
// Build the rigid body.
let velocity = Vector::new(
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
) * 30.0;
let rigid_body = RigidBodyBuilder::new(RigidBodyType::KinematicVelocityBased)
.translation(vector![x, y, z])
.linvel(velocity);
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::ball(rad);
colliders.insert_with_parent(collider, handle, &mut bodies);
}
}
}
testbed.add_callback(move |_, physics, _, _| {
for (_, rb) in physics.bodies.iter_mut() {
let mut linvel = *rb.linvel();
for dim in 0..3 {
if (linvel[dim] > 0.0 && rb.translation()[dim] > (shift * num as f32) / 2.0)
|| (linvel[dim] < 0.0 && rb.translation()[dim] < -(shift * num as f32) / 2.0)
{
linvel[dim] = -linvel[dim];
}
}
rb.set_linvel(linvel, false);
}
});
/*
* 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());
}

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
fn create_pyramid(
bodies: &mut RigidBodySet,

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
fn create_pyramid(
bodies: &mut RigidBodySet,

89
benchmarks3d/ray_cast3.rs Normal file
View File

@@ -0,0 +1,89 @@
use rapier_testbed3d::{Color, Testbed};
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
let settings = testbed.example_settings_mut();
// NOTE: this demo is a bit special. It takes as a setting the builder of another demo,
// builds it, and add a ton of rays into it. This gives us an easy way to check
// ray-casting in a wide variety of situations.
let demos: Vec<_> = crate::demo_builders()
.into_iter()
.filter(|(_, builder)| {
!std::ptr::fn_addr_eq(*builder, self::init_world as fn(&mut Testbed))
})
.collect();
let demo_names: Vec<_> = demos.iter().map(|(name, _)| name.to_string()).collect();
let selected = settings.get_or_set_string("Scene", 0, demo_names);
demos[selected].1(testbed);
/*
* Cast rays at each frame.
*/
let ray_ball_radius = 100.0;
let ray_ball = Ball::new(ray_ball_radius);
let (ray_origins, _) = ray_ball.to_trimesh(100, 100);
let rays: Vec<_> = ray_origins
.into_iter()
.map(|pt| Ray::new(pt, -pt.coords.normalize()))
.collect();
let mut centered_rays = rays.clone();
testbed.add_callback(move |graphics, physics, _, _| {
let Some(graphics) = graphics else {
return;
};
// Re-center the ray relative to the current position of all objects.
// This ensures demos with falling objects dont end up with a boring situation
// where all the rays point into the void.
let mut center = Point::origin();
for (_, b) in physics.bodies.iter() {
center += b.translation();
}
center /= physics.bodies.len() as Real;
for (centered, ray) in centered_rays.iter_mut().zip(rays.iter()) {
centered.origin = center + ray.origin.coords;
}
// Cast the rays.
let t1 = std::time::Instant::now();
let max_toi = ray_ball_radius - 1.0;
let Some(broad_phase) = physics.broad_phase.downcast_ref::<BroadPhaseBvh>() else {
return;
};
let query_pipeline = broad_phase.as_query_pipeline(
physics.narrow_phase.query_dispatcher(),
&physics.bodies,
&physics.colliders,
Default::default(),
);
for ray in &centered_rays {
if let Some((_, toi)) = query_pipeline.cast_ray(ray, max_toi, true) {
let a = ray.origin;
let b = ray.point_at(toi);
graphics
.gizmos
.line(a.into(), b.into(), Color::srgba(0.0, 1.0f32, 0.0, 0.1));
} else {
let a = ray.origin;
let b = ray.point_at(max_toi);
graphics
.gizmos
.line(a.into(), b.into(), Color::srgba(1.0f32, 0.0, 0.0, 0.1));
}
}
let main_check_time = t1.elapsed().as_secs_f32();
if let Some(settings) = &mut graphics.settings {
settings.set_label("Ray count:", format!("{}", rays.len()));
settings.set_label(
"Ray-cast time",
format!("{:.2}ms", main_check_time * 1000.0,),
);
}
});
}

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
fn create_tower_circle(
bodies: &mut RigidBodySet,

View File

@@ -1,6 +1,6 @@
use rapier_testbed3d::Testbed;
use rapier3d::na::ComplexField;
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
pub fn init_world(testbed: &mut Testbed) {
/*