Skip to main content

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}