rapier2d/pipeline/
physics_hooks.rs

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