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}