Skip to main content

rapier3d/pipeline/
physics_hooks.rs

1#[cfg(feature = "alloc")]
2use crate::dynamics::{RigidBodyHandle, RigidBodySet};
3#[cfg(feature = "alloc")]
4use crate::geometry::SoftPairContacts;
5use crate::geometry::{ColliderHandle, ColliderSet, ContactManifold, SolverContacts, SolverFlags};
6#[cfg(feature = "alloc")]
7use crate::math::{Real, Vector};
8#[cfg(feature = "alloc")]
9use na::ComplexField;
10
11/// Context given to custom collision filters to filter-out collisions.
12#[cfg(feature = "alloc")]
13pub struct PairFilterContext<'a> {
14    /// The set of rigid-bodies.
15    pub bodies: &'a RigidBodySet,
16    /// The set of colliders.
17    pub colliders: &'a ColliderSet,
18    /// The handle of the first collider involved in the potential collision.
19    pub collider1: ColliderHandle,
20    /// The handle of the first collider involved in the potential collision.
21    pub collider2: ColliderHandle,
22    /// The handle of the first body involved in the potential collision.
23    pub rigid_body1: Option<RigidBodyHandle>,
24    /// The handle of the first body involved in the potential collision.
25    pub rigid_body2: Option<RigidBodyHandle>,
26}
27
28/// Context given to custom contact modifiers to modify the contacts seen by the constraints solver.
29#[cfg(feature = "alloc")]
30pub struct ContactModificationContext<'a> {
31    /// The set of rigid-bodies.
32    pub bodies: &'a RigidBodySet,
33    /// The set of colliders.
34    pub colliders: &'a ColliderSet,
35    /// The handle of the first collider involved in the potential collision.
36    pub collider1: ColliderHandle,
37    /// The handle of the first collider involved in the potential collision.
38    pub collider2: ColliderHandle,
39    /// The handle of the first body involved in the potential collision.
40    pub rigid_body1: Option<RigidBodyHandle>,
41    /// The handle of the first body involved in the potential collision.
42    pub rigid_body2: Option<RigidBodyHandle>,
43    /// The contacts to modify, in the form the pair holds them: a manifold's solver contacts,
44    /// or the candidates of two soft surfaces.
45    pub contacts: ModifiableContacts<'a>,
46}
47
48/// The contacts a [`ContactModificationContext`] lets the hook modify (see
49/// [`PhysicsHooks::modify_solver_contacts`]).
50#[cfg(feature = "alloc")]
51pub enum ModifiableContacts<'a> {
52    /// One contact manifold and its solver contacts: every pair but two soft surfaces.
53    Rigid(ModifiableManifold<'a>),
54    /// The contact candidates of two soft surfaces (see [`SoftPairContacts`]), editable in
55    /// place: a candidate's `dir` and `dist`, or `enabled` to drop it.
56    Soft(&'a mut SoftPairContacts),
57}
58
59/// A contact manifold as the hook may modify it (see [`ModifiableContacts::Rigid`]).
60#[cfg(feature = "alloc")]
61pub struct ModifiableManifold<'a> {
62    /// The contact manifold.
63    pub manifold: &'a ContactManifold,
64    /// The solver contacts that can be modified.
65    ///
66    /// While inside the hook, each solver contact's `anchor1`/`anchor2` hold the
67    /// fresh **world-space** contact points on each body, and `dist` their
68    /// separation (contact skins deducted); all are writable. After the hook
69    /// returns, any difference between `dist` and the anchors' geometric gap is
70    /// baked into the anchors, which are then converted to body-local frames for
71    /// the solver (see [`SolverContact`](crate::geometry::SolverContact)).
72    pub solver_contacts: &'a mut SolverContacts,
73    /// The contact normal that can be modified.
74    pub normal: &'a mut Vector,
75    /// The friction coefficient applied to every solver contact of this manifold,
76    /// that can be modified. (Since contact materials became per-manifold, per-contact
77    /// friction overrides are no longer possible.)
78    pub friction: &'a mut Real,
79    /// The restitution coefficient applied to every solver contact of this manifold,
80    /// that can be modified.
81    pub restitution: &'a mut Real,
82    /// User-defined data attached to the manifold.
83    // NOTE: we keep this a &'a mut u32 to emphasize the
84    // fact that this can be modified.
85    pub user_data: &'a mut u32,
86}
87
88#[cfg(feature = "alloc")]
89impl<'a> ContactModificationContext<'a> {
90    /// The manifold being modified (`None` for a pair of two soft surfaces).
91    pub fn rigid(&self) -> Option<&ModifiableManifold<'a>> {
92        match &self.contacts {
93            ModifiableContacts::Rigid(rigid) => Some(rigid),
94            ModifiableContacts::Soft(_) => None,
95        }
96    }
97
98    /// The manifold being modified (`None` for a pair of two soft surfaces).
99    pub fn rigid_mut(&mut self) -> Option<&mut ModifiableManifold<'a>> {
100        match &mut self.contacts {
101            ModifiableContacts::Rigid(rigid) => Some(rigid),
102            ModifiableContacts::Soft(_) => None,
103        }
104    }
105
106    /// The soft contact candidates being modified (`None` unless the pair is two soft surfaces).
107    pub fn soft(&self) -> Option<&SoftPairContacts> {
108        match &self.contacts {
109            ModifiableContacts::Rigid(_) => None,
110            ModifiableContacts::Soft(soft) => Some(soft),
111        }
112    }
113
114    /// The soft contact candidates being modified (`None` unless the pair is two soft surfaces).
115    pub fn soft_mut(&mut self) -> Option<&mut SoftPairContacts> {
116        match &mut self.contacts {
117            ModifiableContacts::Soft(soft) => Some(soft),
118            ModifiableContacts::Rigid(_) => None,
119        }
120    }
121}
122
123#[cfg(feature = "alloc")]
124impl ContactModificationContext<'_> {
125    /// Helper function to update `self` to emulate a oneway-platform.
126    ///
127    /// The "oneway" behavior will only allow contacts between two colliders
128    /// if the local contact normal of the first collider involved in the contact
129    /// is almost aligned with the provided `allowed_local_n1` direction.
130    ///
131    /// To make this method work properly it must be called as part of the
132    /// `PhysicsHooks::modify_solver_contacts` method at each timestep, for each
133    /// contact manifold involving a one-way platform. The manifold's `user_data` field must not be
134    /// modified outside this method; a pair of two soft surfaces (no manifold) is left alone.
135    pub fn update_as_oneway_platform(&mut self, allowed_local_n1: Vector, allowed_angle: Real) {
136        const CONTACT_CONFIGURATION_UNKNOWN: u32 = 0;
137        const CONTACT_CURRENTLY_ALLOWED: u32 = 1;
138        const CONTACT_CURRENTLY_FORBIDDEN: u32 = 2;
139
140        let Some(rigid) = self.rigid_mut() else {
141            return;
142        };
143        let cang = ComplexField::cos(allowed_angle);
144
145        // Test the allowed normal with the local-space contact normal that
146        // points towards the exterior of context.collider1.
147        let contact_is_ok = rigid.manifold.local_n1.dot(allowed_local_n1) >= cang;
148
149        match *rigid.user_data {
150            CONTACT_CONFIGURATION_UNKNOWN => {
151                if contact_is_ok {
152                    // The contact is close enough to the allowed normal.
153                    *rigid.user_data = CONTACT_CURRENTLY_ALLOWED;
154                } else {
155                    // The contact normal isn't close enough to the allowed
156                    // normal, so remove all the contacts and mark further contacts
157                    // as forbidden.
158                    rigid.solver_contacts.clear();
159
160                    // NOTE: in some very rare cases `local_n1` will be
161                    // zero if the objects are exactly touching at one point.
162                    // So in this case we can't really conclude.
163                    // If the norm is non-zero, then we can tell we need to forbid
164                    // further contacts. Otherwise we have to wait for the next frame.
165                    if rigid.manifold.local_n1.length_squared() > 0.1 {
166                        *rigid.user_data = CONTACT_CURRENTLY_FORBIDDEN;
167                    }
168                }
169            }
170            CONTACT_CURRENTLY_FORBIDDEN => {
171                // Contacts are forbidden so we need to continue forbidding contacts
172                // until all the contacts are non-penetrating again. In that case, if
173                // the contacts are OK with respect to the contact normal, then we can
174                // mark them as allowed.
175                if contact_is_ok && rigid.solver_contacts.iter().all(|c| c.dist > 0.0) {
176                    *rigid.user_data = CONTACT_CURRENTLY_ALLOWED;
177                } else {
178                    // Discard all the contacts.
179                    rigid.solver_contacts.clear();
180                }
181            }
182            CONTACT_CURRENTLY_ALLOWED => {
183                // We allow all the contacts right now. The configuration becomes
184                // uncertain again when the contact manifold no longer contains any contact.
185                if rigid.solver_contacts.is_empty() {
186                    *rigid.user_data = CONTACT_CONFIGURATION_UNKNOWN;
187                }
188            }
189            _ => unreachable!(),
190        }
191    }
192}
193
194bitflags::bitflags! {
195    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
196    #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
197    /// Flags that enable custom collision filtering and contact modification callbacks.
198    ///
199    /// These are advanced features for custom physics behavior. Most users don't need hooks -
200    /// use [`InteractionGroups`](crate::geometry::InteractionGroups) for collision filtering instead.
201    ///
202    /// Hooks let you:
203    /// - Dynamically decide if two colliders should collide (beyond collision groups)
204    /// - Modify contact properties before solving (friction, restitution, etc.)
205    /// - Implement one-way platforms, custom collision rules
206    ///
207    /// # Example use cases
208    /// - One-way platforms (collide from above, pass through from below)
209    /// - Complex collision rules that can't be expressed with collision groups
210    /// - Dynamic friction/restitution based on impact velocity
211    /// - Ghost mode (player temporarily ignores certain objects)
212    pub struct ActiveHooks: u32 {
213        /// Enables `PhysicsHooks::filter_contact_pair` callback for this collider.
214        ///
215        /// Lets you programmatically decide if contact should be computed and resolved.
216        const FILTER_CONTACT_PAIRS = 0b0001;
217
218        /// Enables `PhysicsHooks::filter_intersection_pair` callback for this collider.
219        ///
220        /// For sensor/intersection filtering (similar to contact filtering but for sensors).
221        const FILTER_INTERSECTION_PAIR = 0b0010;
222
223        /// Enables `PhysicsHooks::modify_solver_contacts` callback for this collider.
224        ///
225        /// Lets you modify contact properties (friction, restitution, etc.) before solving.
226        const MODIFY_SOLVER_CONTACTS = 0b0100;
227    }
228}
229impl Default for ActiveHooks {
230    fn default() -> Self {
231        ActiveHooks::empty()
232    }
233}
234
235/// User-defined functions called by the physics engines during one timestep in order to customize its behavior.
236pub trait PhysicsHooks: crate::utils::MaybeSync {
237    /// Applies the contact pair filter.
238    ///
239    /// Note that this method will only be called if at least one of the colliders
240    /// involved in the contact contains the `ActiveHooks::FILTER_CONTACT_PAIRS` flags
241    /// in its physics hooks flags.
242    ///
243    /// User-defined filter for potential contact pairs detected by the broad-phase.
244    /// This can be used to apply custom logic in order to decide whether two colliders
245    /// should have their contact computed by the narrow-phase, and if these contact
246    /// should be solved by the constraints solver
247    ///
248    /// Note that using a contact pair filter will replace the default contact filtering
249    /// which consists of preventing contact computation between two non-dynamic bodies.
250    ///
251    /// This filtering method is called after taking into account the colliders collision groups.
252    ///
253    /// If this returns `None`, then the narrow-phase will ignore this contact pair and
254    /// not compute any contact manifolds for it.
255    /// If this returns `Some`, then the narrow-phase will compute contact manifolds for
256    /// this pair of colliders, and configure them with the returned solver flags. For
257    /// example, if this returns `Some(SolverFlags::COMPUTE_RIGID_IMPULSES)` then the contacts
258    /// will be taken into account by the constraints solver. If this returns
259    /// `Some(SolverFlags::empty())` then the constraints solver will ignore these
260    /// contacts.
261    fn filter_contact_pair(&self, _context: &PairFilterContext) -> Option<SolverFlags> {
262        Some(SolverFlags::COMPUTE_RIGID_IMPULSES)
263    }
264
265    /// Applies the intersection pair filter.
266    ///
267    /// Note that this method will only be called if at least one of the colliders
268    /// involved in the contact contains the `ActiveHooks::FILTER_INTERSECTION_PAIR` flags
269    /// in its physics hooks flags.
270    ///
271    /// User-defined filter for potential intersection pairs detected by the broad-phase.
272    ///
273    /// This can be used to apply custom logic in order to decide whether two colliders
274    /// should have their intersection computed by the narrow-phase.
275    ///
276    /// Note that using an intersection pair filter will replace the default intersection filtering
277    /// which consists of preventing intersection computation between two non-dynamic bodies.
278    ///
279    /// This filtering method is called after taking into account the colliders collision groups.
280    ///
281    /// If this returns `false`, then the narrow-phase will ignore this pair and
282    /// not compute any intersection information for it.
283    /// If this return `true` then the narrow-phase will compute intersection
284    /// information for this pair.
285    fn filter_intersection_pair(&self, _context: &PairFilterContext) -> bool {
286        true
287    }
288
289    /// Modifies the set of contacts seen by the constraints solver.
290    ///
291    /// Note that this method will only be called if at least one of the colliders
292    /// involved in the contact contains the `ActiveHooks::MODIFY_SOLVER_CONTACTS` flags
293    /// in its physics hooks flags.
294    ///
295    /// By default, the content of `solver_contacts` is computed from `manifold.points`.
296    /// This method will be called on each contact manifold which have the flag `SolverFlags::modify_solver_contacts` set.
297    /// This method can be used to modify the set of solver contacts seen by the constraints solver: contacts
298    /// can be removed and modified. They sit in `context.contacts` (see
299    /// [`ModifiableContacts`]): the manifold's solver contacts for every pair but two soft
300    /// surfaces, whose candidates come instead.
301    ///
302    /// Note that if all the contacts have to be ignored by the constraint solver, you may simply
303    /// do `context.rigid_mut().unwrap().solver_contacts.clear()` (or `disable_all()` on the
304    /// soft candidates).
305    ///
306    /// Modifying the solver contacts allow you to achieve various effects, including:
307    /// - Simulating conveyor belts by setting the `surface_velocity` of a solver contact.
308    /// - Simulating shapes with multiply materials by modifying the friction and restitution
309    ///   coefficient depending of the features in contacts.
310    /// - Simulating one-way platforms depending on the contact normal.
311    ///
312    /// Each contact manifold is given a `u32` user-defined data that is persistent between
313    /// timesteps (as long as the contact manifold exists). This user-defined data is initialized
314    /// as 0 and can be modified in `context.user_data`.
315    ///
316    /// The world-space contact normal can be modified in the manifold's `normal`.
317    ///
318    /// Soft bodies: a soft surface's contacts with a rigid collider go through this hook like any
319    /// manifold; two soft surfaces' contacts arrive as candidates ([`ModifiableContacts::Soft`])
320    /// whose direction and distance may be edited, or disabled; self contacts never reach the hook.
321    fn modify_solver_contacts(&self, _context: &mut ContactModificationContext) {}
322}
323
324#[cfg(feature = "alloc")]
325impl PhysicsHooks for () {
326    fn filter_contact_pair(&self, _context: &PairFilterContext) -> Option<SolverFlags> {
327        Some(SolverFlags::default())
328    }
329
330    fn filter_intersection_pair(&self, _: &PairFilterContext) -> bool {
331        true
332    }
333
334    fn modify_solver_contacts(&self, _: &mut ContactModificationContext) {}
335}