1mod system_param;
21use system_param::ContactStatusBits;
22#[cfg(feature = "parallel")]
23use system_param::NarrowPhaseThreadLocals;
24pub use system_param::{ContactStatusChange, ContactStatusChangeQueue, NarrowPhase};
25
26use core::marker::PhantomData;
27
28use crate::{dynamics::joints::joint_graph::JointGraph, prelude::*};
29use bevy::{
30 ecs::{
31 entity_disabling::Disabled,
32 intern::Interned,
33 schedule::ScheduleLabel,
34 system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
35 },
36 prelude::*,
37};
38
39use super::{CollisionDiagnostics, contact_types::ContactEdgeFlags};
40
41pub struct NarrowPhasePlugin<C: AnyCollider, H: CollisionHooks = ()> {
52 schedule: Interned<dyn ScheduleLabel>,
53 _phantom: PhantomData<(C, H)>,
54}
55
56impl<C: AnyCollider, H: CollisionHooks> NarrowPhasePlugin<C, H> {
57 pub fn new(schedule: impl ScheduleLabel) -> Self {
61 Self {
62 schedule: schedule.intern(),
63 _phantom: PhantomData,
64 }
65 }
66}
67
68impl<C: AnyCollider, H: CollisionHooks> Default for NarrowPhasePlugin<C, H> {
69 fn default() -> Self {
70 Self::new(PhysicsSchedule)
71 }
72}
73
74#[derive(Resource, Default)]
79struct NarrowPhaseInitialized;
80
81impl<C: AnyCollider, H: CollisionHooks + 'static> Plugin for NarrowPhasePlugin<C, H>
82where
83 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
84{
85 fn build(&self, app: &mut App) {
86 let already_initialized = app.world().is_resource_added::<NarrowPhaseInitialized>();
87
88 app.init_resource::<NarrowPhaseConfig>()
89 .init_resource::<ContactGraph>()
90 .init_resource::<JointGraph>()
91 .init_resource::<ContactStatusBits>()
92 .init_resource::<ContactStatusChangeQueue>()
93 .init_resource::<DefaultFriction>()
94 .init_resource::<DefaultRestitution>();
95
96 #[cfg(feature = "parallel")]
97 app.init_resource::<NarrowPhaseThreadLocals>();
98
99 app.add_message::<CollisionStart>()
100 .add_message::<CollisionEnd>();
101
102 app.configure_sets(
104 self.schedule,
105 (
106 NarrowPhaseSystems::First,
107 NarrowPhaseSystems::Update,
108 NarrowPhaseSystems::Last,
109 )
110 .chain()
111 .in_set(PhysicsStepSystems::NarrowPhase),
112 );
113 app.configure_sets(
114 self.schedule,
115 CollisionEventSystems.in_set(PhysicsStepSystems::Finalize),
116 );
117
118 app.add_systems(
120 self.schedule,
121 update_narrow_phase::<C, H>
122 .in_set(NarrowPhaseSystems::Update)
123 .ambiguous_with_all(),
126 );
127
128 if !already_initialized {
129 app.add_observer(remove_collider_on::<Add<(Disabled, ColliderDisabled)>>);
131 app.add_observer(remove_collider_on::<Remove<ColliderMarker>>);
132
133 app.add_observer(on_add_sensor);
137 app.add_observer(on_remove_sensor);
138
139 app.add_observer(on_body_remove_rigid_body_disabled);
142 app.add_observer(on_disable_body);
143
144 app.add_observer(remove_body_on::<Insert<RigidBody>>);
146 app.add_observer(remove_body_on::<Remove<RigidBody>>);
147
148 app.add_systems(
150 self.schedule,
151 trigger_collision_events
152 .in_set(CollisionEventSystems)
153 .ambiguous_with(PhysicsStepSystems::Finalize),
156 );
157 }
158
159 app.init_resource::<NarrowPhaseInitialized>();
160 }
161
162 fn finish(&self, app: &mut App) {
163 app.register_physics_diagnostics::<CollisionDiagnostics>();
165 }
166}
167
168#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
173pub struct CollisionEventSystems;
174
175#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
177#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
178#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
179#[reflect(Debug, Resource, PartialEq)]
180pub struct NarrowPhaseConfig {
181 pub contact_tolerance: f32,
189
190 pub recycle_distance: f32,
199
200 #[cfg_attr(feature = "2d", doc = "Default: `0.2` (approximately 11.5 degrees)")]
204 #[cfg_attr(feature = "3d", doc = "Default: 0.175 (approximately 10 degrees)")]
205 pub recycle_angle: f32,
206
207 pub match_contacts: bool,
216}
217
218impl Default for NarrowPhaseConfig {
219 fn default() -> Self {
220 Self {
221 contact_tolerance: 0.02,
223 recycle_distance: 0.05,
224 #[cfg(feature = "2d")]
226 recycle_angle: 0.2,
227 #[cfg(feature = "3d")]
228 recycle_angle: 0.175,
229 match_contacts: true,
230 }
231 }
232}
233
234#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
236pub enum NarrowPhaseSystems {
237 First,
239 Update,
241 Last,
243}
244
245#[deprecated(since = "0.4.0", note = "Renamed to `NarrowPhaseSystems`")]
247pub type NarrowPhaseSet = NarrowPhaseSystems;
248
249fn update_narrow_phase<C: AnyCollider, H: CollisionHooks + 'static>(
250 mut narrow_phase: NarrowPhase<C>,
251 mut collision_started_writer: MessageWriter<CollisionStart>,
252 mut collision_ended_writer: MessageWriter<CollisionEnd>,
253 time: Res<Time>,
254 hooks: StaticSystemParam<H>,
255 context: StaticSystemParam<C::Context>,
256 mut commands: ParallelCommands,
257 mut diagnostics: ResMut<CollisionDiagnostics>,
258) where
259 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
260{
261 let start = crate::utils::Instant::now();
262
263 narrow_phase.update::<H>(
264 &mut collision_started_writer,
265 &mut collision_ended_writer,
266 time.delta_secs(),
267 &hooks,
268 &context,
269 &mut diagnostics,
270 &mut commands,
271 );
272
273 diagnostics.narrow_phase = start.elapsed();
274 diagnostics.contact_count = narrow_phase.contact_graph.edges.edge_count() as u32;
275}
276
277#[derive(SystemParam)]
278struct TriggerCollisionEventsContext<'w, 's> {
279 query: Query<'w, 's, Has<CollisionEventsEnabled>>,
280 started: MessageReader<'w, 's, CollisionStart>,
281 ended: MessageReader<'w, 's, CollisionEnd>,
282}
283
284fn trigger_collision_events(
287 world: &mut World,
289 state: &mut SystemState<TriggerCollisionEventsContext>,
290 mut started: Local<Vec<CollisionStart>>,
292 mut ended: Local<Vec<CollisionEnd>>,
293) {
294 let mut state = state.get_mut(world).unwrap();
295
296 for event in state.started.read() {
298 let Ok([events_enabled1, events_enabled2]) =
299 state.query.get_many([event.collider1, event.collider2])
300 else {
301 continue;
302 };
303
304 if events_enabled1 {
305 started.push(CollisionStart {
306 collider1: event.collider1,
307 collider2: event.collider2,
308 body1: event.body1,
309 body2: event.body2,
310 });
311 }
312 if events_enabled2 {
313 started.push(CollisionStart {
314 collider1: event.collider2,
315 collider2: event.collider1,
316 body1: event.body2,
317 body2: event.body1,
318 });
319 }
320 }
321
322 for event in state.ended.read() {
324 let Ok([events_enabled1, events_enabled2]) =
325 state.query.get_many([event.collider1, event.collider2])
326 else {
327 continue;
328 };
329
330 if events_enabled1 {
331 ended.push(CollisionEnd {
332 collider1: event.collider1,
333 collider2: event.collider2,
334 body1: event.body1,
335 body2: event.body2,
336 });
337 }
338 if events_enabled2 {
339 ended.push(CollisionEnd {
340 collider1: event.collider2,
341 collider2: event.collider1,
342 body1: event.body2,
343 body2: event.body1,
344 });
345 }
346 }
347
348 started.drain(..).for_each(|event| {
350 world.trigger(event);
351 });
352 ended.drain(..).for_each(|event| {
353 world.trigger(event);
354 });
355}
356
357fn remove_collider(
382 entity: Entity,
383 contact_graph: &mut ContactGraph,
384 contact_status_changes: &mut ContactStatusChangeQueue,
385 colliding_entities_query: &mut Query<&mut CollidingEntities, Allow<Disabled>>,
386 message_writer: &mut MessageWriter<CollisionEnd>,
387) {
388 contact_graph.remove_collider_with(entity, |contact_graph, contact_id| {
389 let contact_edge = contact_graph.edge_weight(contact_id.into()).unwrap();
391
392 if !contact_edge.flags.contains(ContactEdgeFlags::TOUCHING) {
394 return;
395 }
396
397 if contact_edge
399 .flags
400 .contains(ContactEdgeFlags::CONTACT_EVENTS)
401 {
402 message_writer.write(CollisionEnd {
403 collider1: contact_edge.collider1,
404 collider2: contact_edge.collider2,
405 body1: contact_edge.body1,
406 body2: contact_edge.body2,
407 });
408 }
409
410 let other_entity = if contact_edge.collider1 == entity {
412 contact_edge.collider2
413 } else {
414 contact_edge.collider1
415 };
416 if let Ok(mut colliding_entities) = colliding_entities_query.get_mut(other_entity) {
417 colliding_entities.remove(&entity);
418 }
419
420 if let (Some(body1), Some(body2)) = (contact_edge.body1, contact_edge.body2) {
421 contact_status_changes.push(ContactStatusChange::StoppedGeneratingConstraints {
422 contact_id,
423 body1,
424 body2,
425 });
426 }
427 });
428}
429
430fn remove_body_on<E: EventPattern<Event: EntityEvent>>(
432 trigger: On<E>,
433 body_collider_query: Query<&RigidBodyColliders>,
434 mut colliding_entities_query: Query<&mut CollidingEntities, Allow<Disabled>>,
435 mut message_writer: MessageWriter<CollisionEnd>,
436 mut contact_status_changes: ResMut<ContactStatusChangeQueue>,
437 mut contact_graph: ResMut<ContactGraph>,
438) {
439 let Ok(colliders) = body_collider_query.get(trigger.event_target()) else {
440 return;
441 };
442
443 for collider in colliders {
445 remove_collider(
446 collider,
447 &mut contact_graph,
448 &mut contact_status_changes,
449 &mut colliding_entities_query,
450 &mut message_writer,
451 );
452 }
453}
454
455fn remove_collider_on<E: EventPattern<Event: EntityEvent>>(
460 trigger: On<E>,
461 mut contact_graph: ResMut<ContactGraph>,
462 mut contact_status_changes: ResMut<ContactStatusChangeQueue>,
463 mut query: Query<&mut CollidingEntities, Allow<Disabled>>,
464 mut message_writer: MessageWriter<CollisionEnd>,
465) {
466 let entity = trigger.event_target();
467
468 remove_collider(
470 entity,
471 &mut contact_graph,
472 &mut contact_status_changes,
473 &mut query,
474 &mut message_writer,
475 );
476}
477
478fn on_body_remove_rigid_body_disabled(
481 trigger: On<Remove<RigidBodyDisabled>>,
482 body_collider_query: Query<&RigidBodyColliders>,
483 mut contact_status_changes: ResMut<ContactStatusChangeQueue>,
484 mut contact_graph: ResMut<ContactGraph>,
485 mut colliding_entities_query: Query<&mut CollidingEntities, Allow<Disabled>>,
486 mut message_writer: MessageWriter<CollisionEnd>,
487) {
488 let Ok(colliders) = body_collider_query.get(trigger.entity) else {
489 return;
490 };
491
492 for collider in colliders {
493 remove_collider(
494 collider,
495 &mut contact_graph,
496 &mut contact_status_changes,
497 &mut colliding_entities_query,
498 &mut message_writer,
499 );
500 }
501}
502
503fn on_disable_body(
506 trigger: On<Add<(Disabled, RigidBodyDisabled)>>,
507 body_collider_query: Query<&RigidBodyColliders, Allow<Disabled>>,
508 mut contact_status_changes: ResMut<ContactStatusChangeQueue>,
509 mut contact_graph: ResMut<ContactGraph>,
510 mut colliding_entities_query: Query<&mut CollidingEntities, Allow<Disabled>>,
511 mut message_writer: MessageWriter<CollisionEnd>,
512) {
513 let Ok(colliders) = body_collider_query.get(trigger.entity) else {
514 return;
515 };
516
517 for collider in colliders {
518 remove_collider(
519 collider,
520 &mut contact_graph,
521 &mut contact_status_changes,
522 &mut colliding_entities_query,
523 &mut message_writer,
524 );
525 }
526}
527
528fn on_add_sensor(
534 trigger: On<Add<Sensor>>,
535 mut contact_status_changes: ResMut<ContactStatusChangeQueue>,
536 mut contact_graph: ResMut<ContactGraph>,
537 mut colliding_entities_query: Query<&mut CollidingEntities, Allow<Disabled>>,
538 mut message_writer: MessageWriter<CollisionEnd>,
539) {
540 remove_collider(
541 trigger.entity,
542 &mut contact_graph,
543 &mut contact_status_changes,
544 &mut colliding_entities_query,
545 &mut message_writer,
546 );
547}
548
549fn on_remove_sensor(
552 trigger: On<Remove<Sensor>>,
553 mut contact_status_changes: ResMut<ContactStatusChangeQueue>,
554 mut contact_graph: ResMut<ContactGraph>,
555 mut colliding_entities_query: Query<&mut CollidingEntities, Allow<Disabled>>,
556 mut message_writer: MessageWriter<CollisionEnd>,
557) {
558 remove_collider(
559 trigger.entity,
560 &mut contact_graph,
561 &mut contact_status_changes,
562 &mut colliding_entities_query,
563 &mut message_writer,
564 );
565}