Introduce the PhysicsHook trait used for both contact filtering and contact modification.

This commit is contained in:
Crozet Sébastien
2021-02-23 11:24:54 +01:00
parent ad5c10672e
commit 00706e8b36
8 changed files with 160 additions and 58 deletions

View File

@@ -23,16 +23,14 @@ pub fn init_world(testbed: &mut Testbed) {
let handle = bodies.insert(rigid_body); let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size).build(); let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size).build();
colliders.insert(collider, handle, &mut bodies); colliders.insert(collider, handle, &mut bodies);
let mut k = 0;
// Callback that will be executed on the main loop to handle proximities. // Callback that will be executed on the main loop to handle proximities.
testbed.add_callback(move |mut window, mut graphics, physics, _, _| { testbed.add_callback(move |mut window, mut graphics, physics, _, run_state| {
k += 1;
let rigid_body = RigidBodyBuilder::new_dynamic() let rigid_body = RigidBodyBuilder::new_dynamic()
.translation(0.0, 10.0, 0.0) .translation(0.0, 10.0, 0.0)
.build(); .build();
let handle = physics.bodies.insert(rigid_body); let handle = physics.bodies.insert(rigid_body);
let collider = match k % 3 { let collider = match run_state.timestep_id % 3 {
0 => ColliderBuilder::round_cylinder(rad, rad, rad / 10.0).build(), 0 => ColliderBuilder::round_cylinder(rad, rad, rad / 10.0).build(),
1 => ColliderBuilder::cone(rad, rad).build(), 1 => ColliderBuilder::cone(rad, rad).build(),
_ => ColliderBuilder::cuboid(rad, rad, rad).build(), _ => ColliderBuilder::cuboid(rad, rad, rad).build(),

View File

@@ -9,7 +9,10 @@ bitflags::bitflags! {
pub struct SolverFlags: u32 { pub struct SolverFlags: u32 {
/// The constraint solver will take this contact manifold into /// The constraint solver will take this contact manifold into
/// account for force computation. /// account for force computation.
const COMPUTE_IMPULSES = 0b01; const COMPUTE_IMPULSES = 0b001;
/// The user-defined physics hooks will be used to
/// modify the solver contacts of this contact manifold.
const MODIFY_SOLVER_CONTACTS = 0b010;
} }
} }
@@ -104,6 +107,8 @@ pub struct ContactManifoldData {
/// The contacts that will be seen by the constraints solver for computing forces. /// The contacts that will be seen by the constraints solver for computing forces.
#[cfg_attr(feature = "serde-serialize", serde(skip))] #[cfg_attr(feature = "serde-serialize", serde(skip))]
pub solver_contacts: Vec<SolverContact>, pub solver_contacts: Vec<SolverContact>,
/// A user-defined piece of data.
pub user_data: u32,
} }
/// A contact seen by the constraints solver for computing forces. /// A contact seen by the constraints solver for computing forces.
@@ -165,6 +170,7 @@ impl ContactManifoldData {
solver_flags, solver_flags,
normal: Vector::zeros(), normal: Vector::zeros(),
solver_contacts: Vec::new(), solver_contacts: Vec::new(),
user_data: 0,
} }
} }
@@ -205,9 +211,3 @@ impl ContactManifoldData {
// manifold.data.warmstart_multiplier = Self::min_warmstart_multiplier() // manifold.data.warmstart_multiplier = Self::min_warmstart_multiplier()
// } // }
} }
/// A contact manifold that can be modified by the user.
pub struct ModifiableContactManifold<'a> {
manifold: &'a super::ContactManifold,
solver_contacts: &'a mut Vec<SolverContact>,
}

View File

@@ -10,7 +10,7 @@ pub use self::interaction_graph::{
}; };
pub use self::interaction_groups::InteractionGroups; pub use self::interaction_groups::InteractionGroups;
pub use self::narrow_phase::NarrowPhase; pub use self::narrow_phase::NarrowPhase;
pub use self::pair_filter::{ContactPairFilter, IntersectionPairFilter, PairFilterContext}; pub use self::pair_filter::{PairFilterContext, PhysicsHooks};
pub use parry::query::TrackedContact; pub use parry::query::TrackedContact;

View File

@@ -4,10 +4,11 @@ use rayon::prelude::*;
use crate::data::pubsub::Subscription; use crate::data::pubsub::Subscription;
use crate::data::Coarena; use crate::data::Coarena;
use crate::dynamics::{BodyPair, CoefficientCombineRule, RigidBodySet}; use crate::dynamics::{BodyPair, CoefficientCombineRule, RigidBodySet};
use crate::geometry::pair_filter::{ContactModificationContext, PhysicsHooksFlags};
use crate::geometry::{ use crate::geometry::{
BroadPhasePairEvent, ColliderGraphIndex, ColliderHandle, ContactData, ContactEvent, BroadPhasePairEvent, ColliderGraphIndex, ColliderHandle, ContactData, ContactEvent,
ContactManifoldData, ContactPairFilter, IntersectionEvent, IntersectionPairFilter, ContactManifoldData, IntersectionEvent, PairFilterContext, PhysicsHooks, RemovedCollider,
PairFilterContext, RemovedCollider, SolverContact, SolverFlags, SolverContact, SolverFlags,
}; };
use crate::geometry::{ColliderSet, ContactManifold, ContactPair, InteractionGraph}; use crate::geometry::{ColliderSet, ContactManifold, ContactPair, InteractionGraph};
use crate::math::{Real, Vector}; use crate::math::{Real, Vector};
@@ -387,11 +388,13 @@ impl NarrowPhase {
&mut self, &mut self,
bodies: &RigidBodySet, bodies: &RigidBodySet,
colliders: &ColliderSet, colliders: &ColliderSet,
pair_filter: Option<&dyn IntersectionPairFilter>, hooks: &dyn PhysicsHooks,
events: &dyn EventHandler, events: &dyn EventHandler,
) { ) {
let nodes = &self.intersection_graph.graph.nodes; let nodes = &self.intersection_graph.graph.nodes;
let query_dispatcher = &*self.query_dispatcher; let query_dispatcher = &*self.query_dispatcher;
let active_hooks = hooks.active_hooks();
par_iter_mut!(&mut self.intersection_graph.graph.edges).for_each(|edge| { par_iter_mut!(&mut self.intersection_graph.graph.edges).for_each(|edge| {
let handle1 = nodes[edge.source().index()].weight; let handle1 = nodes[edge.source().index()].weight;
let handle2 = nodes[edge.target().index()].weight; let handle2 = nodes[edge.target().index()].weight;
@@ -415,12 +418,15 @@ impl NarrowPhase {
return; return;
} }
if pair_filter.is_none() && !rb1.is_dynamic() && !rb2.is_dynamic() { if !active_hooks.contains(PhysicsHooksFlags::FILTER_INTERSECTION_PAIR)
&& !rb1.is_dynamic()
&& !rb2.is_dynamic()
{
// Default filtering rule: no intersection between two non-dynamic bodies. // Default filtering rule: no intersection between two non-dynamic bodies.
return; return;
} }
if let Some(filter) = pair_filter { if active_hooks.contains(PhysicsHooksFlags::FILTER_INTERSECTION_PAIR) {
let context = PairFilterContext { let context = PairFilterContext {
rigid_body1: rb1, rigid_body1: rb1,
rigid_body2: rb2, rigid_body2: rb2,
@@ -430,7 +436,7 @@ impl NarrowPhase {
collider2: co2, collider2: co2,
}; };
if !filter.filter_intersection_pair(&context) { if !hooks.filter_intersection_pair(&context) {
// No intersection allowed. // No intersection allowed.
return; return;
} }
@@ -458,10 +464,11 @@ impl NarrowPhase {
prediction_distance: Real, prediction_distance: Real,
bodies: &RigidBodySet, bodies: &RigidBodySet,
colliders: &ColliderSet, colliders: &ColliderSet,
pair_filter: Option<&dyn ContactPairFilter>, hooks: &dyn PhysicsHooks,
events: &dyn EventHandler, events: &dyn EventHandler,
) { ) {
let query_dispatcher = &*self.query_dispatcher; let query_dispatcher = &*self.query_dispatcher;
let active_hooks = hooks.active_hooks();
par_iter_mut!(&mut self.contact_graph.graph.edges).for_each(|edge| { par_iter_mut!(&mut self.contact_graph.graph.edges).for_each(|edge| {
let pair = &mut edge.weight; let pair = &mut edge.weight;
@@ -485,12 +492,16 @@ impl NarrowPhase {
return; return;
} }
if pair_filter.is_none() && !rb1.is_dynamic() && !rb2.is_dynamic() { if !active_hooks.contains(PhysicsHooksFlags::FILTER_CONTACT_PAIR)
&& !rb1.is_dynamic()
&& !rb2.is_dynamic()
{
// Default filtering rule: no contact between two non-dynamic bodies. // Default filtering rule: no contact between two non-dynamic bodies.
return; return;
} }
let mut solver_flags = if let Some(filter) = pair_filter { let mut solver_flags = if active_hooks.contains(PhysicsHooksFlags::FILTER_CONTACT_PAIR)
{
let context = PairFilterContext { let context = PairFilterContext {
rigid_body1: rb1, rigid_body1: rb1,
rigid_body2: rb2, rigid_body2: rb2,
@@ -500,7 +511,7 @@ impl NarrowPhase {
collider2: co2, collider2: co2,
}; };
if let Some(solver_flags) = filter.filter_contact_pair(&context) { if let Some(solver_flags) = hooks.filter_contact_pair(&context) {
solver_flags solver_flags
} else { } else {
// No contact allowed. // No contact allowed.
@@ -566,13 +577,39 @@ impl NarrowPhase {
data: contact.data, data: contact.data,
}; };
// TODO: apply the user-defined contact modification/removal, if needed.
manifold.data.solver_contacts.push(solver_contact); manifold.data.solver_contacts.push(solver_contact);
has_any_active_contact = true; has_any_active_contact = true;
continue;
} }
} }
// Apply the user-defined contact modification.
if active_hooks.contains(PhysicsHooksFlags::MODIFY_SOLVER_CONTACTS)
&& manifold
.data
.solver_flags
.contains(SolverFlags::MODIFY_SOLVER_CONTACTS)
{
let mut modifiable_solver_contacts =
std::mem::replace(&mut manifold.data.solver_contacts, Vec::new());
let mut modifiable_user_data = manifold.data.user_data;
let mut context = ContactModificationContext {
rigid_body1: rb1,
rigid_body2: rb2,
collider_handle1: pair.pair.collider1,
collider_handle2: pair.pair.collider2,
collider1: co1,
collider2: co2,
manifold,
solver_contacts: &mut modifiable_solver_contacts,
user_data: &mut modifiable_user_data,
};
hooks.modify_solver_contacts(&mut context);
manifold.data.solver_contacts = modifiable_solver_contacts;
manifold.data.user_data = modifiable_user_data;
}
} }
if has_any_active_contact != pair.has_any_active_contact { if has_any_active_contact != pair.has_any_active_contact {

View File

@@ -1,5 +1,5 @@
use crate::dynamics::RigidBody; use crate::dynamics::RigidBody;
use crate::geometry::{Collider, ColliderHandle, SolverFlags}; use crate::geometry::{Collider, ColliderHandle, ContactManifold, SolverContact, SolverFlags};
/// Context given to custom collision filters to filter-out collisions. /// Context given to custom collision filters to filter-out collisions.
pub struct PairFilterContext<'a> { pub struct PairFilterContext<'a> {
@@ -17,14 +17,54 @@ pub struct PairFilterContext<'a> {
pub collider2: &'a Collider, pub collider2: &'a Collider,
} }
/// User-defined filter for potential contact pairs detected by the broad-phase. pub struct ContactModificationContext<'a> {
/// /// The first collider involved in the potential collision.
/// This can be used to apply custom logic in order to decide whether two colliders pub rigid_body1: &'a RigidBody,
/// should have their contact computed by the narrow-phase, and if these contact /// The first collider involved in the potential collision.
/// should be solved by the constraints solver pub rigid_body2: &'a RigidBody,
pub trait ContactPairFilter: Send + Sync { /// The first collider involved in the potential collision.
pub collider_handle1: ColliderHandle,
/// The first collider involved in the potential collision.
pub collider_handle2: ColliderHandle,
/// 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 contact manifold.
pub manifold: &'a ContactManifold,
/// The solver contacts that can be modified.
pub solver_contacts: &'a mut Vec<SolverContact>,
/// User-defined data attached to the manifold.
// NOTE: we keep this a &'a mut u32 to emphasize the
// fact that this can be modified.
pub user_data: &'a mut u32,
}
bitflags::bitflags! {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// Flags affecting the behavior of the constraints solver for a given contact manifold.
pub struct PhysicsHooksFlags: u32 {
/// If set, Rapier will call `PhysicsHooks::filter_contact_pair` whenever relevant.
const FILTER_CONTACT_PAIR = 0b0001;
/// If set, Rapier will call `PhysicsHooks::filter_intersection_pair` whenever relevant.
const FILTER_INTERSECTION_PAIR = 0b0010;
/// If set, Rapier will call `PhysicsHooks::modify_solver_contact` whenever relevant.
const MODIFY_SOLVER_CONTACTS = 0b0100;
}
}
/// User-defined functions called by the physics engines during one timestep in order to customize its behavior.
pub trait PhysicsHooks: Send + Sync {
/// The sets of hooks that must be taken into account.
fn active_hooks(&self) -> PhysicsHooksFlags;
/// Applies the contact pair filter. /// Applies the contact pair filter.
/// ///
/// 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
///
/// Note that using a contact pair filter will replace the default contact filtering /// Note that using a contact pair filter will replace the default contact filtering
/// which consists of preventing contact computation between two non-dynamic bodies. /// which consists of preventing contact computation between two non-dynamic bodies.
/// ///
@@ -39,15 +79,14 @@ pub trait ContactPairFilter: Send + Sync {
/// `Some(SolverFlags::empty())` then the constraints solver will ignore these /// `Some(SolverFlags::empty())` then the constraints solver will ignore these
/// contacts. /// contacts.
fn filter_contact_pair(&self, context: &PairFilterContext) -> Option<SolverFlags>; fn filter_contact_pair(&self, context: &PairFilterContext) -> Option<SolverFlags>;
}
/// User-defined filter for potential intersection pairs detected by the broad-phase.
///
/// This can be used to apply custom logic in order to decide whether two colliders
/// should have their intersection computed by the narrow-phase.
pub trait IntersectionPairFilter: Send + Sync {
/// Applies the intersection pair filter. /// Applies the intersection pair filter.
/// ///
/// User-defined filter for potential intersection pairs detected by the broad-phase.
///
/// This can be used to apply custom logic in order to decide whether two colliders
/// should have their intersection computed by the narrow-phase.
///
/// Note that using an intersection pair filter will replace the default intersection filtering /// Note that using an intersection pair filter will replace the default intersection filtering
/// which consists of preventing intersection computation between two non-dynamic bodies. /// which consists of preventing intersection computation between two non-dynamic bodies.
/// ///
@@ -58,4 +97,42 @@ pub trait IntersectionPairFilter: Send + Sync {
/// If this return `true` then the narrow-phase will compute intersection /// If this return `true` then the narrow-phase will compute intersection
/// information for this pair. /// information for this pair.
fn filter_intersection_pair(&self, context: &PairFilterContext) -> bool; fn filter_intersection_pair(&self, context: &PairFilterContext) -> bool;
/// Modifies the set of contacts seen by the constraints solver.
///
/// By default, the content of `solver_contacts` is computed from `manifold.points`.
/// This method will be called on each contact manifold which have the flag `SolverFlags::MODIFY_CONTACTS` set.
/// This method can be used to modify the set of solver contacts seen by the constraints solver: contacts
/// can be removed and modified.
///
/// Note that if all the contacts have to be ignored by the constraint solver, you may simply
/// do `context.solver_contacts.clear()`.
///
/// Modifying the solver contacts allow you to achieve various effects, including:
/// - Simulating conveyor belts by setting the `surface_velocity` of a solver contact.
/// - Simulating shapes with multiply materials by modifying the friction and restitution
/// coefficient depending of the features in contacts.
/// - Simulating one-way platforms depending on the contact normal.
///
/// Each contact manifold is given a `u32` user-defined data that is persistent between
/// timesteps (as long as the contact manifold exists). This user-defined data is initialized
/// as 0 and can be modified in `context.user_data`.
fn modify_solver_contacts(&self, context: &mut ContactModificationContext);
}
impl PhysicsHooks for () {
/// The sets of hooks that must be taken into account.
fn active_hooks(&self) -> PhysicsHooksFlags {
PhysicsHooksFlags::empty()
}
fn filter_contact_pair(&self, _: &PairFilterContext) -> Option<SolverFlags> {
None
}
fn filter_intersection_pair(&self, _: &PairFilterContext) -> bool {
false
}
fn modify_solver_contacts(&self, _: &mut ContactModificationContext) {}
} }

View File

@@ -2,8 +2,7 @@
use crate::dynamics::{JointSet, RigidBodySet}; use crate::dynamics::{JointSet, RigidBodySet};
use crate::geometry::{ use crate::geometry::{
BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactPairFilter, BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, NarrowPhase, PhysicsHooks,
IntersectionPairFilter, NarrowPhase,
}; };
use crate::math::Real; use crate::math::Real;
use crate::pipeline::EventHandler; use crate::pipeline::EventHandler;
@@ -44,8 +43,7 @@ impl CollisionPipeline {
narrow_phase: &mut NarrowPhase, narrow_phase: &mut NarrowPhase,
bodies: &mut RigidBodySet, bodies: &mut RigidBodySet,
colliders: &mut ColliderSet, colliders: &mut ColliderSet,
contact_pair_filter: Option<&dyn ContactPairFilter>, hooks: &dyn PhysicsHooks,
proximity_pair_filter: Option<&dyn IntersectionPairFilter>,
events: &dyn EventHandler, events: &dyn EventHandler,
) { ) {
bodies.maintain(colliders); bodies.maintain(colliders);
@@ -58,14 +56,8 @@ impl CollisionPipeline {
narrow_phase.register_pairs(colliders, bodies, &self.broad_phase_events, events); narrow_phase.register_pairs(colliders, bodies, &self.broad_phase_events, events);
narrow_phase.compute_contacts( narrow_phase.compute_contacts(prediction_distance, bodies, colliders, hooks, events);
prediction_distance, narrow_phase.compute_intersections(bodies, colliders, hooks, events);
bodies,
colliders,
contact_pair_filter,
events,
);
narrow_phase.compute_intersections(bodies, colliders, proximity_pair_filter, events);
bodies.update_active_set_with_contacts( bodies.update_active_set_with_contacts(
colliders, colliders,

View File

@@ -7,8 +7,8 @@ use crate::dynamics::{IntegrationParameters, JointSet, RigidBodySet};
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
use crate::dynamics::{JointGraphEdge, ParallelIslandSolver as IslandSolver}; use crate::dynamics::{JointGraphEdge, ParallelIslandSolver as IslandSolver};
use crate::geometry::{ use crate::geometry::{
BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactManifoldIndex, BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactManifoldIndex, NarrowPhase,
ContactPairFilter, IntersectionPairFilter, NarrowPhase, PhysicsHooks,
}; };
use crate::math::{Real, Vector}; use crate::math::{Real, Vector};
use crate::pipeline::EventHandler; use crate::pipeline::EventHandler;
@@ -69,8 +69,7 @@ impl PhysicsPipeline {
bodies: &mut RigidBodySet, bodies: &mut RigidBodySet,
colliders: &mut ColliderSet, colliders: &mut ColliderSet,
joints: &mut JointSet, joints: &mut JointSet,
contact_pair_filter: Option<&dyn ContactPairFilter>, hooks: &dyn PhysicsHooks,
proximity_pair_filter: Option<&dyn IntersectionPairFilter>,
events: &dyn EventHandler, events: &dyn EventHandler,
) { ) {
self.counters.step_started(); self.counters.step_started();
@@ -115,10 +114,10 @@ impl PhysicsPipeline {
integration_parameters.prediction_distance, integration_parameters.prediction_distance,
bodies, bodies,
colliders, colliders,
contact_pair_filter, hooks,
events, events,
); );
narrow_phase.compute_intersections(bodies, colliders, proximity_pair_filter, events); narrow_phase.compute_intersections(bodies, colliders, hooks, events);
// println!("Compute contact time: {}", instant::now() - t); // println!("Compute contact time: {}", instant::now() - t);
self.counters.stages.island_construction_time.start(); self.counters.stages.island_construction_time.start();

View File

@@ -192,8 +192,7 @@ impl Harness {
&mut self.physics.bodies, &mut self.physics.bodies,
&mut self.physics.colliders, &mut self.physics.colliders,
&mut self.physics.joints, &mut self.physics.joints,
None, &(),
None,
&self.event_handler, &self.event_handler,
); );