1mod system_param;
21use system_param::ContactStatusBits;
22pub use system_param::NarrowPhase;
23#[cfg(feature = "parallel")]
24use system_param::ThreadLocalContactStatusBits;
25
26use core::marker::PhantomData;
27
28use crate::{
29 dynamics::solver::{
30 ContactConstraints,
31 constraint_graph::ConstraintGraph,
32 islands::{BodyIslandNode, PhysicsIslands},
33 joint_graph::JointGraph,
34 },
35 prelude::*,
36};
37use bevy::{
38 ecs::{
39 entity_disabling::Disabled,
40 intern::Interned,
41 schedule::ScheduleLabel,
42 system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
43 },
44 prelude::*,
45};
46
47use super::{CollisionDiagnostics, contact_types::ContactEdgeFlags};
48
49pub 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::<ConstraintGraph>()
112 .init_resource::<JointGraph>()
113 .init_resource::<ContactStatusBits>()
114 .init_resource::<DefaultFriction>()
115 .init_resource::<DefaultRestitution>();
116
117 #[cfg(feature = "parallel")]
118 app.init_resource::<ThreadLocalContactStatusBits>();
119
120 app.add_message::<CollisionStart>()
121 .add_message::<CollisionEnd>();
122
123 if self.generate_constraints {
124 app.init_resource::<ContactConstraints>();
125 }
126
127 app.configure_sets(
129 self.schedule,
130 (
131 NarrowPhaseSystems::First,
132 NarrowPhaseSystems::Update,
133 NarrowPhaseSystems::Last,
134 )
135 .chain()
136 .in_set(PhysicsStepSystems::NarrowPhase),
137 );
138 app.configure_sets(
139 self.schedule,
140 CollisionEventSystems.in_set(PhysicsStepSystems::Finalize),
141 );
142
143 app.add_systems(
145 self.schedule,
146 update_narrow_phase::<C, H>
147 .in_set(NarrowPhaseSystems::Update)
148 .ambiguous_with_all(),
151 );
152
153 if !already_initialized {
154 app.add_observer(remove_collider_on::<Add, (Disabled, ColliderDisabled)>);
156 app.add_observer(remove_collider_on::<Remove, ColliderMarker>);
157
158 app.add_observer(on_add_sensor);
162 app.add_observer(on_remove_sensor);
163
164 app.add_observer(on_body_remove_rigid_body_disabled);
167 app.add_observer(on_disable_body);
168
169 app.add_observer(remove_body_on::<Insert, RigidBody>);
171 app.add_observer(remove_body_on::<Remove, RigidBody>);
172
173 app.add_systems(
175 self.schedule,
176 trigger_collision_events
177 .in_set(CollisionEventSystems)
178 .ambiguous_with(PhysicsStepSystems::Finalize),
181 );
182 }
183
184 app.init_resource::<NarrowPhaseInitialized>();
185 }
186
187 fn finish(&self, app: &mut App) {
188 app.register_physics_diagnostics::<CollisionDiagnostics>();
190 }
191}
192
193#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
198pub struct CollisionEventSystems;
199
200#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
202#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
203#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
204#[reflect(Debug, Resource, PartialEq)]
205pub struct NarrowPhaseConfig {
206 pub default_speculative_margin: Scalar,
223
224 pub contact_tolerance: Scalar,
237
238 pub match_contacts: bool,
247}
248
249impl Default for NarrowPhaseConfig {
250 fn default() -> Self {
251 Self {
252 default_speculative_margin: Scalar::MAX,
253 contact_tolerance: 0.005,
254 match_contacts: true,
255 }
256 }
257}
258
259#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
261pub enum NarrowPhaseSystems {
262 First,
264 Update,
266 Last,
268}
269
270#[deprecated(since = "0.4.0", note = "Renamed to `NarrowPhaseSystems`")]
272pub type NarrowPhaseSet = NarrowPhaseSystems;
273
274fn update_narrow_phase<C: AnyCollider, H: CollisionHooks + 'static>(
275 mut narrow_phase: NarrowPhase<C>,
276 mut collision_started_writer: MessageWriter<CollisionStart>,
277 mut collision_ended_writer: MessageWriter<CollisionEnd>,
278 time: Res<Time>,
279 hooks: StaticSystemParam<H>,
280 context: StaticSystemParam<C::Context>,
281 mut commands: ParallelCommands,
282 mut diagnostics: ResMut<CollisionDiagnostics>,
283) where
284 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
285{
286 let start = crate::utils::Instant::now();
287
288 narrow_phase.update::<H>(
289 &mut collision_started_writer,
290 &mut collision_ended_writer,
291 time.delta_seconds_adjusted(),
292 &hooks,
293 &context,
294 &mut commands,
295 );
296
297 diagnostics.narrow_phase = start.elapsed();
298 diagnostics.contact_count = narrow_phase.contact_graph.edges.edge_count() as u32;
299}
300
301#[derive(SystemParam)]
302struct TriggerCollisionEventsContext<'w, 's> {
303 query: Query<'w, 's, Has<CollisionEventsEnabled>>,
304 started: MessageReader<'w, 's, CollisionStart>,
305 ended: MessageReader<'w, 's, CollisionEnd>,
306}
307
308fn trigger_collision_events(
311 world: &mut World,
313 state: &mut SystemState<TriggerCollisionEventsContext>,
314 mut started: Local<Vec<CollisionStart>>,
316 mut ended: Local<Vec<CollisionEnd>>,
317) {
318 let mut state = state.get_mut(world);
319
320 for event in state.started.read() {
322 let Ok([events_enabled1, events_enabled2]) =
323 state.query.get_many([event.collider1, event.collider2])
324 else {
325 continue;
326 };
327
328 if events_enabled1 {
329 started.push(CollisionStart {
330 collider1: event.collider1,
331 collider2: event.collider2,
332 body1: event.body1,
333 body2: event.body2,
334 });
335 }
336 if events_enabled2 {
337 started.push(CollisionStart {
338 collider1: event.collider2,
339 collider2: event.collider1,
340 body1: event.body2,
341 body2: event.body1,
342 });
343 }
344 }
345
346 for event in state.ended.read() {
348 let Ok([events_enabled1, events_enabled2]) =
349 state.query.get_many([event.collider1, event.collider2])
350 else {
351 continue;
352 };
353
354 if events_enabled1 {
355 ended.push(CollisionEnd {
356 collider1: event.collider1,
357 collider2: event.collider2,
358 body1: event.body1,
359 body2: event.body2,
360 });
361 }
362 if events_enabled2 {
363 ended.push(CollisionEnd {
364 collider1: event.collider2,
365 collider2: event.collider1,
366 body1: event.body2,
367 body2: event.body1,
368 });
369 }
370 }
371
372 started.drain(..).for_each(|event| {
374 world.trigger(event);
375 });
376 ended.drain(..).for_each(|event| {
377 world.trigger(event);
378 });
379}
380
381fn remove_collider(
402 entity: Entity,
403 contact_graph: &mut ContactGraph,
404 joint_graph: &JointGraph,
405 constraint_graph: &mut ConstraintGraph,
406 mut islands: Option<&mut PhysicsIslands>,
407 body_islands: &mut Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
408 colliding_entities_query: &mut Query<
409 &mut CollidingEntities,
410 Or<(With<Disabled>, Without<Disabled>)>,
411 >,
412 message_writer: &mut MessageWriter<CollisionEnd>,
413) {
414 contact_graph.remove_collider_with(entity, |contact_graph, contact_id| {
416 let contact_edge = contact_graph.edge_weight(contact_id.into()).unwrap();
418
419 if !contact_edge.flags.contains(ContactEdgeFlags::TOUCHING) {
421 return;
422 }
423
424 if contact_edge
426 .flags
427 .contains(ContactEdgeFlags::CONTACT_EVENTS)
428 {
429 message_writer.write(CollisionEnd {
430 collider1: contact_edge.collider1,
431 collider2: contact_edge.collider2,
432 body1: contact_edge.body1,
433 body2: contact_edge.body2,
434 });
435 }
436
437 let other_entity = if contact_edge.collider1 == entity {
439 contact_edge.collider2
440 } else {
441 contact_edge.collider1
442 };
443 if let Ok(mut colliding_entities) = colliding_entities_query.get_mut(other_entity) {
444 colliding_entities.remove(&entity);
445 }
446
447 let has_island = contact_edge.island.is_some();
448
449 if let (Some(body1), Some(body2)) = (contact_edge.body1, contact_edge.body2) {
451 for _ in 0..contact_edge.constraint_handles.len() {
452 constraint_graph.pop_manifold(contact_graph, contact_id, body1, body2);
453 }
454 }
455
456 if has_island && let Some(ref mut islands) = islands {
458 islands.remove_contact(contact_id, body_islands, contact_graph, joint_graph);
459 }
460 });
461}
462
463fn remove_body_on<E: EntityEvent, B: Bundle>(
466 trigger: On<E, B>,
467 body_collider_query: Query<&RigidBodyColliders>,
468 mut colliding_entities_query: Query<
469 &mut CollidingEntities,
470 Or<(With<Disabled>, Without<Disabled>)>,
471 >,
472 mut message_writer: MessageWriter<CollisionEnd>,
473 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
474 mut islands: Option<ResMut<PhysicsIslands>>,
475 mut constraint_graph: ResMut<ConstraintGraph>,
476 mut contact_graph: ResMut<ContactGraph>,
477 joint_graph: ResMut<JointGraph>,
478 mut commands: Commands,
479) {
480 let Ok(colliders) = body_collider_query.get(trigger.event_target()) else {
481 return;
482 };
483
484 if let Ok(body_island) = body_islands.get_mut(trigger.event_target()) {
486 commands.queue(WakeIslands(vec![body_island.island_id]));
487 }
488
489 for collider in colliders {
491 remove_collider(
492 collider,
493 &mut contact_graph,
494 &joint_graph,
495 &mut constraint_graph,
496 islands.as_deref_mut(),
497 &mut body_islands,
498 &mut colliding_entities_query,
499 &mut message_writer,
500 );
501 }
502}
503
504fn remove_collider_on<E: EntityEvent, B: Bundle>(
509 trigger: On<E, B>,
510 mut contact_graph: ResMut<ContactGraph>,
511 joint_graph: ResMut<JointGraph>,
512 mut constraint_graph: ResMut<ConstraintGraph>,
513 mut islands: Option<ResMut<PhysicsIslands>>,
514 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
515 mut query: Query<&mut CollidingEntities, Or<(With<Disabled>, Without<Disabled>)>>,
517 collider_of: Query<&ColliderOf, Or<(With<Disabled>, Without<Disabled>)>>,
518 mut message_writer: MessageWriter<CollisionEnd>,
519 mut commands: Commands,
520) {
521 let entity = trigger.event_target();
522
523 let body1 = collider_of
524 .get(entity)
525 .map(|&ColliderOf { body }| body)
526 .ok();
527
528 if let Some(body) = body1
530 && let Ok(body_island) = body_islands.get_mut(body)
531 {
532 commands.queue(WakeIslands(vec![body_island.island_id]));
533 }
534
535 remove_collider(
537 entity,
538 &mut contact_graph,
539 &joint_graph,
540 &mut constraint_graph,
541 islands.as_deref_mut(),
542 &mut body_islands,
543 &mut query,
544 &mut message_writer,
545 );
546}
547
548fn on_body_remove_rigid_body_disabled(
551 trigger: On<Add, BodyIslandNode>,
552 body_collider_query: Query<&RigidBodyColliders>,
553 mut constraint_graph: ResMut<ConstraintGraph>,
554 mut contact_graph: ResMut<ContactGraph>,
555 joint_graph: ResMut<JointGraph>,
556 mut islands: Option<ResMut<PhysicsIslands>>,
557 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
558 mut colliding_entities_query: Query<
559 &mut CollidingEntities,
560 Or<(With<Disabled>, Without<Disabled>)>,
561 >,
562 mut message_writer: MessageWriter<CollisionEnd>,
563) {
564 let Ok(colliders) = body_collider_query.get(trigger.entity) else {
565 return;
566 };
567
568 for collider in colliders {
569 remove_collider(
570 collider,
571 &mut contact_graph,
572 &joint_graph,
573 &mut constraint_graph,
574 islands.as_deref_mut(),
575 &mut body_islands,
576 &mut colliding_entities_query,
577 &mut message_writer,
578 );
579 }
580}
581
582fn on_disable_body(
585 trigger: On<Add, (Disabled, RigidBodyDisabled)>,
586 body_collider_query: Query<&RigidBodyColliders, Or<(With<Disabled>, Without<Disabled>)>>,
587 mut constraint_graph: ResMut<ConstraintGraph>,
588 mut contact_graph: ResMut<ContactGraph>,
589 joint_graph: Res<JointGraph>,
590 mut islands: Option<ResMut<PhysicsIslands>>,
591 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
592 mut colliding_entities_query: Query<
593 &mut CollidingEntities,
594 Or<(With<Disabled>, Without<Disabled>)>,
595 >,
596 mut message_writer: MessageWriter<CollisionEnd>,
597) {
598 let Ok(colliders) = body_collider_query.get(trigger.entity) else {
599 return;
600 };
601
602 for collider in colliders {
603 remove_collider(
604 collider,
605 &mut contact_graph,
606 &joint_graph,
607 &mut constraint_graph,
608 islands.as_deref_mut(),
609 &mut body_islands,
610 &mut colliding_entities_query,
611 &mut message_writer,
612 );
613 }
614}
615
616fn on_add_sensor(
622 trigger: On<Add, Sensor>,
623 mut constraint_graph: ResMut<ConstraintGraph>,
624 mut contact_graph: ResMut<ContactGraph>,
625 joint_graph: Res<JointGraph>,
626 mut islands: Option<ResMut<PhysicsIslands>>,
627 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
628 mut colliding_entities_query: Query<
629 &mut CollidingEntities,
630 Or<(With<Disabled>, Without<Disabled>)>,
631 >,
632 mut message_writer: MessageWriter<CollisionEnd>,
633) {
634 remove_collider(
635 trigger.entity,
636 &mut contact_graph,
637 &joint_graph,
638 &mut constraint_graph,
639 islands.as_deref_mut(),
640 &mut body_islands,
641 &mut colliding_entities_query,
642 &mut message_writer,
643 );
644}
645
646fn on_remove_sensor(
649 trigger: On<Remove, Sensor>,
650 mut constraint_graph: ResMut<ConstraintGraph>,
651 mut contact_graph: ResMut<ContactGraph>,
652 joint_graph: ResMut<JointGraph>,
653 mut islands: Option<ResMut<PhysicsIslands>>,
654 mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
655 mut colliding_entities_query: Query<
656 &mut CollidingEntities,
657 Or<(With<Disabled>, Without<Disabled>)>,
658 >,
659 mut message_writer: MessageWriter<CollisionEnd>,
660) {
661 remove_collider(
662 trigger.entity,
663 &mut contact_graph,
664 &joint_graph,
665 &mut constraint_graph,
666 islands.as_deref_mut(),
667 &mut body_islands,
668 &mut colliding_entities_query,
669 &mut message_writer,
670 );
671}