1mod system_param;
6use system_param::ContactStatusBits;
7pub use system_param::NarrowPhase;
8#[cfg(feature = "parallel")]
9use system_param::ThreadLocalContactStatusBits;
10
11use core::marker::PhantomData;
12
13use crate::{
14 dynamics::solver::{
15 ContactConstraints,
16 constraint_graph::ConstraintGraph,
17 islands::{BodyIslandNode, PhysicsIslands},
18 joint_graph::JointGraph,
19 },
20 prelude::*,
21};
22use bevy::{
23 ecs::{
24 entity_disabling::Disabled,
25 intern::Interned,
26 schedule::ScheduleLabel,
27 system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
28 },
29 prelude::*,
30};
31
32use super::{CollisionDiagnostics, contact_types::ContactEdgeFlags};
33
34pub struct NarrowPhasePlugin<C: AnyCollider, H: CollisionHooks = ()> {
60 schedule: Interned<dyn ScheduleLabel>,
61 generate_constraints: bool,
68 _phantom: PhantomData<(C, H)>,
69}
70
71impl<C: AnyCollider, H: CollisionHooks> NarrowPhasePlugin<C, H> {
72 pub fn new(schedule: impl ScheduleLabel, generate_constraints: bool) -> Self {
81 Self {
82 schedule: schedule.intern(),
83 generate_constraints,
84 _phantom: PhantomData,
85 }
86 }
87}
88
89impl<C: AnyCollider, H: CollisionHooks> Default for NarrowPhasePlugin<C, H> {
90 fn default() -> Self {
91 Self::new(PhysicsSchedule, true)
92 }
93}
94
95#[derive(Resource, Default)]
100struct NarrowPhaseInitialized;
101
102impl<C: AnyCollider, H: CollisionHooks + 'static> Plugin for NarrowPhasePlugin<C, H>
103where
104 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
105{
106 fn build(&self, app: &mut App) {
107 let already_initialized = app.world().is_resource_added::<NarrowPhaseInitialized>();
108
109 app.init_resource::<NarrowPhaseConfig>()
110 .init_resource::<ContactGraph>()
111 .init_resource::<ContactStatusBits>()
112 .init_resource::<DefaultFriction>()
113 .init_resource::<DefaultRestitution>();
114
115 #[cfg(feature = "parallel")]
116 app.init_resource::<ThreadLocalContactStatusBits>();
117
118 app.add_message::<CollisionStart>()
119 .add_message::<CollisionEnd>();
120
121 if self.generate_constraints {
122 app.init_resource::<ContactConstraints>();
123 }
124
125 app.configure_sets(
127 self.schedule,
128 (
129 NarrowPhaseSystems::First,
130 NarrowPhaseSystems::Update,
131 NarrowPhaseSystems::Last,
132 )
133 .chain()
134 .in_set(PhysicsStepSystems::NarrowPhase),
135 );
136 app.configure_sets(
137 self.schedule,
138 CollisionEventSystems.in_set(PhysicsStepSystems::Finalize),
139 );
140
141 app.add_systems(
143 self.schedule,
144 update_narrow_phase::<C, H>
145 .in_set(NarrowPhaseSystems::Update)
146 .ambiguous_with_all(),
149 );
150
151 if !already_initialized {
152 app.add_observer(remove_collider_on::<Add, (Disabled, ColliderDisabled)>);
154 app.add_observer(remove_collider_on::<Remove, ColliderMarker>);
155
156 app.add_observer(on_add_sensor);
160 app.add_observer(on_remove_sensor);
161
162 app.add_observer(on_body_remove_rigid_body_disabled);
165 app.add_observer(on_disable_body);
166
167 app.add_observer(remove_body_on::<Insert, RigidBody>);
169 app.add_observer(remove_body_on::<Remove, RigidBody>);
170
171 app.add_systems(
173 self.schedule,
174 trigger_collision_events
175 .in_set(CollisionEventSystems)
176 .ambiguous_with(PhysicsStepSystems::Finalize),
179 );
180 }
181
182 app.init_resource::<NarrowPhaseInitialized>();
183 }
184
185 fn finish(&self, app: &mut App) {
186 app.register_physics_diagnostics::<CollisionDiagnostics>();
188 }
189}
190
191#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
196pub struct CollisionEventSystems;
197
198#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
200#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
201#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
202#[reflect(Debug, Resource, PartialEq)]
203pub struct NarrowPhaseConfig {
204 pub default_speculative_margin: Scalar,
221
222 pub contact_tolerance: Scalar,
235
236 pub match_contacts: bool,
245}
246
247impl Default for NarrowPhaseConfig {
248 fn default() -> Self {
249 Self {
250 default_speculative_margin: Scalar::MAX,
251 contact_tolerance: 0.005,
252 match_contacts: true,
253 }
254 }
255}
256
257#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
259pub enum NarrowPhaseSystems {
260 First,
262 Update,
264 Last,
266}
267
268#[deprecated(since = "0.4.0", note = "Renamed to `NarrowPhaseSystems`")]
270pub type NarrowPhaseSet = NarrowPhaseSystems;
271
272fn update_narrow_phase<C: AnyCollider, H: CollisionHooks + 'static>(
273 mut narrow_phase: NarrowPhase<C>,
274 mut collision_started_writer: MessageWriter<CollisionStart>,
275 mut collision_ended_writer: MessageWriter<CollisionEnd>,
276 time: Res<Time>,
277 hooks: StaticSystemParam<H>,
278 context: StaticSystemParam<C::Context>,
279 mut commands: ParallelCommands,
280 mut diagnostics: ResMut<CollisionDiagnostics>,
281) where
282 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
283{
284 let start = crate::utils::Instant::now();
285
286 narrow_phase.update::<H>(
287 &mut collision_started_writer,
288 &mut collision_ended_writer,
289 time.delta_seconds_adjusted(),
290 &hooks,
291 &context,
292 &mut commands,
293 );
294
295 diagnostics.narrow_phase = start.elapsed();
296 diagnostics.contact_count = narrow_phase.contact_graph.edges.edge_count() as u32;
297}
298
299#[derive(SystemParam)]
300struct TriggerCollisionEventsContext<'w, 's> {
301 query: Query<'w, 's, Has<CollisionEventsEnabled>>,
302 started: MessageReader<'w, 's, CollisionStart>,
303 ended: MessageReader<'w, 's, CollisionEnd>,
304}
305
306fn trigger_collision_events(
309 world: &mut World,
311 state: &mut SystemState<TriggerCollisionEventsContext>,
312 mut started: Local<Vec<CollisionStart>>,
314 mut ended: Local<Vec<CollisionEnd>>,
315) {
316 let mut state = state.get_mut(world);
317
318 for event in state.started.read() {
320 let Ok([events_enabled1, events_enabled2]) =
321 state.query.get_many([event.collider1, event.collider2])
322 else {
323 continue;
324 };
325
326 if events_enabled1 {
327 started.push(CollisionStart {
328 collider1: event.collider1,
329 collider2: event.collider2,
330 body1: event.body1,
331 body2: event.body2,
332 });
333 }
334 if events_enabled2 {
335 started.push(CollisionStart {
336 collider1: event.collider2,
337 collider2: event.collider1,
338 body1: event.body2,
339 body2: event.body1,
340 });
341 }
342 }
343
344 for event in state.ended.read() {
346 let Ok([events_enabled1, events_enabled2]) =
347 state.query.get_many([event.collider1, event.collider2])
348 else {
349 continue;
350 };
351
352 if events_enabled1 {
353 ended.push(CollisionEnd {
354 collider1: event.collider1,
355 collider2: event.collider2,
356 body1: event.body1,
357 body2: event.body2,
358 });
359 }
360 if events_enabled2 {
361 ended.push(CollisionEnd {
362 collider1: event.collider2,
363 collider2: event.collider1,
364 body1: event.body2,
365 body2: event.body1,
366 });
367 }
368 }
369
370 started.drain(..).for_each(|event| {
372 world.trigger(event);
373 });
374 ended.drain(..).for_each(|event| {
375 world.trigger(event);
376 });
377}
378
379fn remove_collider(
400 entity: Entity,
401 contact_graph: &mut ContactGraph,
402 joint_graph: &JointGraph,
403 constraint_graph: &mut ConstraintGraph,
404 mut islands: Option<&mut PhysicsIslands>,
405 body_islands: &mut Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
406 colliding_entities_query: &mut Query<
407 &mut CollidingEntities,
408 Or<(With<Disabled>, Without<Disabled>)>,
409 >,
410 message_writer: &mut MessageWriter<CollisionEnd>,
411) {
412 contact_graph.remove_collider_with(entity, |contact_graph, contact_id| {
414 let contact_edge = contact_graph.edge_weight(contact_id.into()).unwrap();
416
417 if !contact_edge.flags.contains(ContactEdgeFlags::TOUCHING) {
419 return;
420 }
421
422 if contact_edge
424 .flags
425 .contains(ContactEdgeFlags::CONTACT_EVENTS)
426 {
427 message_writer.write(CollisionEnd {
428 collider1: contact_edge.collider1,
429 collider2: contact_edge.collider2,
430 body1: contact_edge.body1,
431 body2: contact_edge.body2,
432 });
433 }
434
435 let other_entity = if contact_edge.collider1 == entity {
437 contact_edge.collider2
438 } else {
439 contact_edge.collider1
440 };
441 if let Ok(mut colliding_entities) = colliding_entities_query.get_mut(other_entity) {
442 colliding_entities.remove(&entity);
443 }
444
445 let has_island = contact_edge.island.is_some();
446
447 if let (Some(body1), Some(body2)) = (contact_edge.body1, contact_edge.body2) {
449 for _ in 0..contact_edge.constraint_handles.len() {
450 constraint_graph.pop_manifold(contact_graph, contact_id, body1, body2);
451 }
452 }
453
454 if has_island && let Some(ref mut islands) = islands {
456 islands.remove_contact(contact_id, body_islands, contact_graph, joint_graph);
457 }
458 });
459}
460
461fn remove_body_on<E: EntityEvent, B: Bundle>(
464 trigger: On<E, B>,
465 body_collider_query: Query<&RigidBodyColliders>,
466 mut colliding_entities_query: Query<
467 &mut CollidingEntities,
468 Or<(With<Disabled>, Without<Disabled>)>,
469 >,
470 mut message_writer: MessageWriter<CollisionEnd>,
471 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
472 mut islands: Option<ResMut<PhysicsIslands>>,
473 mut constraint_graph: ResMut<ConstraintGraph>,
474 mut contact_graph: ResMut<ContactGraph>,
475 joint_graph: ResMut<JointGraph>,
476 mut commands: Commands,
477) {
478 let Ok(colliders) = body_collider_query.get(trigger.event_target()) else {
479 return;
480 };
481
482 if let Ok(body_island) = body_islands.get_mut(trigger.event_target()) {
484 commands.queue(WakeIslands(vec![body_island.island_id]));
485 }
486
487 for collider in colliders {
489 remove_collider(
490 collider,
491 &mut contact_graph,
492 &joint_graph,
493 &mut constraint_graph,
494 islands.as_deref_mut(),
495 &mut body_islands,
496 &mut colliding_entities_query,
497 &mut message_writer,
498 );
499 }
500}
501
502fn remove_collider_on<E: EntityEvent, B: Bundle>(
507 trigger: On<E, B>,
508 mut contact_graph: ResMut<ContactGraph>,
509 joint_graph: ResMut<JointGraph>,
510 mut constraint_graph: ResMut<ConstraintGraph>,
511 mut islands: Option<ResMut<PhysicsIslands>>,
512 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
513 mut query: Query<&mut CollidingEntities, Or<(With<Disabled>, Without<Disabled>)>>,
515 collider_of: Query<&ColliderOf, Or<(With<Disabled>, Without<Disabled>)>>,
516 mut message_writer: MessageWriter<CollisionEnd>,
517 mut commands: Commands,
518) {
519 let entity = trigger.event_target();
520
521 let body1 = collider_of
522 .get(entity)
523 .map(|&ColliderOf { body }| body)
524 .ok();
525
526 if let Some(body) = body1
528 && let Ok(body_island) = body_islands.get_mut(body)
529 {
530 commands.queue(WakeIslands(vec![body_island.island_id]));
531 }
532
533 remove_collider(
535 entity,
536 &mut contact_graph,
537 &joint_graph,
538 &mut constraint_graph,
539 islands.as_deref_mut(),
540 &mut body_islands,
541 &mut query,
542 &mut message_writer,
543 );
544}
545
546fn on_body_remove_rigid_body_disabled(
549 trigger: On<Add, BodyIslandNode>,
550 body_collider_query: Query<&RigidBodyColliders>,
551 mut constraint_graph: ResMut<ConstraintGraph>,
552 mut contact_graph: ResMut<ContactGraph>,
553 joint_graph: ResMut<JointGraph>,
554 mut islands: Option<ResMut<PhysicsIslands>>,
555 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
556 mut colliding_entities_query: Query<
557 &mut CollidingEntities,
558 Or<(With<Disabled>, Without<Disabled>)>,
559 >,
560 mut message_writer: MessageWriter<CollisionEnd>,
561) {
562 let Ok(colliders) = body_collider_query.get(trigger.entity) else {
563 return;
564 };
565
566 for collider in colliders {
567 remove_collider(
568 collider,
569 &mut contact_graph,
570 &joint_graph,
571 &mut constraint_graph,
572 islands.as_deref_mut(),
573 &mut body_islands,
574 &mut colliding_entities_query,
575 &mut message_writer,
576 );
577 }
578}
579
580fn on_disable_body(
583 trigger: On<Add, (Disabled, RigidBodyDisabled)>,
584 body_collider_query: Query<&RigidBodyColliders, Or<(With<Disabled>, Without<Disabled>)>>,
585 mut constraint_graph: ResMut<ConstraintGraph>,
586 mut contact_graph: ResMut<ContactGraph>,
587 joint_graph: Res<JointGraph>,
588 mut islands: Option<ResMut<PhysicsIslands>>,
589 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
590 mut colliding_entities_query: Query<
591 &mut CollidingEntities,
592 Or<(With<Disabled>, Without<Disabled>)>,
593 >,
594 mut message_writer: MessageWriter<CollisionEnd>,
595) {
596 let Ok(colliders) = body_collider_query.get(trigger.entity) else {
597 return;
598 };
599
600 for collider in colliders {
601 remove_collider(
602 collider,
603 &mut contact_graph,
604 &joint_graph,
605 &mut constraint_graph,
606 islands.as_deref_mut(),
607 &mut body_islands,
608 &mut colliding_entities_query,
609 &mut message_writer,
610 );
611 }
612}
613
614fn on_add_sensor(
620 trigger: On<Add, Sensor>,
621 mut constraint_graph: ResMut<ConstraintGraph>,
622 mut contact_graph: ResMut<ContactGraph>,
623 joint_graph: Res<JointGraph>,
624 mut islands: Option<ResMut<PhysicsIslands>>,
625 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
626 mut colliding_entities_query: Query<
627 &mut CollidingEntities,
628 Or<(With<Disabled>, Without<Disabled>)>,
629 >,
630 mut message_writer: MessageWriter<CollisionEnd>,
631) {
632 remove_collider(
633 trigger.entity,
634 &mut contact_graph,
635 &joint_graph,
636 &mut constraint_graph,
637 islands.as_deref_mut(),
638 &mut body_islands,
639 &mut colliding_entities_query,
640 &mut message_writer,
641 );
642}
643
644fn on_remove_sensor(
647 trigger: On<Remove, Sensor>,
648 mut constraint_graph: ResMut<ConstraintGraph>,
649 mut contact_graph: ResMut<ContactGraph>,
650 joint_graph: ResMut<JointGraph>,
651 mut islands: Option<ResMut<PhysicsIslands>>,
652 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
653 mut colliding_entities_query: Query<
654 &mut CollidingEntities,
655 Or<(With<Disabled>, Without<Disabled>)>,
656 >,
657 mut message_writer: MessageWriter<CollisionEnd>,
658) {
659 remove_collider(
660 trigger.entity,
661 &mut contact_graph,
662 &joint_graph,
663 &mut constraint_graph,
664 islands.as_deref_mut(),
665 &mut body_islands,
666 &mut colliding_entities_query,
667 &mut message_writer,
668 );
669}