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