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}