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-examples-3d"
version = "0.1.0"
authors = ["Sébastien Crozet <sebcrozet@dimforge.com>"]
edition = "2021"
edition = "2024"
default-run = "all_examples3"
[features]

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
fn create_wall(
testbed: &mut Testbed,

View File

@@ -1,9 +1,9 @@
use crate::utils::character::{self, CharacterControlMode};
use rapier_testbed3d::Testbed;
use rapier3d::{
control::{KinematicCharacterController, PidController},
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,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,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_ball_articulations(
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::*;
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) {
/*
@@ -86,7 +86,10 @@ pub fn init_world(testbed: &mut Testbed) {
let big_mass =
MassProperties::from_cuboid(1.0, vector![cube_len / 2.0, cube_len / 2.0, cube_len / 2.0])
.mass();
println!("debug_cube_high_mass_ratio3: small stick mass: {small_mass}, big cube mass: {big_mass}, mass_ratio: {}", big_mass / small_mass);
println!(
"debug_cube_high_mass_ratio3: small stick mass: {small_mass}, big cube mass: {big_mass}, mass_ratio: {}",
big_mass / small_mass
);
/*
* Set up the testbed.

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
// This shows a bug when a cylinder is in contact with a very large
// but very thin cuboid. In this case the EPA returns an incorrect

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
#[derive(serde::Deserialize)]
struct PhysicsState {
@@ -15,38 +15,52 @@ struct PhysicsState {
}
pub fn init_world(testbed: &mut Testbed) {
/*
* Set up the testbed.
*/
let path = "state.bin";
let bytes = match std::fs::read(path) {
// Deserialize
let setting = testbed.example_settings_mut();
let frame_id = setting.get_or_set_u32("frame", 0, 0..=1400);
let frame_dirs = "/Users/sebcrozet/work/hytopia/sdk/examples/bug-demo";
let path = format!("{frame_dirs}/snapshot{frame_id}.bincode");
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(err) => {
println!(
"Failed to open the serialzed scene file {:?}: {}",
path, err
);
println!("Failed to open the serialized scene file {path:?}: {err}");
return;
}
};
match bincode::deserialize(&bytes) {
Ok(state) => {
let state: PhysicsState = state;
println!("World state deserialized successfully:");
println!("\tgravity: {:?}", state.gravity);
println!(
"\tintegration parameters: {:?}",
state.integration_parameters
);
println!("\tbodies: {:?}", state.bodies.len());
println!("\tcolliders: {:?}", state.colliders.len());
println!("\timpulse_joints: {:?}", state.impulse_joints.len());
for (_, rb) in state.bodies.iter() {
if rb.linvel().norm() != 0.0 {
println!("\tlinvel: {:?}", rb.linvel());
}
}
testbed.set_world(
state.bodies,
state.colliders,
state.impulse_joints,
state.multibody_joints,
);
testbed.harness_mut().physics.islands = state.islands;
testbed.harness_mut().physics.broad_phase = state.broad_phase;
testbed.harness_mut().physics.broad_phase = Box::new(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;
testbed.set_graphics_shift(vector![-541.0, -6377257.0, -61.0]);
testbed.look_at(point![10.0, 10.0, 10.0], point![0.0, 0.0, 0.0]);
}
Err(err) => println!("Failed to deserialize the world state: {}", err),
Err(err) => println!("Failed to deserialize the world state: {err}"),
}
}

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 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) {
let mut bodies = RigidBodySet::new();

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 prismatic_repro(
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::*;
// This shows a bug when a cylinder is in contact with a very large
// but very thin cuboid. In this case the EPA returns an incorrect

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 obj::raw::object::Polygon;
use rapier_testbed3d::Testbed;
use rapier3d::parry::bounding_volume;
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use std::fs::File;
use std::io::BufReader;
@@ -54,7 +54,7 @@ pub fn do_init_world(testbed: &mut Testbed, use_convex_decomposition: bool) {
let deltas = Isometry::identity();
let mut shapes = Vec::new();
println!("Parsing and decomposing: {}", obj_path);
println!("Parsing and decomposing: {obj_path}");
let input = BufReader::new(File::open(obj_path).unwrap());
if let Ok(model) = obj::raw::parse_obj(input) {
@@ -75,7 +75,8 @@ pub fn do_init_world(testbed: &mut Testbed, use_convex_decomposition: bool) {
.collect();
// Compute the size of the model, to scale it and have similar size for everything.
let aabb = bounding_volume::details::point_cloud_aabb(&deltas, &vertices);
let aabb =
bounding_volume::details::point_cloud_aabb(&deltas, vertices.iter().copied());
let center = aabb.center();
let diag = (aabb.maxs - aabb.mins).norm();

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
const MAX_NUMBER_OF_BODIES: usize = 400;
@@ -17,10 +17,13 @@ pub fn init_world(testbed: &mut Testbed) {
let ground_size = 100.1;
let ground_height = 2.1; // 16.0;
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
colliders.insert_with_parent(collider, handle, &mut bodies);
for k in 0..3 {
let rigid_body =
RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height - k as f32, 0.0]);
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
colliders.insert_with_parent(collider, handle, &mut bodies);
}
// Callback that will be executed on the main loop to handle proximities.
testbed.add_callback(move |mut graphics, physics, _, run_state| {
@@ -55,7 +58,7 @@ pub fn init_world(testbed: &mut Testbed) {
.reverse()
});
let num_to_remove = to_remove.len() - MAX_NUMBER_OF_BODIES;
let num_to_remove = to_remove.len().saturating_sub(MAX_NUMBER_OF_BODIES);
for (handle, _) in &to_remove[..num_to_remove] {
physics.bodies.remove(
*handle,

View File

@@ -1,5 +1,5 @@
use rapier_testbed3d::harness::{Harness, RapierBroadPhaseType};
use rapier3d::prelude::*;
use rapier_testbed3d::harness::Harness;
pub fn init_world(harness: &mut Harness) {
/*
@@ -56,7 +56,13 @@ pub fn init_world(harness: &mut Harness) {
/*
* Set up the harness.
*/
harness.set_world(bodies, colliders, impulse_joints, multibody_joints);
harness.set_world(
bodies,
colliders,
impulse_joints,
multibody_joints,
RapierBroadPhaseType::default(),
);
}
fn main() {

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::*;
fn create_coupled_joints(
bodies: &mut RigidBodySet,

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
pub fn build_block(
testbed: &mut Testbed,
@@ -118,7 +118,7 @@ pub fn init_world(testbed: &mut Testbed) {
num_blocks_built += numx * numy * numz;
}
println!("Num keva blocks: {}", num_blocks_built);
println!("Num keva blocks: {num_blocks_built}");
/*
* Set up the testbed.

View File

@@ -1,5 +1,5 @@
use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
// This shows a bug when a cylinder is in contact with a very large
// but very thin cuboid. In this case the EPA returns an incorrect

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::*;
struct OneWayPlatformHook {
platform1: ColliderHandle,

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 crate::utils::character::{self, CharacterControlMode};
use rapier_testbed3d::Testbed;
use rapier3d::control::{KinematicCharacterController, PidController};
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,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,6 +1,6 @@
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;
use rapier3d_urdf::{UrdfLoaderOptions, UrdfMultibodyOptions, UrdfRobot};
use rapier_testbed3d::Testbed;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,11 +1,11 @@
use rapier_testbed3d::{
KeyCode, PhysicsState, TestbedGraphics,
ui::egui::{Align2, ComboBox, Slider, Ui, Window},
};
use rapier3d::{
control::{CharacterLength, KinematicCharacterController, PidController},
prelude::*,
};
use rapier_testbed3d::{
ui::egui::{Align2, ComboBox, Slider, Ui, Window},
KeyCode, PhysicsState, TestbedGraphics,
};
pub type CharacterSpeed = Real;
@@ -143,18 +143,27 @@ fn update_kinematic_controller(
let character_body = &phx.bodies[character_handle];
let character_collider = &phx.colliders[character_body.colliders()[0]];
let character_pose = *character_collider.position();
let character_shape = character_collider.shared_shape().clone();
let character_mass = character_body.mass();
let Some(broad_phase) = phx.broad_phase.downcast_ref::<BroadPhaseBvh>() else {
return;
};
let query_pipeline = broad_phase.as_query_pipeline_mut(
phx.narrow_phase.query_dispatcher(),
&mut phx.bodies,
&mut phx.colliders,
QueryFilter::new().exclude_rigid_body(character_handle),
);
let mut collisions = vec![];
let mvt = controller.move_shape(
phx.integration_parameters.dt,
&phx.bodies,
&phx.colliders,
&phx.query_pipeline,
character_collider.shape(),
character_collider.position(),
&query_pipeline.as_ref(),
&*character_shape,
&character_pose,
desired_movement.cast::<Real>(),
QueryFilter::new().exclude_rigid_body(character_handle),
|c| collisions.push(c),
);
@@ -166,13 +175,10 @@ fn update_kinematic_controller(
controller.solve_character_collision_impulses(
phx.integration_parameters.dt,
&mut phx.bodies,
&phx.colliders,
&phx.query_pipeline,
character_collider.shape(),
query_pipeline,
&*character_shape,
character_mass,
&*collisions,
QueryFilter::new().exclude_rigid_body(character_handle),
);
let character_body = &mut phx.bodies[character_handle];

View File

@@ -1,6 +1,6 @@
use rapier_testbed3d::Testbed;
use rapier3d::control::{DynamicRayCastVehicleController, WheelTuning};
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::{KeyCode, Testbed};
use rapier3d::prelude::*;
pub fn init_world(testbed: &mut Testbed) {
/*

View File

@@ -1,9 +1,9 @@
use obj::raw::object::Polygon;
use rapier_testbed3d::KeyCode;
use rapier_testbed3d::Testbed;
use rapier3d::parry::bounding_volume;
use rapier3d::parry::transformation::voxelization::FillMode;
use rapier3d::prelude::*;
use rapier_testbed3d::KeyCode;
use rapier_testbed3d::Testbed;
use std::fs::File;
use std::io::BufReader;
@@ -60,7 +60,7 @@ pub fn init_world(testbed: &mut Testbed) {
let deltas = Isometry::identity();
let mut shapes = Vec::new();
println!("Parsing and decomposing: {}", obj_path);
println!("Parsing and decomposing: {obj_path}");
let input = BufReader::new(File::open(obj_path).unwrap());
@@ -88,7 +88,8 @@ pub fn init_world(testbed: &mut Testbed) {
.collect();
// Compute the size of the model, to scale it and have similar size for everything.
let aabb = bounding_volume::details::point_cloud_aabb(&deltas, &vertices);
let aabb =
bounding_volume::details::point_cloud_aabb(&deltas, vertices.iter().copied());
let center = aabb.center();
let diag = (aabb.maxs - aabb.mins).norm();
@@ -210,14 +211,17 @@ pub fn init_world(testbed: &mut Testbed) {
predicate: Some(&|_, co: &Collider| co.shape().as_voxels().is_some()),
..Default::default()
};
if let Some((handle, hit)) = physics.query_pipeline.cast_ray_and_get_normal(
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,
&ray,
Real::MAX,
true,
filter,
) {
);
if let Some((handle, hit)) = query_pipeline.cast_ray_and_get_normal(&ray, Real::MAX, true) {
// Highlight the voxel.
let hit_collider = &physics.colliders[handle];
let hit_local_normal = hit_collider