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

@@ -9,7 +9,10 @@ bitflags::bitflags! {
pub struct SolverFlags: u32 {
/// The constraint solver will take this contact manifold into
/// 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.
#[cfg_attr(feature = "serde-serialize", serde(skip))]
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.
@@ -165,6 +170,7 @@ impl ContactManifoldData {
solver_flags,
normal: Vector::zeros(),
solver_contacts: Vec::new(),
user_data: 0,
}
}
@@ -205,9 +211,3 @@ impl ContactManifoldData {
// 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::narrow_phase::NarrowPhase;
pub use self::pair_filter::{ContactPairFilter, IntersectionPairFilter, PairFilterContext};
pub use self::pair_filter::{PairFilterContext, PhysicsHooks};
pub use parry::query::TrackedContact;

View File

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

View File

@@ -1,5 +1,5 @@
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.
pub struct PairFilterContext<'a> {
@@ -17,14 +17,54 @@ pub struct PairFilterContext<'a> {
pub collider2: &'a Collider,
}
/// 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 {
pub struct ContactModificationContext<'a> {
/// 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 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.
///
/// 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
/// 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
/// contacts.
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.
///
/// 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
/// 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
/// information for this pair.
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::geometry::{
BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactPairFilter,
IntersectionPairFilter, NarrowPhase,
BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, NarrowPhase, PhysicsHooks,
};
use crate::math::Real;
use crate::pipeline::EventHandler;
@@ -44,8 +43,7 @@ impl CollisionPipeline {
narrow_phase: &mut NarrowPhase,
bodies: &mut RigidBodySet,
colliders: &mut ColliderSet,
contact_pair_filter: Option<&dyn ContactPairFilter>,
proximity_pair_filter: Option<&dyn IntersectionPairFilter>,
hooks: &dyn PhysicsHooks,
events: &dyn EventHandler,
) {
bodies.maintain(colliders);
@@ -58,14 +56,8 @@ impl CollisionPipeline {
narrow_phase.register_pairs(colliders, bodies, &self.broad_phase_events, events);
narrow_phase.compute_contacts(
prediction_distance,
bodies,
colliders,
contact_pair_filter,
events,
);
narrow_phase.compute_intersections(bodies, colliders, proximity_pair_filter, events);
narrow_phase.compute_contacts(prediction_distance, bodies, colliders, hooks, events);
narrow_phase.compute_intersections(bodies, colliders, hooks, events);
bodies.update_active_set_with_contacts(
colliders,

View File

@@ -7,8 +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,
ContactPairFilter, IntersectionPairFilter, NarrowPhase,
BroadPhase, BroadPhasePairEvent, ColliderPair, ColliderSet, ContactManifoldIndex, NarrowPhase,
PhysicsHooks,
};
use crate::math::{Real, Vector};
use crate::pipeline::EventHandler;
@@ -69,8 +69,7 @@ impl PhysicsPipeline {
bodies: &mut RigidBodySet,
colliders: &mut ColliderSet,
joints: &mut JointSet,
contact_pair_filter: Option<&dyn ContactPairFilter>,
proximity_pair_filter: Option<&dyn IntersectionPairFilter>,
hooks: &dyn PhysicsHooks,
events: &dyn EventHandler,
) {
self.counters.step_started();
@@ -115,10 +114,10 @@ impl PhysicsPipeline {
integration_parameters.prediction_distance,
bodies,
colliders,
contact_pair_filter,
hooks,
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);
self.counters.stages.island_construction_time.start();