avian3d/collision/narrow_phase/
mod.rs

1//! Manages contacts and generates contact constraints.
2//!
3//! See [`NarrowPhasePlugin`].
4
5mod system_param;
6use system_param::ContactStatusBits;
7pub use system_param::NarrowPhase;
8#[cfg(feature = "parallel")]
9use system_param::NarrowPhaseThreadLocals;
10
11use core::marker::PhantomData;
12
13use crate::{dynamics::solver::ContactConstraints, prelude::*};
14use bevy::{
15    ecs::{
16        entity_disabling::Disabled,
17        intern::Interned,
18        schedule::ScheduleLabel,
19        system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
20    },
21    prelude::*,
22};
23use dynamics::solver::SolverDiagnostics;
24
25use super::CollisionDiagnostics;
26
27/// Manages contacts and generates contact constraints.
28///
29/// # Overview
30///
31/// Before the narrow phase, the [broad phase](super::broad_phase) creates a contact pair
32/// in the [`ContactGraph`] resource for each pair of intersecting [`ColliderAabb`]s.
33///
34/// The narrow phase then determines which contact pairs found in the [`ContactGraph`] are touching,
35/// and computes updated contact points and normals in a parallel loop.
36///
37/// Afterwards, the narrow phase removes contact pairs whose AABBs no longer overlap,
38/// and sends collision events for colliders that started or stopped touching.
39/// This is done in a fast serial loop to preserve determinism.
40///
41/// Finally, a [`ContactConstraint`] is generated for each contact pair that is touching
42/// or expected to touch during the time step. These constraints are added to the [`ContactConstraints`]
43/// resource, and are later used by the [`SolverPlugin`] to solve contacts.
44///
45/// [`ContactConstraint`]: dynamics::solver::contact::ContactConstraint
46///
47/// # Collider Types
48///
49/// The plugin takes a collider type. This should be [`Collider`] for
50/// the vast majority of applications, but for custom collision backends
51/// you may use any collider that implements the [`AnyCollider`] trait.
52pub struct NarrowPhasePlugin<C: AnyCollider, H: CollisionHooks = ()> {
53    schedule: Interned<dyn ScheduleLabel>,
54    /// If `true`, the narrow phase will generate [`ContactConstraint`]s
55    /// and add them to the [`ContactConstraints`] resource.
56    ///
57    /// Contact constraints are used by the [`SolverPlugin`] for solving contacts.
58    ///
59    /// [`ContactConstraint`]: dynamics::solver::contact::ContactConstraint
60    generate_constraints: bool,
61    _phantom: PhantomData<(C, H)>,
62}
63
64impl<C: AnyCollider, H: CollisionHooks> NarrowPhasePlugin<C, H> {
65    /// Creates a [`NarrowPhasePlugin`] with the schedule used for running its systems
66    /// and whether it should generate [`ContactConstraint`]s for the [`ContactConstraints`] resource.
67    ///
68    /// Contact constraints are used by the [`SolverPlugin`] for solving contacts.
69    ///
70    /// The default schedule is [`PhysicsSchedule`].
71    ///
72    /// [`ContactConstraint`]: dynamics::solver::contact::ContactConstraint
73    pub fn new(schedule: impl ScheduleLabel, generate_constraints: bool) -> Self {
74        Self {
75            schedule: schedule.intern(),
76            generate_constraints,
77            _phantom: PhantomData,
78        }
79    }
80}
81
82impl<C: AnyCollider, H: CollisionHooks> Default for NarrowPhasePlugin<C, H> {
83    fn default() -> Self {
84        Self::new(PhysicsSchedule, true)
85    }
86}
87
88/// A resource that indicates that the narrow phase has been initialized.
89///
90/// This is used to ensure that some systems are only added once
91/// even with multiple collider types.
92#[derive(Resource, Default)]
93struct NarrowPhaseInitialized;
94
95impl<C: AnyCollider, H: CollisionHooks + 'static> Plugin for NarrowPhasePlugin<C, H>
96where
97    for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
98{
99    fn build(&self, app: &mut App) {
100        let already_initialized = app.world().is_resource_added::<NarrowPhaseInitialized>();
101
102        app.init_resource::<NarrowPhaseConfig>()
103            .init_resource::<ContactGraph>()
104            .init_resource::<ContactStatusBits>()
105            .init_resource::<DefaultFriction>()
106            .init_resource::<DefaultRestitution>();
107
108        #[cfg(feature = "parallel")]
109        app.init_resource::<NarrowPhaseThreadLocals>();
110
111        app.register_type::<(
112            NarrowPhaseConfig,
113            DefaultFriction,
114            DefaultRestitution,
115            CollisionEventsEnabled,
116        )>();
117
118        app.add_event::<CollisionStarted>()
119            .add_event::<CollisionEnded>();
120
121        if self.generate_constraints {
122            app.init_resource::<ContactConstraints>();
123        }
124
125        // Set up system set scheduling.
126        app.configure_sets(
127            self.schedule,
128            (
129                NarrowPhaseSet::First,
130                NarrowPhaseSet::Update,
131                NarrowPhaseSet::Last,
132            )
133                .chain()
134                .in_set(PhysicsStepSet::NarrowPhase),
135        );
136        app.configure_sets(
137            self.schedule,
138            CollisionEventSystems.in_set(PhysicsStepSet::Finalize),
139        );
140
141        // Perform narrow phase collision detection.
142        app.add_systems(
143            self.schedule,
144            update_narrow_phase::<C, H>
145                .in_set(NarrowPhaseSet::Update)
146                // Allowing ambiguities is required so that it's possible
147                // to have multiple collision backends at the same time.
148                .ambiguous_with_all(),
149        );
150
151        if !already_initialized {
152            // Remove collision pairs when colliders are disabled or removed.
153            app.add_observer(remove_collider_on::<OnAdd, Disabled>);
154            app.add_observer(remove_collider_on::<OnAdd, ColliderDisabled>);
155            app.add_observer(remove_collider_on::<OnRemove, ColliderMarker>);
156
157            // Trigger collision events for colliders that started or stopped touching.
158            app.add_systems(
159                PhysicsSchedule,
160                trigger_collision_events.in_set(CollisionEventSystems),
161            );
162        }
163
164        app.init_resource::<NarrowPhaseInitialized>();
165    }
166
167    fn finish(&self, app: &mut App) {
168        // Register timer and counter diagnostics for collision detection.
169        app.register_physics_diagnostics::<CollisionDiagnostics>();
170    }
171}
172
173/// A system set for triggering the [`OnCollisionStart`] and [`OnCollisionEnd`] events.
174///
175/// Runs in [`PhysicsStepSet::Finalize`], after the solver has run and contact impulses
176/// have been computed and applied.
177#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
178pub struct CollisionEventSystems;
179
180/// A resource for configuring the [narrow phase](NarrowPhasePlugin).
181#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
182#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
183#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
184#[reflect(Debug, Resource, PartialEq)]
185pub struct NarrowPhaseConfig {
186    /// The default maximum [speculative margin](SpeculativeMargin) used for
187    /// [speculative collisions](dynamics::ccd#speculative-collision). This can be overridden
188    /// for individual entities with the [`SpeculativeMargin`] component.
189    ///
190    /// By default, the maximum speculative margin is unbounded, so contacts can be predicted
191    /// from any distance, provided that the bodies are moving fast enough. As the prediction distance
192    /// grows, the contact data becomes more and more approximate, and in rare cases, it can even cause
193    /// [issues](dynamics::ccd#caveats-of-speculative-collision) such as ghost collisions.
194    ///
195    /// By limiting the maximum speculative margin, these issues can be mitigated, at the cost
196    /// of an increased risk of tunneling. Setting it to `0.0` disables speculative collision
197    /// altogether for entities without [`SpeculativeMargin`].
198    ///
199    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
200    ///
201    /// Default: `MAX` (unbounded)
202    pub default_speculative_margin: Scalar,
203
204    /// A contact tolerance that acts as a minimum bound for the [speculative margin](dynamics::ccd#speculative-collision).
205    ///
206    /// A small, positive contact tolerance helps ensure that contacts are not missed
207    /// due to numerical issues or solver jitter for objects that are in continuous
208    /// contact, such as pushing against each other.
209    ///
210    /// Making the contact tolerance too large will have a negative impact on performance,
211    /// as contacts will be computed even for objects that are not in close proximity.
212    ///
213    /// This is implicitly scaled by the [`PhysicsLengthUnit`].
214    ///
215    /// Default: `0.005`
216    pub contact_tolerance: Scalar,
217
218    /// If `true`, the current contacts will be matched with the previous contacts
219    /// based on feature IDs or contact positions, and the contact impulses from
220    /// the previous frame will be copied over for the new contacts.
221    ///
222    /// Using these impulses as the initial guess is referred to as *warm starting*,
223    /// and it can help the contact solver resolve overlap and stabilize much faster.
224    ///
225    /// Default: `true`
226    pub match_contacts: bool,
227}
228
229impl Default for NarrowPhaseConfig {
230    fn default() -> Self {
231        Self {
232            default_speculative_margin: Scalar::MAX,
233            contact_tolerance: 0.005,
234            match_contacts: true,
235        }
236    }
237}
238
239/// System sets for systems running in [`PhysicsStepSet::NarrowPhase`].
240#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
241pub enum NarrowPhaseSet {
242    /// Runs at the start of the narrow phase. Empty by default.
243    First,
244    /// Updates contacts in the [`ContactGraph`] and processes contact state changes.
245    Update,
246    /// Runs at the end of the narrow phase. Empty by default.
247    Last,
248}
249
250fn update_narrow_phase<C: AnyCollider, H: CollisionHooks + 'static>(
251    mut narrow_phase: NarrowPhase<C>,
252    mut collision_started_event_writer: EventWriter<CollisionStarted>,
253    mut collision_ended_event_writer: EventWriter<CollisionEnded>,
254    time: Res<Time>,
255    hooks: StaticSystemParam<H>,
256    context: StaticSystemParam<C::Context>,
257    mut commands: ParallelCommands,
258    mut diagnostics: ResMut<CollisionDiagnostics>,
259    solver_diagnostics: Option<ResMut<SolverDiagnostics>>,
260) where
261    for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
262{
263    let start = crate::utils::Instant::now();
264
265    narrow_phase.update::<H>(
266        &mut collision_started_event_writer,
267        &mut collision_ended_event_writer,
268        time.delta_seconds_adjusted(),
269        &hooks,
270        &context,
271        &mut commands,
272    );
273
274    diagnostics.narrow_phase = start.elapsed();
275    diagnostics.contact_count = narrow_phase.contact_graph.internal.edge_count() as u32;
276
277    if let Some(mut solver_diagnostics) = solver_diagnostics {
278        solver_diagnostics.contact_constraint_count = narrow_phase.contact_constraints.len() as u32;
279    }
280}
281
282#[derive(SystemParam)]
283struct TriggerCollisionEventsContext<'w, 's> {
284    query: Query<'w, 's, (Option<&'static ColliderOf>, Has<CollisionEventsEnabled>)>,
285    started: EventReader<'w, 's, CollisionStarted>,
286    ended: EventReader<'w, 's, CollisionEnded>,
287}
288
289/// Triggers [`OnCollisionStart`] and [`OnCollisionEnd`] events for colliders
290/// that started or stopped touching and have the [`CollisionEventsEnabled`] component.
291fn trigger_collision_events(
292    // We use exclusive access here to avoid queuing a new command for each event.
293    world: &mut World,
294    state: &mut SystemState<TriggerCollisionEventsContext>,
295    // Cache pairs in buffers to avoid reallocating every time.
296    mut started_pairs: Local<Vec<(Entity, OnCollisionStart)>>,
297    mut ended_pairs: Local<Vec<(Entity, OnCollisionEnd)>>,
298) {
299    let mut state = state.get_mut(world);
300
301    // Collect `OnCollisionStart` and `OnCollisionEnd` events
302    // for entities that have events enabled.
303    for event in state.started.read() {
304        let Ok([(collider_of1, events_enabled1), (collider_of2, events_enabled2)]) =
305            state.query.get_many([event.0, event.1])
306        else {
307            continue;
308        };
309        if events_enabled1 {
310            let collider = event.1;
311            let body = collider_of2.map(|c| c.body);
312            started_pairs.push((event.0, OnCollisionStart { collider, body }));
313        }
314        if events_enabled2 {
315            let collider = event.0;
316            let body = collider_of1.map(|c| c.body);
317            started_pairs.push((event.1, OnCollisionStart { collider, body }));
318        }
319    }
320    for event in state.ended.read() {
321        let Ok([(collider_of1, events_enabled1), (collider_of2, events_enabled2)]) =
322            state.query.get_many([event.0, event.1])
323        else {
324            continue;
325        };
326        if events_enabled1 {
327            let collider = event.1;
328            let body = collider_of2.map(|c| c.body);
329            ended_pairs.push((event.0, OnCollisionEnd { collider, body }));
330        }
331        if events_enabled2 {
332            let collider = event.0;
333            let body = collider_of1.map(|c| c.body);
334            ended_pairs.push((event.1, OnCollisionEnd { collider, body }));
335        }
336    }
337
338    // Trigger the events, draining the buffers in the process.
339    started_pairs.drain(..).for_each(|(entity, event)| {
340        world.trigger_targets(event, entity);
341    });
342    ended_pairs.drain(..).for_each(|(entity, event)| {
343        world.trigger_targets(event, entity);
344    });
345}
346
347/// Removes colliders from the [`ContactGraph`] when the given trigger is activated.
348///
349/// Also removes the collider from the [`CollidingEntities`] of the other entity,
350/// wakes up the other body, and sends a [`CollisionEnded`] event.
351fn remove_collider_on<E: Event, C: Component>(
352    trigger: Trigger<E, C>,
353    mut contact_graph: ResMut<ContactGraph>,
354    mut query: Query<&mut CollidingEntities>,
355    mut event_writer: EventWriter<CollisionEnded>,
356    mut commands: Commands,
357) {
358    let entity = trigger.target();
359
360    // Remove the collider from the contact graph.
361    contact_graph.remove_collider_with(entity, |contact_pair| {
362        // If the contact pair was not touching, we don't need to do anything.
363        if !contact_pair.flags.contains(ContactPairFlags::TOUCHING) {
364            return;
365        }
366
367        // Send a collision ended event.
368        if contact_pair
369            .flags
370            .contains(ContactPairFlags::CONTACT_EVENTS)
371        {
372            event_writer.write(CollisionEnded(
373                contact_pair.collider1,
374                contact_pair.collider2,
375            ));
376        }
377
378        // Remove the entity from the `CollidingEntities` of the other entity.
379        let other_entity = if contact_pair.collider1 == entity {
380            contact_pair.collider2
381        } else {
382            contact_pair.collider1
383        };
384        if let Ok(mut colliding_entities) = query.get_mut(other_entity) {
385            colliding_entities.remove(&entity);
386        }
387
388        // Wake up the other body.
389        commands.queue(WakeUpBody(other_entity));
390    });
391}