feat: persistent islands + manifold reduction (#895)

* feat: initial implementation of contact manifold reduction

* feat: try bepu-like manifold reduction

* feat: simplification of the constraints counting and indexing logic

* feat: add concept of incremental islands with a single awake island

More islands manager fixes

* feat: start adding support for multiple awake islands

* feat: add more timings

* feat: implement incremental island split & merge

* chore: refactor islands manager into multiple files

* chore: refactor manifold reduction to its own file + add naive reduction method

* feat: add islands manager validation checks

* fix various bugs in the new islands system

* chore: remove redundant active_set_offset field
This commit is contained in:
Sébastien Crozet
2026-01-09 17:04:02 +01:00
committed by GitHub
parent 134132900a
commit 48de83817e
40 changed files with 2099 additions and 1114 deletions

View File

@@ -1,5 +1,6 @@
#![allow(dead_code)]
use crate::ui::egui::emath::OrderedFloat;
use na::{Isometry3, Matrix4, Point3, Quaternion, Translation3, Unit, UnitQuaternion, Vector3};
use physx::cooking::{
ConvexMeshCookingResult, PxConvexMeshDesc, PxCookingParams, PxHeightFieldDesc,
@@ -133,7 +134,7 @@ pub static FOUNDATION: std::cell::RefCell<PxPhysicsFoundation> = std::cell::RefC
pub struct PhysxWorld {
// physics: Physics,
materials: Vec<Owner<PxMaterial>>,
// materials: Vec<Owner<PxMaterial>>,
shapes: Vec<Owner<PxShape>>,
scene: Option<Owner<PxScene>>,
}
@@ -141,7 +142,7 @@ pub struct PhysxWorld {
impl Drop for PhysxWorld {
fn drop(&mut self) {
let scene = self.scene.take();
// FIXME: we get a segfault if we don't forget the scene.
// FIXME: we get a segfault if we don't leak the scene.
std::mem::forget(scene);
}
}
@@ -161,7 +162,6 @@ impl PhysxWorld {
FOUNDATION.with(|physics| {
let mut physics = physics.borrow_mut();
let mut shapes = Vec::new();
let mut materials = Vec::new();
let friction_type = if use_two_friction_directions {
FrictionType::TwoDirectional
@@ -300,10 +300,13 @@ impl PhysxWorld {
* Colliders
*
*/
let mut materials_cache = HashMap::new();
for (_, collider) in colliders.iter() {
if let Some((mut px_shape, px_material, collider_pos)) =
physx_collider_from_rapier_collider(&mut *physics, &collider)
{
if let Some((mut px_shape, collider_pos)) = physx_collider_from_rapier_collider(
&mut *physics,
&collider,
&mut materials_cache,
) {
if let Some(parent_handle) = collider.parent() {
let parent_body = &bodies[parent_handle];
@@ -331,7 +334,6 @@ impl PhysxWorld {
}
shapes.push(px_shape);
materials.push(px_material);
}
}
}
@@ -396,7 +398,6 @@ impl PhysxWorld {
Self {
scene: Some(scene),
shapes,
materials,
}
})
}
@@ -557,7 +558,8 @@ impl PhysxWorld {
fn physx_collider_from_rapier_collider(
physics: &mut PxPhysicsFoundation,
collider: &Collider,
) -> Option<(Owner<PxShape>, Owner<PxMaterial>, Isometry3<f32>)> {
materials_cache: &mut HashMap<[OrderedFloat<f32>; 2], Owner<PxMaterial>>,
) -> Option<(Owner<PxShape>, Isometry3<f32>)> {
let mut local_pose = collider.position_wrt_parent().copied().unwrap_or(na::one());
let cooking_params = PxCookingParams::new(physics).unwrap();
let shape = collider.shape();
@@ -566,14 +568,22 @@ fn physx_collider_from_rapier_collider(
} else {
ShapeFlags::SimulationShape
};
let mut material = physics
.create_material(
collider.material().friction,
collider.material().friction,
collider.material().restitution,
(),
)
.unwrap();
let material = materials_cache
.entry([
OrderedFloat(collider.material().friction),
OrderedFloat(collider.material().restitution),
])
.or_insert_with(|| {
physics
.create_material(
collider.material().friction,
collider.material().friction,
collider.material().restitution,
(),
)
.unwrap()
});
let materials = &mut [material.as_mut()][..];
let shape = if let Some(cuboid) = shape.as_cuboid() {
@@ -705,7 +715,7 @@ fn physx_collider_from_rapier_collider(
return None;
};
shape.map(|s| (s, material, local_pose))
shape.map(|s| (s, local_pose))
}
type PxPhysicsFoundation = PhysicsFoundation<DefaultAllocator, PxShape>;

View File

@@ -320,6 +320,10 @@ fn profiling_ui(ui: &mut Ui, counters: &Counters) {
"Broad-phase: {:.2}ms",
counters.broad_phase_time_ms()
));
ui.label(format!(
"Final broad-phase: {:.2}ms",
counters.cd.final_broad_phase_time.time_ms()
));
ui.label(format!(
"Narrow-phase: {:.2}ms",
counters.narrow_phase_time_ms()
@@ -332,6 +336,20 @@ fn profiling_ui(ui: &mut Ui, counters: &Counters) {
"Velocity assembly: {:.2}ms",
counters.solver.velocity_assembly_time.time_ms()
));
ui.label(format!(
"> Velocity assembly - solver bodies: {:.2}ms",
counters
.solver
.velocity_assembly_time_solver_bodies
.time_ms()
));
ui.label(format!(
"> Velocity assembly - constraints init: {:.2}ms",
counters
.solver
.velocity_assembly_time_constraints_init
.time_ms()
));
ui.label(format!(
"Velocity resolution: {:.2}ms",
counters.velocity_resolution_time_ms()
@@ -370,10 +388,16 @@ fn profiling_ui(ui: &mut Ui, counters: &Counters) {
"Island computation: {:.2}ms",
counters.island_construction_time_ms()
));
ui.label(format!(
"Active constraints collection: {:.2}ms",
counters.stages.island_constraints_collection_time.time_ms()
));
ui.label(format!("Mprops update: {:.2}ms", counters.update_time_ms()));
ui.label(format!(
"User changes: {:.2}ms",
counters.stages.user_changes.time_ms()
));
ui.label(format!("Debug timer: {:.2}ms", counters.custom.time_ms()));
});
}