Introduce the PhysicsHook trait used for both contact filtering and contact modification.
This commit is contained in:
@@ -23,16 +23,14 @@ pub fn init_world(testbed: &mut Testbed) {
|
||||
let handle = bodies.insert(rigid_body);
|
||||
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size).build();
|
||||
colliders.insert(collider, handle, &mut bodies);
|
||||
let mut k = 0;
|
||||
|
||||
// Callback that will be executed on the main loop to handle proximities.
|
||||
testbed.add_callback(move |mut window, mut graphics, physics, _, _| {
|
||||
k += 1;
|
||||
testbed.add_callback(move |mut window, mut graphics, physics, _, run_state| {
|
||||
let rigid_body = RigidBodyBuilder::new_dynamic()
|
||||
.translation(0.0, 10.0, 0.0)
|
||||
.build();
|
||||
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(),
|
||||
1 => ColliderBuilder::cone(rad, rad).build(),
|
||||
_ => ColliderBuilder::cuboid(rad, rad, rad).build(),
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -192,8 +192,7 @@ impl Harness {
|
||||
&mut self.physics.bodies,
|
||||
&mut self.physics.colliders,
|
||||
&mut self.physics.joints,
|
||||
None,
|
||||
None,
|
||||
&(),
|
||||
&self.event_handler,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user