avian3d/collision/narrow_phase/system_param.rs
1#[cfg(feature = "parallel")]
2use core::cell::RefCell;
3
4use crate::{
5 collision::{
6 CollisionDiagnostics,
7 collider::EnlargedAabb,
8 contact_types::{ContactEdgeFlags, ContactId, ContactRecyclingCache},
9 },
10 data_structures::{bit_vec::BitVec, pair_key::PairKey},
11 prelude::*,
12};
13#[cfg(feature = "3d")]
14use bevy::math::ops::FloatPow;
15use bevy::{
16 ecs::{
17 query::QueryData,
18 system::{SystemParam, SystemParamItem, lifetimeless::Read},
19 },
20 prelude::*,
21};
22#[cfg(feature = "parallel")]
23use thread_local::ThreadLocal;
24
25#[derive(QueryData)]
26struct ColliderQuery<C: AnyCollider> {
27 entity: Entity,
28 of: Option<Read<ColliderOf>>,
29 shape: Read<C>,
30 enlarged_aabb: Read<EnlargedAabb>,
31 position: Read<Position>,
32 rotation: Read<Rotation>,
33 transform: Option<Read<ColliderTransform>>,
34 layers: Read<CollisionLayers>,
35 friction: Option<Read<Friction>>,
36 restitution: Option<Read<Restitution>>,
37 collision_margin: Option<Read<CollisionMargin>>,
38 is_sensor: Has<Sensor>,
39}
40
41#[derive(QueryData)]
42struct RigidBodyQuery {
43 entity: Entity,
44 rb: Read<RigidBody>,
45 position: Read<Position>,
46 rotation: Read<Rotation>,
47 center_of_mass: Read<ComputedCenterOfMass>,
48 linear_velocity: Read<LinearVelocity>,
49 angular_velocity: Read<AngularVelocity>,
50 // TODO: We should define these as purely collider components and not query for them here.
51 friction: Option<Read<Friction>>,
52 restitution: Option<Read<Restitution>>,
53 collision_margin: Option<Read<CollisionMargin>>,
54 speculative_ccd: Option<Read<SpeculativeCcd>>,
55 size_metrics: Read<BodySizeMetrics>,
56}
57
58/// A system parameter for managing the narrow phase.
59///
60/// Responsibilities:
61///
62/// - Updates each active [`ContactPair`] in the [`ContactGraph`].
63/// - Sends [collision events](crate::collision::collision_events) when colliders start or stop touching.
64/// - Removes contact pairs from the [`ContactGraph`] when AABBs stop overlapping.
65/// - Records [`ContactStatusChange`]s for the solver when contacts start or stop generating contact manifolds.
66///
67/// [`ConstraintGraph`]: crate::dynamics::solver::constraint_graph::ConstraintGraph
68/// [`PhysicsIslands`]: crate::dynamics::solver::islands::PhysicsIslands
69#[derive(SystemParam)]
70#[expect(missing_docs)]
71pub struct NarrowPhase<'w, 's, C: AnyCollider> {
72 collider_query: Query<'w, 's, ColliderQuery<C>, Without<ColliderDisabled>>,
73 colliding_entities_query: Query<'w, 's, &'static mut CollidingEntities>,
74 body_query: Query<'w, 's, RigidBodyQuery, Without<RigidBodyDisabled>>,
75 pub contact_graph: ResMut<'w, ContactGraph>,
76 /// The queue of contact status changess.
77 pub contact_status_changes: ResMut<'w, ContactStatusChangeQueue>,
78 contact_status_bits: ResMut<'w, ContactStatusBits>,
79 #[cfg(feature = "parallel")]
80 thread_locals: ResMut<'w, NarrowPhaseThreadLocals>,
81 pub config: Res<'w, NarrowPhaseConfig>,
82 default_friction: Res<'w, DefaultFriction>,
83 default_restitution: Res<'w, DefaultRestitution>,
84 length_unit: Res<'w, PhysicsLengthUnit>,
85}
86
87/// A bit vector for tracking contact status changes.
88/// Set bits correspond to contact pairs that were either added or removed.
89#[derive(Resource, Default, Deref, DerefMut)]
90pub(super) struct ContactStatusBits(pub BitVec);
91
92/// Thread-local data for the narrow phase contact updates.
93#[cfg(feature = "parallel")]
94#[derive(Resource, Default, Deref, DerefMut)]
95pub(super) struct NarrowPhaseThreadLocals(pub ThreadLocal<RefCell<NarrowPhaseThreadContext>>);
96
97/// A thread-local context for the narrow phase contact updates.
98#[cfg(feature = "parallel")]
99#[derive(Default)]
100pub(super) struct NarrowPhaseThreadContext {
101 /// Bit vector for tracking contact status changes.
102 ///
103 /// Set bits correspond to contact pairs that were either added or removed.
104 ///
105 /// The thread-local bit vectors are combined with the global [`ContactStatusBits`].
106 // TODO: We could just combine all the bit vectors into the first one
107 pub contact_status_bits: BitVec,
108 /// The number of contacts recycled by the narrow phase on this thread.
109 pub recycled_count: u32,
110}
111
112/// A change in a contact's solver status, recorded by the narrow phase
113/// and applied by the solver.
114///
115/// The narrow phase only *detects and records* these changes.
116/// The solver drains the [`ContactStatusChangeQueue`] queue and applies them,
117/// keeping the narrow phase and solver decoupled.
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub enum ContactStatusChange {
120 /// The contact started touching and should generate constraints.
121 StartedGeneratingConstraints(ContactId),
122 /// The contact stopped touching and should stop generating constraints.
123 ///
124 /// The bodies are captured here because the contact edge no longer exists.
125 StoppedGeneratingConstraints {
126 /// The contact whose constraints should be removed.
127 contact_id: ContactId,
128 /// The first body in the contact.
129 body1: Entity,
130 /// The second body in the contact.
131 body2: Entity,
132 },
133 /// The number of manifolds of a touching contact changed by `delta`.
134 /// Constraints should be added (if `delta > 0`) or removed (if `delta < 0`)
135 /// to match the new manifold count.
136 ManifoldCountChanged {
137 /// The contact whose manifold count changed.
138 contact: ContactId,
139 /// The first body in the contact.
140 body1: Entity,
141 /// The second body in the contact.
142 body2: Entity,
143 /// The signed change in the number of manifolds.
144 delta: i32,
145 },
146}
147
148/// A queue of [`ContactStatusChange`]s recorded by the narrow phase
149/// and applied by the solver.
150#[derive(Resource, Default, Debug, Deref, DerefMut)]
151pub struct ContactStatusChangeQueue(pub Vec<ContactStatusChange>);
152
153impl<C: AnyCollider> NarrowPhase<'_, '_, C> {
154 /// Updates the narrow phase.
155 ///
156 /// - Updates each active [`ContactPair`] in the [`ContactGraph`].
157 /// - Sends [collision events](crate::collision::collision_events) when colliders start or stop touching.
158 /// - Removes contact pairs from the [`ContactGraph`] when AABBs stop overlapping.
159 /// - Records [`ContactStatusChange`]s into [`ContactStatusChangeQueue`] for the solver to apply.
160 pub fn update<H: CollisionHooks>(
161 &mut self,
162 collision_started_writer: &mut MessageWriter<CollisionStart>,
163 collision_ended_writer: &mut MessageWriter<CollisionEnd>,
164 delta_secs: f32,
165 hooks: &SystemParamItem<H>,
166 context: &SystemParamItem<C::Context>,
167 diagnostics: &mut CollisionDiagnostics,
168 commands: &mut ParallelCommands,
169 ) where
170 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
171 {
172 // Update contacts for all contact pairs.
173 self.update_contacts::<H>(delta_secs, hooks, context, diagnostics, commands);
174
175 // Process contact status changes, iterating over set bits serially to maintain determinism.
176 //
177 // The narrow phase only updates the `ContactGraph` and records `ContactStatusChange`s.
178 // The solver applies those changes to the constraint graph and physics islands.
179 //
180 // Iterating over set bits is done efficiently with the "count trailing zeros" method:
181 // https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
182 for (i, mut bits) in self.contact_status_bits.blocks().enumerate() {
183 while bits != 0 {
184 let trailing_zeros = bits.trailing_zeros();
185 let contact_id = ContactId(i as u32 * 64 + trailing_zeros);
186
187 let (contact_edge, contact_pair) = self
188 .contact_graph
189 .get_mut_by_id(contact_id)
190 .unwrap_or_else(|| panic!("Contact pair not found for {contact_id:?}"));
191
192 // Three options:
193 // 1. The AABBs are no longer overlapping, and the contact pair should be removed.
194 // 2. The colliders started touching, and a collision started event should be sent.
195 // 3. The colliders stopped touching, and a collision ended event should be sent.
196 if contact_pair.aabbs_disjoint() {
197 // Send a collision ended event if the contact pair was touching.
198 let send_event = contact_edge
199 .flags
200 .contains(ContactEdgeFlags::TOUCHING | ContactEdgeFlags::CONTACT_EVENTS);
201 if send_event {
202 collision_ended_writer.write(CollisionEnd {
203 collider1: contact_pair.collider1,
204 collider2: contact_pair.collider2,
205 body1: contact_pair.body1,
206 body2: contact_pair.body2,
207 });
208 }
209
210 // Remove from `CollidingEntities`.
211 Self::remove_colliding_entities(
212 &mut self.colliding_entities_query,
213 contact_pair.collider1,
214 contact_pair.collider2,
215 );
216
217 let pair_key = PairKey::new(
218 contact_pair.collider1.index_u32(),
219 contact_pair.collider2.index_u32(),
220 );
221
222 if contact_pair.generates_constraints()
223 && let (Some(body1), Some(body2)) = (contact_pair.body1, contact_pair.body2)
224 {
225 self.contact_status_changes.push(
226 ContactStatusChange::StoppedGeneratingConstraints {
227 contact_id,
228 body1,
229 body2,
230 },
231 );
232 }
233
234 // Remove the contact edge from the contact graph.
235 self.contact_graph.remove_edge_by_id(&pair_key, contact_id);
236 } else if contact_pair.collision_started() {
237 // Send collision started event.
238 if contact_edge.events_enabled() {
239 collision_started_writer.write(CollisionStart {
240 collider1: contact_pair.collider1,
241 collider2: contact_pair.collider2,
242 body1: contact_pair.body1,
243 body2: contact_pair.body2,
244 });
245 }
246
247 // Add to `CollidingEntities`.
248 Self::add_colliding_entities(
249 &mut self.colliding_entities_query,
250 contact_pair.collider1,
251 contact_pair.collider2,
252 );
253
254 debug_assert!(
255 !contact_pair.manifolds.is_empty(),
256 "Manifolds should not be empty when colliders start touching"
257 );
258
259 contact_edge.flags.set(ContactEdgeFlags::TOUCHING, true);
260
261 // Clear the status change flags.
262 contact_pair
263 .flags
264 .set(ContactPairFlags::STARTED_TOUCHING, false);
265 contact_pair
266 .flags
267 .set(ContactPairFlags::STARTED_GENERATING_CONSTRAINTS, false);
268
269 if contact_pair.generates_constraints() {
270 self.contact_status_changes.push(
271 ContactStatusChange::StartedGeneratingConstraints(contact_id),
272 );
273 }
274 } else if contact_pair
275 .flags
276 .contains(ContactPairFlags::STOPPED_TOUCHING)
277 {
278 // Send collision ended event.
279 if contact_edge.events_enabled() {
280 collision_ended_writer.write(CollisionEnd {
281 collider1: contact_pair.collider1,
282 collider2: contact_pair.collider2,
283 body1: contact_pair.body1,
284 body2: contact_pair.body2,
285 });
286 }
287
288 // Remove from `CollidingEntities`.
289 Self::remove_colliding_entities(
290 &mut self.colliding_entities_query,
291 contact_pair.collider1,
292 contact_pair.collider2,
293 );
294
295 debug_assert!(
296 contact_pair.manifolds.is_empty(),
297 "Manifolds should be empty when colliders stopped touching"
298 );
299
300 contact_edge.flags.set(ContactEdgeFlags::TOUCHING, false);
301
302 // Clear the status change flags.
303 contact_pair
304 .flags
305 .set(ContactPairFlags::STOPPED_TOUCHING, false);
306
307 if contact_pair.generates_constraints()
308 && let (Some(body1), Some(body2)) = (contact_pair.body1, contact_pair.body2)
309 {
310 self.contact_status_changes.push(
311 ContactStatusChange::StoppedGeneratingConstraints {
312 contact_id,
313 body1,
314 body2,
315 },
316 );
317 }
318 } else if contact_pair.is_touching()
319 && contact_pair
320 .flags
321 .contains(ContactPairFlags::STARTED_GENERATING_CONSTRAINTS)
322 {
323 // The contact pair should start generating constraints.
324 contact_pair
325 .flags
326 .set(ContactPairFlags::STARTED_GENERATING_CONSTRAINTS, false);
327
328 self.contact_status_changes.push(
329 ContactStatusChange::StartedGeneratingConstraints(contact_id),
330 );
331 } else if contact_pair.is_touching()
332 && contact_pair.generates_constraints()
333 && contact_pair.manifold_count_change != 0
334 {
335 // The contact pair is still touching, but the manifold count changed.
336 // Let the solver add or remove the corresponding constraints.
337 let delta = contact_pair.manifold_count_change;
338 contact_pair.manifold_count_change = 0;
339
340 if let (Some(body1), Some(body2)) = (contact_pair.body1, contact_pair.body2) {
341 self.contact_status_changes.push(
342 ContactStatusChange::ManifoldCountChanged {
343 contact: contact_id,
344 body1,
345 body2,
346 delta,
347 },
348 );
349 }
350 }
351
352 // Clear the least significant set bit.
353 bits &= bits - 1;
354 }
355 }
356 }
357
358 /// Adds the colliding entities to their respective [`CollidingEntities`] components.
359 fn add_colliding_entities(
360 query: &mut Query<&mut CollidingEntities>,
361 entity1: Entity,
362 entity2: Entity,
363 ) {
364 if let Ok(mut colliding_entities1) = query.get_mut(entity1) {
365 colliding_entities1.insert(entity2);
366 }
367 if let Ok(mut colliding_entities2) = query.get_mut(entity2) {
368 colliding_entities2.insert(entity1);
369 }
370 }
371
372 /// Removes the colliding entities from their respective [`CollidingEntities`] components.
373 fn remove_colliding_entities(
374 query: &mut Query<&mut CollidingEntities>,
375 entity1: Entity,
376 entity2: Entity,
377 ) {
378 if let Ok(mut colliding_entities1) = query.get_mut(entity1) {
379 colliding_entities1.remove(&entity2);
380 }
381 if let Ok(mut colliding_entities2) = query.get_mut(entity2) {
382 colliding_entities2.remove(&entity1);
383 }
384 }
385
386 /// Updates contacts for all contact pairs in the [`ContactGraph`].
387 ///
388 /// Also updates the [`ContactStatusBits`] resource to track status changes for each contact pair.
389 /// Set bits correspond to contact pairs that were either added or removed,
390 /// while unset bits correspond to contact pairs that were persisted.
391 ///
392 /// The order of contact pairs is preserved.
393 fn update_contacts<H: CollisionHooks>(
394 &mut self,
395 delta_secs: f32,
396 hooks: &SystemParamItem<H>,
397 collider_context: &SystemParamItem<C::Context>,
398 diagnostics: &mut CollisionDiagnostics,
399 par_commands: &mut ParallelCommands,
400 ) where
401 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
402 {
403 let length_unit = self.length_unit.0;
404 let contact_tolerance = length_unit * self.config.contact_tolerance;
405 let recycle_distance = length_unit * self.config.recycle_distance;
406 let recycle_distance_non_touching = recycle_distance.min(contact_tolerance);
407 #[cfg(feature = "2d")]
408 let recycle_angle_cos = ops::cos(self.config.recycle_angle);
409 #[cfg(feature = "3d")]
410 let recycle_angular_distance = ops::cos(self.config.recycle_angle * 0.5).squared();
411 let inv_delta_secs = delta_secs.recip();
412
413 // Contact bit vecs must be sized based on the full contact capacity,
414 // not the number of active contact pairs, because pair indices
415 // are unstable and can be invalidated when pairs are removed.
416 let bit_count = self.contact_graph.edges.raw_edges().len();
417
418 // Clear the bit vector used to track status changes for each contact pair.
419 self.contact_status_bits.set_bit_count_and_clear(bit_count);
420
421 #[cfg(feature = "parallel")]
422 self.thread_locals.iter_mut().for_each(|context| {
423 let thread_locals = &mut context.borrow_mut();
424 thread_locals
425 .contact_status_bits
426 .set_bit_count_and_clear(bit_count);
427 thread_locals.recycled_count = 0;
428 });
429
430 // Compute contacts for all contact pairs in parallel or serially
431 // based on the `parallel` feature.
432 //
433 // Constraints are also generated for each contact pair that is touching
434 // or expected to start touching.
435 //
436 // If parallelism is used, status changes are written to thread-local bit vectors,
437 // where set bits correspond to contact pairs that were added or removed.
438 // At the end, the bit vectors are combined using bit-wise OR.
439 //
440 // If parallelism is not used, status changes are instead written
441 // directly to the global bit vector.
442 //
443 // TODO: An alternative to thread-local bit vectors could be to have one larger bit vector
444 // and to chunk it into smaller bit vectors for each thread. Might not be any faster though.
445 crate::utils::par_for_each(self.contact_graph.active_pairs_mut(), 64, |_i, contacts| {
446 let contact_id = contacts.contact_id.0 as usize;
447
448 #[cfg(not(feature = "parallel"))]
449 let contact_status_bits = &mut self.contact_status_bits;
450 #[cfg(not(feature = "parallel"))]
451 let recycled_count = &mut diagnostics.recycled_count;
452
453 // TODO: Move this out of the chunk iteration? Requires refactoring `par_for_each!`.
454 #[cfg(feature = "parallel")]
455 // Get the thread-local narrow phase context.
456 let mut thread_context = self
457 .thread_locals
458 .get_or(|| {
459 // No thread-local bit vector exists for this thread yet.
460 // Create a new one with the same capacity as the global bit vector.
461 let mut contact_status_bits = BitVec::new(bit_count);
462 contact_status_bits.set_bit_count_and_clear(bit_count);
463 NarrowPhaseThreadContext {
464 contact_status_bits,
465 recycled_count: 0,
466 }
467 .into()
468 })
469 .borrow_mut();
470
471 #[cfg(feature = "parallel")]
472 let NarrowPhaseThreadContext {
473 contact_status_bits,
474 recycled_count,
475 } = &mut *thread_context;
476
477 // Get the colliders for the contact pair.
478 let Ok([collider1, collider2]) = self
479 .collider_query
480 .get_many([contacts.collider1, contacts.collider2])
481 else {
482 return;
483 };
484
485 // An extra speculative distance requested for this timestep.
486 // This can be used to ensure that TOI impacts are handled by the discrete solver,
487 // or more generally for speculative contacts.
488 let mut speculative_distance = core::mem::take(&mut contacts.speculative_distance);
489
490 // Check if the AABBs of the colliders still overlap and the contact pair is valid.
491 let overlap = collider1.enlarged_aabb.intersects(
492 &collider2
493 .enlarged_aabb
494 .grow(Vector::splat(speculative_distance)),
495 );
496
497 // Also check if the collision layers are still compatible and the contact pair is valid.
498 // TODO: Ideally, we would have fine-grained change detection for `CollisionLayers`
499 // rather than checking it for every pair here.
500 if !overlap || !collider1.layers.interacts_with(*collider2.layers) {
501 // The AABBs no longer overlap. The contact pair should be removed.
502 contacts.flags.set(ContactPairFlags::DISJOINT_AABB, true);
503 contact_status_bits.set(contact_id);
504 } else {
505 // The AABBs overlap. Compute contacts.
506
507 let was_touching = contacts.flags.contains(ContactPairFlags::TOUCHING);
508
509 let body1_bundle = collider1
510 .of
511 .and_then(|&ColliderOf { body }| self.body_query.get(body).ok());
512 let body2_bundle = collider2
513 .of
514 .and_then(|&ColliderOf { body }| self.body_query.get(body).ok());
515
516 // The rigid body's friction, restitution, and collision margin
517 // will be used if the collider doesn't have them specified.
518 let (
519 is_static1,
520 collider_offset1,
521 world_com1,
522 body_pos1,
523 body_rot1,
524 lin_vel1,
525 ang_vel1,
526 rb_friction1,
527 rb_collision_margin1,
528 speculative_ccd1,
529 size_metrics1,
530 ) = body1_bundle
531 .as_ref()
532 .map(|body| {
533 (
534 body.rb.is_static(),
535 (collider1.position.0 - body.position.0).f32(),
536 body.rotation * body.center_of_mass.0,
537 body.position.0,
538 Rot::from(*body.rotation),
539 body.linear_velocity.0,
540 body.angular_velocity.0,
541 body.friction,
542 body.collision_margin,
543 body.speculative_ccd.copied(),
544 *body.size_metrics,
545 )
546 })
547 .unwrap_or_default();
548 let (
549 is_static2,
550 collider_offset2,
551 world_com2,
552 body_pos2,
553 body_rot2,
554 lin_vel2,
555 ang_vel2,
556 rb_friction2,
557 rb_collision_margin2,
558 speculative_ccd2,
559 size_metrics2,
560 ) = body2_bundle
561 .as_ref()
562 .map(|body| {
563 (
564 body.rb.is_static(),
565 (collider2.position.0 - body.position.0).f32(),
566 body.rotation * body.center_of_mass.0,
567 body.position.0,
568 Rot::from(*body.rotation),
569 body.linear_velocity.0,
570 body.angular_velocity.0,
571 body.friction,
572 body.collision_margin,
573 body.speculative_ccd.copied(),
574 *body.size_metrics,
575 )
576 })
577 .unwrap_or_default();
578
579 // Store these to avoid having to query for the bodies
580 // when processing status changes for the constraint graph.
581 contacts.flags.set(ContactPairFlags::STATIC1, is_static1);
582 contacts.flags.set(ContactPairFlags::STATIC2, is_static2);
583
584 // Compute the relative pose of the bodies in the local space of the first body.
585 let inv_rot1 = body_rot1.inverse();
586 let pos12 = (body_pos2 - body_pos1).f32();
587 let rot12_1 = inv_rot1 * body_rot2;
588 let pos12_1 = inv_rot1 * pos12;
589
590 // Contact recycling optimization, based on Box2D/Box3D by Erin Catto.
591 // This is inspired by persistent contact manifolds used in some physics engines,
592 // but allows larger relative motion, and has fewer tuning parameters (distance and angle).
593 // This has a huge impact on the performance and stability of stacks and resting bodies.
594 'recycle: {
595 if recycle_distance <= 0.0 {
596 // Recycling is disabled.
597 break 'recycle;
598 }
599
600 let Some(cache) = contacts.recycling_cache.as_ref() else {
601 // No previous contact data to recycle.
602 break 'recycle;
603 };
604
605 #[cfg(feature = "2d")]
606 {
607 let cos1 = body_rot1.cos * cache.rotation1.cos
608 + body_rot1.sin * cache.rotation1.sin;
609 let cos2 = body_rot2.cos * cache.rotation2.cos
610 + body_rot2.sin * cache.rotation2.sin;
611 let min_cos = cos1.min(cos2);
612 if min_cos <= recycle_angle_cos {
613 // The relative rotation has changed too much.
614 break 'recycle;
615 }
616 }
617 #[cfg(feature = "3d")]
618 {
619 let angle1 = body_rot1.dot(cache.rotation1);
620 let angle2 = body_rot2.dot(cache.rotation2);
621 let min_angle = (angle1 * angle1).min(angle2 * angle2);
622 if min_angle <= recycle_angular_distance {
623 // The relative rotation has changed too much.
624 break 'recycle;
625 }
626 }
627
628 let sweep_radius1 = if is_static1 {
629 0.0
630 } else {
631 size_metrics1.sweep_radius
632 };
633 let sweep_radius2 = if is_static2 {
634 0.0
635 } else {
636 size_metrics2.sweep_radius
637 };
638 let max_extent = sweep_radius1.max(sweep_radius2);
639 let distance_squared = pos12_1.distance_squared(cache.relative_translation);
640
641 let tolerance = if was_touching {
642 recycle_distance
643 } else {
644 recycle_distance_non_touching
645 };
646
647 #[cfg(feature = "2d")]
648 {
649 let distance = distance_squared.sqrt();
650 let delta_rotation = rot12_1.inverse() * cache.relative_rotation;
651 if distance + max_extent * delta_rotation.sin.abs() >= tolerance {
652 // The relative motion is too large.
653 break 'recycle;
654 }
655 }
656 #[cfg(feature = "3d")]
657 {
658 if distance_squared >= tolerance * tolerance {
659 // The relative motion is too large.
660 break 'recycle;
661 } else {
662 // Check the maximum motion along the arc of the relative rotation.
663 let distance = distance_squared.sqrt();
664 let slack = tolerance - distance;
665 let delta_rotation = cache.relative_rotation.inverse() * rot12_1;
666 // TODO: Use a Vec3 max_extent and a symmetric cross product
667 let arc = delta_rotation.chord_length() * max_extent;
668 if arc >= slack {
669 // The relative motion is too large.
670 break 'recycle;
671 }
672 }
673 }
674
675 // The relative motion is small enough for contact recycling!
676 let delta_rot1 = body_rot1 * cache.rotation1.inverse();
677 let delta_rot2 = body_rot2 * cache.rotation2.inverse();
678 #[cfg(feature = "3d")]
679 let delta_rot1 = Mat3::from_quat(delta_rot1);
680 #[cfg(feature = "3d")]
681 let delta_rot2 = Mat3::from_quat(delta_rot2);
682 let com12 = world_com2 - world_com1 + pos12;
683
684 contacts.manifolds.iter_mut().for_each(|manifold| {
685 manifold.points.iter_mut().for_each(|point| {
686 // Keep anchors but update separation. This eliminates jitter.
687 let r1 = delta_rot1 * point.anchor1;
688 let r2 = delta_rot2 * point.anchor2;
689 let delta_separation = com12 + (r2 - r1);
690 point.penetration =
691 point.base_penetration - delta_separation.dot(manifold.normal);
692 });
693 });
694
695 *recycled_count += 1;
696
697 return;
698 }
699
700 // Update the recycling cache for the next frame.
701 contacts.recycling_cache = Some(ContactRecyclingCache {
702 rotation1: body_rot1,
703 rotation2: body_rot2,
704 relative_translation: pos12_1,
705 relative_rotation: rot12_1,
706 });
707
708 // If either collider is a sensor or either body is disabled, no constraints should be generated.
709 let is_disabled = body1_bundle.is_none()
710 || body2_bundle.is_none()
711 || collider1.is_sensor
712 || collider2.is_sensor;
713
714 if !is_disabled && !contacts.generates_constraints() {
715 // This can happen when a sensor is removed, or when a collider is attached to a body.
716 contacts
717 .flags
718 .set(ContactPairFlags::STARTED_GENERATING_CONSTRAINTS, true);
719 contact_status_bits.set(contact_id);
720 }
721
722 contacts
723 .flags
724 .set(ContactPairFlags::GENERATE_CONSTRAINTS, !is_disabled);
725
726 // Get combined friction and restitution coefficients of the colliders
727 // or the bodies they are attached to. Fall back to the global defaults.
728 let friction = collider1
729 .friction
730 .or(rb_friction1)
731 .copied()
732 .unwrap_or(self.default_friction.0)
733 .combine(
734 collider2
735 .friction
736 .or(rb_friction2)
737 .copied()
738 .unwrap_or(self.default_friction.0),
739 )
740 .dynamic_coefficient;
741 let restitution = collider1
742 .restitution
743 .copied()
744 .unwrap_or(self.default_restitution.0)
745 .combine(
746 collider2
747 .restitution
748 .copied()
749 .unwrap_or(self.default_restitution.0),
750 )
751 .coefficient;
752
753 // Use the collider's own collision margin if specified, and fall back to the body's
754 // collision margin.
755 //
756 // The collision margin adds artificial thickness to colliders for performance
757 // and stability. See the `CollisionMargin` documentation for more details.
758 let collision_margin1 = collider1
759 .collision_margin
760 .or(rb_collision_margin1)
761 .map_or(0.0, |margin| margin.0);
762 let collision_margin2 = collider2
763 .collision_margin
764 .or(rb_collision_margin2)
765 .map_or(0.0, |margin| margin.0);
766 let collision_margin_sum = collision_margin1 + collision_margin2;
767
768 // The relative linear velocity is used below to compute the normal speed
769 // at each contact point for restitution and speculative contact pruning.
770 let relative_linear_velocity = lin_vel2 - lin_vel1;
771
772 // Compute the effective speculative margin from the opt-in `SpeculativeCcd` component.
773 let max_distance1 = speculative_ccd1.map_or(0.0, |s| s.max_distance);
774 let max_distance2 = speculative_ccd2.map_or(0.0, |s| s.max_distance);
775
776 if max_distance1 != 0.0 || max_distance2 != 0.0 {
777 // Clamp each body's contribution to its maximum speculative distance.
778 let clamped_vel1 = if max_distance1 < f32::MAX {
779 lin_vel1.clamp_length_max(max_distance1 * inv_delta_secs)
780 } else {
781 lin_vel1
782 };
783 let clamped_vel2 = if max_distance2 < f32::MAX {
784 lin_vel2.clamp_length_max(max_distance2 * inv_delta_secs)
785 } else {
786 lin_vel2
787 };
788
789 // The margin is how far the bodies are expected to move relative to each other.
790 let margin = (clamped_vel2 - clamped_vel1).length() * delta_secs;
791 speculative_distance = margin.max(speculative_distance);
792 }
793
794 // Ensure that the speculative distance is at least as large as the contact tolerance.
795 speculative_distance = speculative_distance.max(contact_tolerance);
796
797 // The maximum distance at which contacts are detected.
798 let max_contact_distance = collision_margin_sum + speculative_distance;
799
800 // Save the old manifolds for warm starting.
801 let old_manifolds = contacts.manifolds.clone();
802
803 // TODO: It'd be good to persist the manifolds and let Parry match contacts.
804 // This isn't currently done because it requires using Parry's contact manifold type.
805 // Compute the contact manifolds.
806 let context =
807 ColliderPairContext::new(collider1.entity, collider2.entity, collider_context);
808 collider1.shape.contact_manifolds_with_context(
809 collider2.shape,
810 collider1.position.0,
811 *collider1.rotation,
812 collider2.position.0,
813 *collider2.rotation,
814 max_contact_distance,
815 &mut contacts.manifolds,
816 context,
817 );
818
819 // Transform and prune contact data.
820 contacts.manifolds.retain_mut(|manifold| {
821 // Set the initial surface properties.
822 manifold.friction = friction;
823 manifold.restitution = restitution;
824 #[cfg(feature = "2d")]
825 {
826 manifold.tangent_speed = 0.0;
827 }
828 #[cfg(feature = "3d")]
829 {
830 manifold.tangent_velocity = Vec3::ZERO;
831 }
832
833 let normal = manifold.normal;
834
835 // Transform and prune contact points.
836 manifold.retain_points_mut(|point| {
837 // Transform contact points to be relative to the centers of mass of the bodies.
838 point.anchor1 += collider_offset1 - world_com1;
839 point.anchor2 += collider_offset2 - world_com2;
840
841 // Add the collision margin to the penetration depth.
842 point.penetration += collision_margin_sum;
843
844 // Store the base penetration depth for contact recycling.
845 point.base_penetration = point.penetration;
846
847 // Compute the relative velocity along the contact normal.
848 #[cfg(feature = "2d")]
849 let relative_velocity = relative_linear_velocity
850 + ang_vel2 * point.anchor2.perp()
851 - ang_vel1 * point.anchor1.perp();
852 #[cfg(feature = "3d")]
853 let relative_velocity = relative_linear_velocity
854 + ang_vel2.cross(point.anchor2)
855 - ang_vel1.cross(point.anchor1);
856 let normal_speed = relative_velocity.dot(normal);
857 point.normal_speed = normal_speed;
858
859 // Keep the contact if (1) the separation distance is below the required threshold,
860 // or if (2) the bodies are expected to come into contact within the next time step.
861 -point.penetration < speculative_distance || {
862 let delta_distance = normal_speed * delta_secs;
863 delta_distance - point.penetration < speculative_distance
864 }
865 });
866
867 // Prune extra contact points.
868 #[cfg(feature = "3d")]
869 if manifold.points.len() > 4 {
870 manifold.prune_points();
871 }
872
873 !manifold.points.is_empty()
874 });
875
876 // Check if the colliders are now touching.
877 let mut touching = !contacts.manifolds.is_empty();
878
879 if touching && contacts.flags.contains(ContactPairFlags::MODIFY_CONTACTS) {
880 par_commands.command_scope(|mut commands| {
881 touching = hooks.modify_contacts(contacts, &mut commands);
882 });
883 if !touching {
884 contacts.manifolds.clear();
885 }
886 }
887
888 contacts.flags.set(ContactPairFlags::TOUCHING, touching);
889
890 // TODO: Manifold reduction
891
892 // TODO: This condition is pretty arbitrary, mainly to skip dense trimeshes.
893 // If we let Parry handle contact matching, this wouldn't be needed.
894 if contacts.manifolds.len() <= 4 && self.config.match_contacts {
895 // TODO: Cache this?
896 let distance_threshold = 0.1 * self.length_unit.0;
897
898 for manifold in contacts.manifolds.iter_mut() {
899 for previous_manifold in old_manifolds.iter() {
900 manifold.match_contacts(&previous_manifold.points, distance_threshold);
901 }
902 }
903 }
904
905 // Record how many manifolds were added or removed.
906 // This is used to add or remove contact constraints in the `ConstraintGraph` for persistent contacts.
907 contacts.manifold_count_change =
908 contacts.manifolds.len() as i32 - old_manifolds.len() as i32;
909
910 // TODO: For unmatched contacts, apply any leftover impulses from the previous frame.
911
912 if touching && !was_touching {
913 // The colliders started touching.
914 contacts.flags.set(ContactPairFlags::STARTED_TOUCHING, true);
915 contact_status_bits.set(contact_id);
916 } else if !touching && was_touching {
917 // The colliders stopped touching.
918 contacts.flags.set(ContactPairFlags::STOPPED_TOUCHING, true);
919 contact_status_bits.set(contact_id);
920 } else if contacts.manifold_count_change != 0 {
921 // The manifold count changed, but the colliders are still touching.
922 // This is used to add or remove contact constraints in the `ConstraintGraph`.
923 contact_status_bits.set(contact_id);
924 }
925 };
926 });
927
928 #[cfg(feature = "parallel")]
929 {
930 // Combine the thread-local bit vectors serially using bit-wise OR.
931 self.thread_locals.iter_mut().for_each(|context| {
932 let thread_context = context.borrow();
933 let contact_status_bits = &thread_context.contact_status_bits;
934 self.contact_status_bits.or(contact_status_bits);
935 diagnostics.recycled_count += thread_context.recycled_count;
936 });
937 }
938 }
939}