From cc44b65094766aab40561f22431a95877ed5ff11 Mon Sep 17 00:00:00 2001 From: Crozet Sébastien Date: Tue, 27 Oct 2020 16:11:20 +0100 Subject: Added user-implementable traits for collision/proximity pair filtering. --- src/geometry/mod.rs | 2 ++ src/geometry/narrow_phase.rs | 70 ++++++++++++++++++++++++++++++-------- src/geometry/user_callbacks.rs | 60 ++++++++++++++++++++++++++++++++ src/pipeline/collision_pipeline.rs | 23 +++++++++++-- src/pipeline/physics_pipeline.rs | 7 +++- src_testbed/testbed.rs | 6 ++++ 6 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 src/geometry/user_callbacks.rs diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index 9da35d9..c8ad28e 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -22,6 +22,7 @@ pub use self::proximity_detector::{DefaultProximityDispatcher, ProximityDispatch #[cfg(feature = "dim3")] pub use self::round_cylinder::RoundCylinder; pub use self::trimesh::Trimesh; +pub use self::user_callbacks::{ContactPairFilter, PairFilterContext, ProximityPairFilter}; pub use ncollide::query::Proximity; /// A segment shape. @@ -104,3 +105,4 @@ mod polygonal_feature_map; #[cfg(feature = "dim3")] mod round_cylinder; mod shape; +mod user_callbacks; diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index f5517ba..fb5da99 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -14,8 +14,9 @@ use crate::geometry::proximity_detector::{ // proximity_detector::ProximityDetectionContextSimd, WBall, //}; use crate::geometry::{ - BroadPhasePairEvent, ColliderGraphIndex, ColliderHandle, ContactEvent, ProximityEvent, - ProximityPair, RemovedCollider, SolverFlags, + BroadPhasePairEvent, ColliderGraphIndex, ColliderHandle, ContactEvent, ContactPairFilter, + PairFilterContext, ProximityEvent, ProximityPair, ProximityPairFilter, RemovedCollider, + SolverFlags, }; use crate::geometry::{ColliderSet, ContactManifold, ContactPair, InteractionGraph}; //#[cfg(feature = "simd-is-enabled")] @@ -290,6 +291,7 @@ impl NarrowPhase { prediction_distance: f32, bodies: &RigidBodySet, colliders: &ColliderSet, + pair_filter: Option<&dyn ProximityPairFilter>, events: &dyn EventHandler, ) { par_iter_mut!(&mut self.proximity_graph.graph.edges).for_each(|edge| { @@ -301,16 +303,35 @@ impl NarrowPhase { let rb1 = &bodies[co1.parent]; let rb2 = &bodies[co2.parent]; - if (rb1.is_sleeping() || rb1.is_static()) && (rb2.is_sleeping() || rb2.is_static()) { - // No need to update this contact because nothing moved. + if (rb1.is_sleeping() && rb2.is_static()) || (rb2.is_sleeping() && rb1.is_static()) { + // No need to update this proximity because nothing moved. return; } if !co1.collision_groups.test(co2.collision_groups) { - // The collision is not allowed. + // The proximity is not allowed. + return; + } + + if pair_filter.is_none() && !rb1.is_dynamic() && !rb2.is_dynamic() { + // Default filtering rule: no proximity between two non-dynamic bodies. return; } + if let Some(filter) = pair_filter { + let context = PairFilterContext { + collider1: co1, + collider2: co2, + rigid_body1: rb1, + rigid_body2: rb2, + }; + + if !filter.filter_proximity_pair(&context) { + // No proximity allowed. + return; + } + } + let dispatcher = DefaultProximityDispatcher; if pair.detector.is_none() { // We need a redispatch for this detector. @@ -341,6 +362,7 @@ impl NarrowPhase { prediction_distance: f32, bodies: &RigidBodySet, colliders: &ColliderSet, + pair_filter: Option<&dyn ContactPairFilter>, events: &dyn EventHandler, ) { par_iter_mut!(&mut self.contact_graph.graph.edges).for_each(|edge| { @@ -352,9 +374,7 @@ impl NarrowPhase { let rb1 = &bodies[co1.parent]; let rb2 = &bodies[co2.parent]; - if ((rb1.is_sleeping() || rb1.is_static()) && (rb2.is_sleeping() || rb2.is_static())) - || (!rb1.is_dynamic() && !rb2.is_dynamic()) - { + if (rb1.is_sleeping() && rb2.is_static()) || (rb2.is_sleeping() && rb1.is_static()) { // No need to update this contact because nothing moved. return; } @@ -364,6 +384,33 @@ impl NarrowPhase { return; } + if pair_filter.is_none() && !rb1.is_dynamic() && !rb2.is_dynamic() { + // Default filtering rule: no contact between two non-dynamic bodies. + return; + } + + let solver_flags = if let Some(filter) = pair_filter { + let context = PairFilterContext { + collider1: co1, + collider2: co2, + rigid_body1: rb1, + rigid_body2: rb2, + }; + + if let Some(solver_flags) = filter.filter_contact_pair(&context) { + solver_flags + } else { + // No contact allowed. + return; + } + } else { + if co1.solver_groups.test(co2.solver_groups) { + SolverFlags::COMPUTE_FORCES + } else { + SolverFlags::empty() + } + }; + let dispatcher = DefaultContactDispatcher; if pair.generator.is_none() { // We need a redispatch for this generator. @@ -374,12 +421,6 @@ impl NarrowPhase { pair.generator_workspace = workspace; } - let solver_flags = if co1.solver_groups.test(co2.solver_groups) { - SolverFlags::COMPUTE_FORCES - } else { - SolverFlags::empty() - }; - let context = ContactGenerationContext { dispatcher: &dispatcher, prediction_distance, @@ -415,6 +456,7 @@ impl NarrowPhase { let rb2 = &bodies[manifold.body_pair.body2]; if manifold.solver_flags.contains(SolverFlags::COMPUTE_FORCES) && manifold.num_active_contacts() != 0 + && (rb1.is_dynamic() || rb2.is_dynamic()) && (!rb1.is_dynamic() || !rb1.is_sleeping()) && (!rb2.is_dynamic() || !rb2.is_sleeping()) { diff --git a/src/geometry/user_callbacks.rs b/src/geometry/user_callbacks.rs new file mode 100644 index 0000000..3602faf --- /dev/null +++ b/src/geometry/user_callbacks.rs @@ -0,0 +1,60 @@ +use crate::dynamics::RigidBody; +use crate::geometry::{Collider, SolverFlags}; + +/// Context given to custom collision filters to filter-out collisions. +pub struct PairFilterContext<'a> { + /// The first collider involved in the potential collision. + pub collider1: &'a Collider, + /// The first collider involved in the potential collision. + pub collider2: &'a Collider, + /// The first collider involved in the potential collision. + pub rigid_body1: &'a RigidBody, + /// The first collider involved in the potential collision. + pub rigid_body2: &'a RigidBody, +} + +/// User-defined filter for potential contact pairs detected by the broad-phase. +/// +/// This can be used to apply custom logic in order to decide whether two colliders +/// should have their contact computed by the narrow-phase, and if these contact +/// should be solved by the constraints solver +pub trait ContactPairFilter: Send + Sync { + /// Applies the contact pair filter. + /// + /// Note that using a contact pair filter will replace the default contact filtering + /// which consists of preventing contact computation between two non-dynamic bodies. + /// + /// Note that using a contact pair filter will replace the default determination + /// of solver flags, based on the colliders solver groups. + /// + /// This filtering method is called after taking into account the colliders collision groups. + /// + /// If this returns `None`, then the narrow-phase will ignore this contact pair and + /// not compute any contact manifolds for it. + /// If this returns `Some`, then the narrow-phase will compute contact manifolds for + /// this pair of colliders, and configure them with the returned solver flags. For + /// example, if this returns `Some(SolverFlags::COMPUTE_FORCES)` then the contacts + /// will be taken into account by the constraints solver. If this returns + /// `Some(SolverFlags::empty())` then the constraints solver will ignore these + /// contacts. + fn filter_contact_pair(&self, context: &PairFilterContext) -> Option; +} + +/// User-defined filter for potential proximity pairs detected by the broad-phase. +/// +/// This can be used to apply custom logic in order to decide whether two colliders +/// should have their proximity computed by the narrow-phase. +pub trait ProximityPairFilter: Send + Sync { + /// Applies the proximity pair filter. + /// + /// Note that using a proximity pair filter will replace the default proximity filtering + /// which consists of preventing proximity computation between two non-dynamic bodies. + /// + /// This filtering method is called after taking into account the colliders collision groups. + /// + /// If this returns `false`, then the narrow-phase will ignore this pair and + /// not compute any proximity information for it. + /// If this return `true` then the narrow-phase will compute proximity + /// information for this pair. + fn filter_proximity_pair(&self, context: &PairFilterContext) -> bool; +} diff --git a/src/pipeline/collision_pipeline.rs b/src/pipeline/collision_pipeline.rs index 5a19e52..b8896e8 100644 --- a/src/pipeline/collision_pipeline.rs +++ b/src/pipeline/collision_pipeline.rs @@ -1,7 +1,10 @@ //! Physics pipeline structures. use crate::dynamics::{JointSet, RigidBodySet}; -use crate::geometry::{BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, NarrowPhase}; +use crate::geometry::{ + BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactPairFilter, NarrowPhase, + ProximityPairFilter, +}; use crate::pipeline::EventHandler; /// The collision pipeline, responsible for performing collision detection between colliders. @@ -40,6 +43,8 @@ impl CollisionPipeline { narrow_phase: &mut NarrowPhase, bodies: &mut RigidBodySet, colliders: &mut ColliderSet, + contact_pair_filter: Option<&dyn ContactPairFilter>, + proximity_pair_filter: Option<&dyn ProximityPairFilter>, events: &dyn EventHandler, ) { bodies.maintain_active_set(); @@ -52,8 +57,20 @@ impl CollisionPipeline { narrow_phase.register_pairs(colliders, bodies, &self.broad_phase_events, events); - narrow_phase.compute_contacts(prediction_distance, bodies, colliders, events); - narrow_phase.compute_proximities(prediction_distance, bodies, colliders, events); + narrow_phase.compute_contacts( + prediction_distance, + bodies, + colliders, + contact_pair_filter, + events, + ); + narrow_phase.compute_proximities( + prediction_distance, + bodies, + colliders, + proximity_pair_filter, + events, + ); bodies.update_active_set_with_contacts( colliders, diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index 47fd260..b99934b 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -7,7 +7,8 @@ use crate::dynamics::{IntegrationParameters, JointSet, RigidBodySet}; #[cfg(feature = "parallel")] use crate::dynamics::{JointGraphEdge, ParallelIslandSolver as IslandSolver}; use crate::geometry::{ - BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactManifoldIndex, NarrowPhase, + BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactManifoldIndex, + ContactPairFilter, NarrowPhase, ProximityPairFilter, }; use crate::math::Vector; use crate::pipeline::EventHandler; @@ -68,6 +69,8 @@ impl PhysicsPipeline { bodies: &mut RigidBodySet, colliders: &mut ColliderSet, joints: &mut JointSet, + contact_pair_filter: Option<&dyn ContactPairFilter>, + proximity_pair_filter: Option<&dyn ProximityPairFilter>, events: &dyn EventHandler, ) { self.counters.step_started(); @@ -112,12 +115,14 @@ impl PhysicsPipeline { integration_parameters.prediction_distance, bodies, colliders, + contact_pair_filter, events, ); narrow_phase.compute_proximities( integration_parameters.prediction_distance, bodies, colliders, + proximity_pair_filter, events, ); // println!("Compute contact time: {}", instant::now() - t); diff --git a/src_testbed/testbed.rs b/src_testbed/testbed.rs index 456b894..3bf720a 100644 --- a/src_testbed/testbed.rs +++ b/src_testbed/testbed.rs @@ -681,6 +681,8 @@ impl Testbed { &mut self.physics.bodies, &mut self.physics.colliders, &mut self.physics.joints, + None, + None, &self.event_handler, ); @@ -1457,6 +1459,8 @@ impl State for Testbed { &mut physics.bodies, &mut physics.colliders, &mut physics.joints, + None, + None, event_handler, ); }); @@ -1471,6 +1475,8 @@ impl State for Testbed { &mut self.physics.bodies, &mut self.physics.colliders, &mut self.physics.joints, + None, + None, &self.event_handler, ); -- cgit From 24bd97636e890195c8a72f8e265809bbae44ab13 Mon Sep 17 00:00:00 2001 From: Crozet Sébastien Date: Tue, 27 Oct 2020 16:21:33 +0100 Subject: Rename SolverFlags::COMPUTE_FORCES to SolverFlags::COMPUTE_IMPULSES. This is closer to what the solver actually does. --- src/geometry/contact.rs | 2 +- src/geometry/narrow_phase.rs | 6 ++++-- src/geometry/user_callbacks.rs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/geometry/contact.rs b/src/geometry/contact.rs index 1f50e43..d8f3632 100644 --- a/src/geometry/contact.rs +++ b/src/geometry/contact.rs @@ -15,7 +15,7 @@ bitflags::bitflags! { pub struct SolverFlags: u32 { /// The constraint solver will take this contact manifold into /// account for force computation. - const COMPUTE_FORCES = 0b01; + const COMPUTE_IMPULSES = 0b01; } } diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index fb5da99..fd2652d 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -405,7 +405,7 @@ impl NarrowPhase { } } else { if co1.solver_groups.test(co2.solver_groups) { - SolverFlags::COMPUTE_FORCES + SolverFlags::COMPUTE_IMPULSES } else { SolverFlags::empty() } @@ -454,7 +454,9 @@ impl NarrowPhase { for manifold in &mut inter.weight.manifolds { let rb1 = &bodies[manifold.body_pair.body1]; let rb2 = &bodies[manifold.body_pair.body2]; - if manifold.solver_flags.contains(SolverFlags::COMPUTE_FORCES) + if manifold + .solver_flags + .contains(SolverFlags::COMPUTE_IMPULSES) && manifold.num_active_contacts() != 0 && (rb1.is_dynamic() || rb2.is_dynamic()) && (!rb1.is_dynamic() || !rb1.is_sleeping()) diff --git a/src/geometry/user_callbacks.rs b/src/geometry/user_callbacks.rs index 3602faf..9b36695 100644 --- a/src/geometry/user_callbacks.rs +++ b/src/geometry/user_callbacks.rs @@ -33,7 +33,7 @@ pub trait ContactPairFilter: Send + Sync { /// not compute any contact manifolds for it. /// If this returns `Some`, then the narrow-phase will compute contact manifolds for /// this pair of colliders, and configure them with the returned solver flags. For - /// example, if this returns `Some(SolverFlags::COMPUTE_FORCES)` then the contacts + /// example, if this returns `Some(SolverFlags::COMPUTE_IMPULSES)` then the contacts /// will be taken into account by the constraints solver. If this returns /// `Some(SolverFlags::empty())` then the constraints solver will ignore these /// contacts. -- cgit From 3bfa4079999f6c886e692de256abf51e4506a2b1 Mon Sep 17 00:00:00 2001 From: Crozet Sébastien Date: Tue, 27 Oct 2020 16:48:05 +0100 Subject: ContactPairFilter: don't overwrite the effect of the solver groups. This is more consistent with the fact that the effect of collision groups is not overwritten either. --- src/geometry/narrow_phase.rs | 20 ++++++++++---------- src/geometry/user_callbacks.rs | 11 ++++------- src/pipeline/physics_pipeline.rs | 4 ++++ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index fd2652d..69678cd 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -320,10 +320,10 @@ impl NarrowPhase { if let Some(filter) = pair_filter { let context = PairFilterContext { - collider1: co1, - collider2: co2, rigid_body1: rb1, rigid_body2: rb2, + collider1: co1, + collider2: co2, }; if !filter.filter_proximity_pair(&context) { @@ -389,12 +389,12 @@ impl NarrowPhase { return; } - let solver_flags = if let Some(filter) = pair_filter { + let mut solver_flags = if let Some(filter) = pair_filter { let context = PairFilterContext { - collider1: co1, - collider2: co2, rigid_body1: rb1, rigid_body2: rb2, + collider1: co1, + collider2: co2, }; if let Some(solver_flags) = filter.filter_contact_pair(&context) { @@ -404,13 +404,13 @@ impl NarrowPhase { return; } } else { - if co1.solver_groups.test(co2.solver_groups) { - SolverFlags::COMPUTE_IMPULSES - } else { - SolverFlags::empty() - } + SolverFlags::COMPUTE_IMPULSES }; + if !co1.solver_groups.test(co2.solver_groups) { + solver_flags.remove(SolverFlags::COMPUTE_IMPULSES); + } + let dispatcher = DefaultContactDispatcher; if pair.generator.is_none() { // We need a redispatch for this generator. diff --git a/src/geometry/user_callbacks.rs b/src/geometry/user_callbacks.rs index 9b36695..ae0119f 100644 --- a/src/geometry/user_callbacks.rs +++ b/src/geometry/user_callbacks.rs @@ -3,14 +3,14 @@ use crate::geometry::{Collider, SolverFlags}; /// Context given to custom collision filters to filter-out collisions. pub struct PairFilterContext<'a> { - /// The first collider involved in the potential collision. - pub collider1: &'a Collider, - /// The first collider involved in the potential collision. - pub collider2: &'a Collider, /// The first collider involved in the potential collision. pub rigid_body1: &'a RigidBody, /// The first collider involved in the potential collision. pub rigid_body2: &'a RigidBody, + /// The first collider involved in the potential collision. + pub collider1: &'a Collider, + /// The first collider involved in the potential collision. + pub collider2: &'a Collider, } /// User-defined filter for potential contact pairs detected by the broad-phase. @@ -24,9 +24,6 @@ pub trait ContactPairFilter: Send + Sync { /// Note that using a contact pair filter will replace the default contact filtering /// which consists of preventing contact computation between two non-dynamic bodies. /// - /// Note that using a contact pair filter will replace the default determination - /// of solver flags, based on the colliders solver groups. - /// /// This filtering method is called after taking into account the colliders collision groups. /// /// If this returns `None`, then the narrow-phase will ignore this contact pair and diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index b99934b..0720ff1 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -290,6 +290,8 @@ mod test { &mut bodies, &mut colliders, &mut joints, + None, + None, &(), ); } @@ -332,6 +334,8 @@ mod test { &mut bodies, &mut colliders, &mut joints, + None, + None, &(), ); } -- cgit From 74f0297221607e1929db75e79089d7cb75558dfe Mon Sep 17 00:00:00 2001 From: Crozet Sébastien Date: Tue, 27 Oct 2020 17:14:13 +0100 Subject: Fix performance regression due to sleeping objects pairs no longer being ignored by the narrow-phase. --- src/geometry/narrow_phase.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index 69678cd..c1bd411 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -303,7 +303,10 @@ impl NarrowPhase { let rb1 = &bodies[co1.parent]; let rb2 = &bodies[co2.parent]; - if (rb1.is_sleeping() && rb2.is_static()) || (rb2.is_sleeping() && rb1.is_static()) { + if (rb1.is_sleeping() && rb2.is_static()) + || (rb2.is_sleeping() && rb1.is_static()) + || (rb1.is_sleeping() && rb2.is_sleeping()) + { // No need to update this proximity because nothing moved. return; } @@ -374,7 +377,10 @@ impl NarrowPhase { let rb1 = &bodies[co1.parent]; let rb2 = &bodies[co2.parent]; - if (rb1.is_sleeping() && rb2.is_static()) || (rb2.is_sleeping() && rb1.is_static()) { + if (rb1.is_sleeping() && rb2.is_static()) + || (rb2.is_sleeping() && rb1.is_static()) + || (rb1.is_sleeping() && rb2.is_sleeping()) + { // No need to update this contact because nothing moved. return; } -- cgit