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