rapier2d/geometry/narrow_phase/pair_management.rs
1//! Contact/intersection pair lifecycle: collider removal, user-change handling,
2//! pair insertion/removal in the interaction graphs (with persistent
3//! solver-structure mirroring), and broad-phase event registration.
4
5use super::{
6 ColliderGraphIndices, NarrowPhase, PairRemovalMode, assign_pair_solver_color,
7 clear_pair_solver_color,
8};
9use crate::alloc_prelude::*;
10use crate::dynamics::solver::solver_contact_graph::GraphPos;
11use crate::dynamics::{IslandManager, RigidBodySet};
12use crate::geometry::{
13 BroadPhasePairEvent, ColliderChanges, ColliderGraphIndex, ColliderHandle, ColliderPair,
14 ColliderSet, CollisionEvent, ContactManifoldData, ContactPair, InteractionGraph,
15 IntersectionPair, PairEventStatus,
16};
17use crate::pipeline::{ActiveEvents, EventHandler};
18use crate::prelude::CollisionEventFlags;
19use parry::utils::hashmap::HashMap;
20
21impl NarrowPhase {
22 /// Maintain the narrow-phase internal state by taking collider removal into account.
23 #[profiling::function]
24 pub fn handle_user_changes(
25 &mut self,
26 mut islands: Option<&mut IslandManager>,
27 modified_colliders: &[ColliderHandle],
28 removed_colliders: &[ColliderHandle],
29 colliders: &mut ColliderSet,
30 bodies: &mut RigidBodySet,
31 events: &dyn EventHandler,
32 ) {
33 // TODO: avoid these hash-maps.
34 // They are necessary to handle the swap-remove done internally
35 // by the contact/intersection graphs when a node is removed.
36 let mut prox_id_remap = HashMap::default();
37 let mut contact_id_remap = HashMap::default();
38
39 for collider in removed_colliders {
40 // NOTE: if the collider does not have any graph indices currently, there is nothing
41 // to remove in the narrow-phase for this collider.
42 if let Some(graph_idx) = self
43 .graph_indices
44 .remove(collider.0, ColliderGraphIndices::invalid())
45 {
46 let intersection_graph_id = prox_id_remap
47 .get(collider)
48 .copied()
49 .unwrap_or(graph_idx.intersection_graph_index);
50 let contact_graph_id = contact_id_remap
51 .get(collider)
52 .copied()
53 .unwrap_or(graph_idx.contact_graph_index);
54
55 self.remove_collider(
56 intersection_graph_id,
57 contact_graph_id,
58 islands.as_deref_mut(),
59 colliders,
60 bodies,
61 &mut prox_id_remap,
62 &mut contact_id_remap,
63 events,
64 );
65 }
66 }
67
68 self.handle_user_changes_on_colliders(
69 islands,
70 modified_colliders,
71 colliders,
72 bodies,
73 events,
74 );
75 }
76
77 #[profiling::function]
78 pub(crate) fn remove_collider(
79 &mut self,
80 intersection_graph_id: ColliderGraphIndex,
81 contact_graph_id: ColliderGraphIndex,
82 mut islands: Option<&mut IslandManager>,
83 colliders: &mut ColliderSet,
84 bodies: &mut RigidBodySet,
85 prox_id_remap: &mut HashMap<ColliderHandle, ColliderGraphIndex>,
86 contact_id_remap: &mut HashMap<ColliderHandle, ColliderGraphIndex>,
87 events: &dyn EventHandler,
88 ) {
89 // Wake up every body in contact with the deleted collider and generate Stopped collision events.
90 if let Some(islands) = islands.as_deref_mut() {
91 for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
92 if let Some(parent) = colliders.get(a).and_then(|c| c.parent.as_ref()) {
93 islands.wake_up(bodies, parent.handle, true)
94 }
95
96 if let Some(parent) = colliders.get(b).and_then(|c| c.parent.as_ref()) {
97 islands.wake_up(bodies, parent.handle, true)
98 }
99
100 if pair
101 .event_status
102 .contains(PairEventStatus::START_EVENT_EMITTED)
103 {
104 events.handle_collision_event(
105 bodies,
106 colliders,
107 CollisionEvent::Stopped(a, b, CollisionEventFlags::REMOVED),
108 Some(pair),
109 );
110 }
111 }
112 } else {
113 // If there is no island, don’t wake-up bodies, but do send the Stopped collision event.
114 for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
115 if pair
116 .event_status
117 .contains(PairEventStatus::START_EVENT_EMITTED)
118 {
119 events.handle_collision_event(
120 bodies,
121 colliders,
122 CollisionEvent::Stopped(a, b, CollisionEventFlags::REMOVED),
123 Some(pair),
124 );
125 }
126 }
127 }
128
129 // Generate Stopped collision events for intersections.
130 for (a, b, pair) in self
131 .intersection_graph
132 .interactions_with(intersection_graph_id)
133 {
134 if pair.start_event_emitted {
135 events.handle_collision_event(
136 bodies,
137 colliders,
138 CollisionEvent::Stopped(
139 a,
140 b,
141 CollisionEventFlags::REMOVED | CollisionEventFlags::SENSOR,
142 ),
143 None,
144 );
145 }
146 }
147
148 // We have to manage the fact that one other collider will
149 // have its graph index changed because of the node's swap-remove.
150 if let Some(replacement) = self.intersection_graph.remove_node(intersection_graph_id) {
151 if let Some(replacement) = self.graph_indices.get_mut(replacement.0) {
152 replacement.intersection_graph_index = intersection_graph_id;
153 } else {
154 prox_id_remap.insert(replacement, intersection_graph_id);
155 // I feel like this should never happen now that the narrow-phase is the one owning
156 // the graph_indices. Let's put an unreachable in there and see if anybody still manages
157 // to reach it. If nobody does, we will remove this.
158 unreachable!();
159 }
160 }
161
162 // Node removal swap-removes contact-graph edges, shifting the edge indices the
163 // solver graph's `ContactRef`s and the force-event list are keyed by (neither is
164 // mirrored through node removal), so both must be rebuilt from scratch next step.
165 self.solver_graph_valid = false;
166 self.force_list_valid = false;
167 let pair_solver_hints = &mut self.pair_solver_hints;
168 // Persistent islands can't be rebuilt lazily like the tables above:
169 // unlink each removed edge and mirror each swap-remove exactly.
170 let mut edges_len = self.contact_graph.graph.edges.len();
171 let mut pi = islands.map(|i| &mut i.persistent);
172 if let Some(replacement) = self
173 .contact_graph
174 .remove_node_with(contact_graph_id, &mut |e| {
175 // Mirror the edges vec's swap_remove on the solver hints (on length
176 // mismatch — deserialized state — drop them; they rebuild lazily).
177 if e.index() < pair_solver_hints.len() {
178 pair_solver_hints.swap_remove(e.index());
179 } else {
180 pair_solver_hints.clear();
181 }
182
183 edges_len -= 1;
184 if let Some(pi) = pi.as_deref_mut() {
185 pi.unlink_contact(e.index() as u32);
186 pi.contact_edge_removed(e.index() as u32, edges_len as u32);
187 }
188 })
189 {
190 if let Some(replacement) = self.graph_indices.get_mut(replacement.0) {
191 replacement.contact_graph_index = contact_graph_id;
192 } else {
193 contact_id_remap.insert(replacement, contact_graph_id);
194 // I feel like this should never happen now that the narrow-phase is the one owning
195 // the graph_indices. Let's put an unreachable in there and see if anybody still manages
196 // to reach it. If nobody does, we will remove this.
197 unreachable!();
198 }
199 }
200 }
201
202 #[profiling::function]
203 pub(crate) fn handle_user_changes_on_colliders(
204 &mut self,
205 mut islands: Option<&mut IslandManager>,
206 modified_colliders: &[ColliderHandle],
207 colliders: &ColliderSet,
208 bodies: &mut RigidBodySet,
209 events: &dyn EventHandler,
210 ) {
211 let mut pairs_to_remove = vec![];
212
213 for handle in modified_colliders {
214 // NOTE: we use `get` because the collider may no longer
215 // exist if it has been removed.
216 if let Some(co) = colliders.get(*handle) {
217 // Any user modification can flip the contact-force-event config
218 // (`ActiveEvents`/threshold have no change flag), so flag its pairs for
219 // force-event membership reconciliation at the next graph maintenance.
220 if let Some(gid) = self.graph_indices.get(handle.0) {
221 if InteractionGraph::<ColliderHandle, ContactPair>::is_graph_index_valid(
222 gid.contact_graph_index,
223 ) {
224 for edge in self.contact_graph.graph.edges(gid.contact_graph_index) {
225 self.force_event_flagged.push(edge.id().index() as u32);
226 }
227 }
228 }
229
230 if !co.changes.needs_narrow_phase_update() {
231 // No flag relevant to the narrow-phase is enabled for this collider.
232 continue;
233 }
234
235 if let Some(gid) = self.graph_indices.get(handle.0) {
236 // For each modified colliders, we need to wake-up the bodies it is in contact with
237 // so that the narrow-phase properly takes into account the change in, e.g.,
238 // collision groups. Waking up the modified collider's parent isn't enough because
239 // it could be a fixed or kinematic body which don't propagate the wake-up state.
240 if let Some(islands) = islands.as_deref_mut() {
241 if let Some(co_parent) = &co.parent {
242 islands.wake_up(bodies, co_parent.handle, true);
243 }
244
245 for inter in self
246 .contact_graph
247 .interactions_with(gid.contact_graph_index)
248 {
249 let other_handle = if *handle == inter.0 { inter.1 } else { inter.0 };
250 let other_parent = colliders
251 .get(other_handle)
252 .and_then(|co| co.parent.as_ref());
253
254 if let Some(other_parent) = other_parent {
255 islands.wake_up(bodies, other_parent.handle, true);
256 }
257 }
258 }
259
260 // A parent or effective-dominance change (re-parenting, body type
261 // change) invalidates the solver graph colors of this collider's
262 // pairs: release and re-assign them with the current bodies.
263 if co.changes.intersects(
264 ColliderChanges::PARENT | ColliderChanges::PARENT_EFFECTIVE_DOMINANCE,
265 ) {
266 let mut edges_to_recolor = alloc::vec::Vec::new();
267 for edge in self.contact_graph.graph.edges(gid.contact_graph_index) {
268 edges_to_recolor.push(edge.id().index());
269 }
270
271 for edge_id in edges_to_recolor {
272 let pair = &mut self.contact_graph.graph.edges[edge_id].weight;
273 clear_pair_solver_color(&mut self.body_solver_color_masks, pair);
274
275 let touching = pair.has_any_active_contact();
276 if touching {
277 let body_info = |co: ColliderHandle| {
278 colliders
279 .get(co)
280 .and_then(|co| co.parent.as_ref())
281 .map(|p| {
282 let rb = &bodies[p.handle];
283 (p.handle.into_raw_parts().0, rb.is_fixed())
284 })
285 };
286 let info1 = body_info(pair.collider1);
287 let info2 = body_info(pair.collider2);
288 assign_pair_solver_color(
289 &mut self.body_solver_color_masks,
290 pair,
291 info1,
292 info2,
293 );
294 }
295
296 // Persistent islands: after re-parenting or a body type change,
297 // a link recorded with the old endpoints may no longer describe
298 // connectivity (a link to a fixed body doesn't connect). Refresh it.
299 if let Some(islands) = islands.as_deref_mut() {
300 let parent = |co: ColliderHandle| {
301 colliders.get(co).and_then(|c| c.parent.map(|p| p.handle))
302 };
303 let pair = &self.contact_graph.graph.edges[edge_id].weight;
304 let (co1, co2) = (pair.collider1, pair.collider2);
305 islands.persistent.unlink_contact(edge_id as u32);
306 if touching {
307 islands.persistent.link_contact(
308 bodies,
309 edge_id as u32,
310 parent(co1),
311 parent(co2),
312 );
313 }
314 }
315 }
316 }
317
318 // For each collider which had their sensor status modified, we need
319 // to transfer their contact/intersection graph edges to the intersection/contact graph.
320 // To achieve this we will remove the relevant contact/intersection pairs form the
321 // contact/intersection graphs, and then add them into the other graph.
322 if co.changes.intersects(ColliderChanges::TYPE) {
323 if co.is_sensor() {
324 // Find the contact pairs for this collider and
325 // push them to `pairs_to_remove`.
326 for inter in self
327 .contact_graph
328 .interactions_with(gid.contact_graph_index)
329 {
330 pairs_to_remove.push((
331 ColliderPair::new(inter.0, inter.1),
332 PairRemovalMode::FromContactGraph,
333 ));
334 }
335 } else {
336 // Find the contact pairs for this collider and
337 // push them to `pairs_to_remove` if both involved
338 // colliders are not sensors.
339 for inter in self
340 .intersection_graph
341 .interactions_with(gid.intersection_graph_index)
342 .filter(|(h1, h2, _)| {
343 !colliders[*h1].is_sensor() && !colliders[*h2].is_sensor()
344 })
345 {
346 pairs_to_remove.push((
347 ColliderPair::new(inter.0, inter.1),
348 PairRemovalMode::FromIntersectionGraph,
349 ));
350 }
351 }
352 }
353
354 // NOTE: if a collider only changed parent, we don’t need to remove it from any
355 // of the graphs as re-parenting doesn’t change the sensor status of a
356 // collider. If needed, their collision/intersection data will be
357 // updated/removed automatically in the contact or intersection update
358 // functions.
359 }
360 }
361 }
362
363 // Remove the pair from the relevant graph.
364 for pair in &pairs_to_remove {
365 self.remove_pair(
366 islands.as_deref_mut(),
367 colliders,
368 bodies,
369 &pair.0,
370 events,
371 pair.1,
372 );
373 }
374
375 // Add the removed pair to the relevant graph.
376 for pair in pairs_to_remove {
377 self.add_pair(colliders, &pair.0);
378 }
379 }
380
381 #[profiling::function]
382 fn remove_pair(
383 &mut self,
384 mut islands: Option<&mut IslandManager>,
385 colliders: &ColliderSet,
386 bodies: &mut RigidBodySet,
387 pair: &ColliderPair,
388 events: &dyn EventHandler,
389 mode: PairRemovalMode,
390 ) {
391 if let (Some(co1), Some(co2)) =
392 (colliders.get(pair.collider1), colliders.get(pair.collider2))
393 {
394 // TODO: could we just unwrap here?
395 // Don't we have the guarantee that we will get a `AddPair` before a `DeletePair`?
396 if let (Some(gid1), Some(gid2)) = (
397 self.graph_indices.get(pair.collider1.0),
398 self.graph_indices.get(pair.collider2.0),
399 ) {
400 if mode == PairRemovalMode::FromIntersectionGraph
401 || (mode == PairRemovalMode::Auto && (co1.is_sensor() || co2.is_sensor()))
402 {
403 let intersection = self
404 .intersection_graph
405 .remove_edge(gid1.intersection_graph_index, gid2.intersection_graph_index);
406
407 // Emit an intersection lost event if we had an intersection before removing the edge.
408 if let Some(mut intersection) = intersection {
409 if intersection.intersecting
410 && (co1.flags.active_events | co2.flags.active_events)
411 .contains(ActiveEvents::COLLISION_EVENTS)
412 {
413 intersection.emit_stop_event(
414 bodies,
415 colliders,
416 pair.collider1,
417 pair.collider2,
418 events,
419 )
420 }
421 }
422 } else {
423 // O(1) maintenance of the persistent solver structures through the edges vec's
424 // swap-remove (with stored-position fixup) — a global rebuild
425 // is O(all pairs). If a rebuild is pending, `ContactRef`/`graph_pos` are garbage: skip it.
426 let solver_graph_valid = self.solver_graph_valid;
427 let graph = &mut self.solver_contact_graph;
428 let force_list = &mut self.force_event_pairs;
429 let force_pos = &mut self.force_event_pos;
430 let pair_solver_hints = &mut self.pair_solver_hints;
431 let num_edges = self.contact_graph.graph.edges.len();
432 let edges_ptr = self.contact_graph.graph.edges.as_mut_ptr();
433 let mut removed_index = usize::MAX;
434 let mut consistent = true;
435 let contact_pair = self.contact_graph.remove_edge_with(
436 gid1.contact_graph_index,
437 gid2.contact_graph_index,
438 &mut |e| {
439 removed_index = e.index();
440 // Mirror the edges vec's swap_remove on the solver hints
441 // (on length mismatch — deserialized state — drop them
442 // and fall back to a full solver-graph rebuild).
443 if e.index() < pair_solver_hints.len() {
444 pair_solver_hints.swap_remove(e.index());
445 } else {
446 pair_solver_hints.clear();
447 consistent = false;
448 }
449
450 if e.index() >= num_edges {
451 consistent = false;
452 return;
453 }
454
455 // Pull the removed pair's manifolds out of the buckets. Raw ordinal
456 // access: the fixup may rewrite another manifold of this same pair via
457 // `edges_ptr`. SAFETY: runs before the swap_remove (indices live); single-threaded.
458 if solver_graph_valid {
459 unsafe {
460 let pair: *mut ContactPair =
461 &mut (*edges_ptr.add(e.index())).weight;
462 let sm = (*pair).solver_manifolds_mut();
463 let (sm_ptr, num) = (sm.as_mut_ptr(), sm.len());
464 for ordinal in 0..num {
465 let mdata: *mut ContactManifoldData =
466 &mut (*sm_ptr.add(ordinal)).data;
467 let pos = (*mdata).graph_pos;
468 if pos.is_some() {
469 Self::remove_and_fixup(graph, edges_ptr, pos);
470 (*mdata).graph_pos = GraphPos::NONE;
471 }
472 }
473 }
474 }
475
476 // Force-event list: drop the removed pair's membership, then
477 // mirror the swap-remove on the back-ref array (grown at pair
478 // add so it matches the edges vec; anything else = deserialized).
479 if force_pos.len() == num_edges {
480 let cur = force_pos[e.index()];
481 if cur != u32::MAX {
482 force_list.swap_remove(cur as usize);
483 if (cur as usize) < force_list.len() {
484 force_pos[force_list[cur as usize] as usize] = cur;
485 }
486 force_pos[e.index()] = u32::MAX;
487 }
488 force_pos.swap_remove(e.index());
489 } else {
490 force_pos.clear();
491 force_list.clear();
492 consistent = false;
493 }
494 },
495 );
496
497 if !consistent {
498 // Deserialized/degenerate bookkeeping: rebuild from scratch.
499 self.solver_graph_valid = false;
500 self.force_list_valid = false;
501 } else if removed_index < self.contact_graph.graph.edges.len() {
502 // A pair was swap-moved into the removed slot: rewrite the
503 // edge index its persistent entries are keyed by.
504 let new_edge = removed_index as u32;
505 let moved = &mut self.contact_graph.graph.edges[removed_index].weight;
506 let graph = &mut self.solver_contact_graph;
507 // Same rebuild-pending gate as the removal loop above:
508 // stale `graph_pos` back-refs must not drive bucket writes.
509 if solver_graph_valid {
510 for m in moved.solver_manifolds_mut() {
511 if m.data.graph_pos.is_some() {
512 graph.rewrite_edge(m.data.graph_pos, new_edge);
513 }
514 }
515 }
516 let fpos = self
517 .force_event_pos
518 .get(removed_index)
519 .copied()
520 .unwrap_or(u32::MAX);
521 if fpos != u32::MAX {
522 self.force_event_pairs[fpos as usize] = new_edge;
523 }
524 }
525
526 // Persistent islands: unlink the removed pair (if it was
527 // touching) and mirror the edges-vec swap-remove on the
528 // link-location table.
529 if removed_index != usize::MAX && num_edges > 0 {
530 if let Some(islands) = islands.as_deref_mut() {
531 islands.persistent.unlink_contact(removed_index as u32);
532 islands
533 .persistent
534 .contact_edge_removed(removed_index as u32, num_edges as u32 - 1);
535 }
536 }
537
538 // Emit a contact stopped event if we had a contact before removing the edge.
539 // Also wake up the dynamic bodies that were in contact.
540 if let Some(mut ctct) = contact_pair {
541 clear_pair_solver_color(&mut self.body_solver_color_masks, &mut ctct);
542
543 if ctct.has_any_active_contact() {
544 if let Some(islands) = islands {
545 if let Some(co_parent1) = &co1.parent {
546 islands.wake_up(bodies, co_parent1.handle, true);
547 }
548
549 if let Some(co_parent2) = co2.parent {
550 islands.wake_up(bodies, co_parent2.handle, true);
551 }
552 }
553
554 if (co1.flags.active_events | co2.flags.active_events)
555 .contains(ActiveEvents::COLLISION_EVENTS)
556 {
557 ctct.emit_stop_event(bodies, colliders, events);
558 }
559 }
560
561 // Retire the pair for reuse by `add_pair` (bounded pool).
562 if self.retired_pairs.len() < 2048 {
563 self.retired_pairs.push(ctct);
564 }
565 }
566 }
567 }
568 }
569 }
570
571 #[profiling::function]
572 fn add_pair(&mut self, colliders: &ColliderSet, pair: &ColliderPair) {
573 if let (Some(co1), Some(co2)) =
574 (colliders.get(pair.collider1), colliders.get(pair.collider2))
575 {
576 // These colliders have no parents - continue.
577
578 let (gid1, gid2) = self.graph_indices.ensure_pair_exists(
579 pair.collider1.0,
580 pair.collider2.0,
581 ColliderGraphIndices::invalid(),
582 );
583
584 if co1.is_sensor() || co2.is_sensor() {
585 // NOTE: the collider won't have a graph index as long
586 // as it does not interact with anything.
587 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid1.intersection_graph_index)
588 {
589 gid1.intersection_graph_index =
590 self.intersection_graph.graph.add_node(pair.collider1);
591 }
592
593 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid2.intersection_graph_index)
594 {
595 gid2.intersection_graph_index =
596 self.intersection_graph.graph.add_node(pair.collider2);
597 }
598
599 if self
600 .intersection_graph
601 .graph
602 .find_edge(gid1.intersection_graph_index, gid2.intersection_graph_index)
603 .is_none()
604 {
605 let _ = self.intersection_graph.add_edge(
606 gid1.intersection_graph_index,
607 gid2.intersection_graph_index,
608 IntersectionPair::new(),
609 );
610 }
611 } else {
612 // NOTE: same code as above, but for the contact graph.
613 // TODO: refactor both pieces of code somehow?
614
615 // NOTE: the collider won't have a graph index as long
616 // as it does not interact with anything.
617 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid1.contact_graph_index) {
618 gid1.contact_graph_index = self.contact_graph.graph.add_node(pair.collider1);
619 }
620
621 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid2.contact_graph_index) {
622 gid2.contact_graph_index = self.contact_graph.graph.add_node(pair.collider2);
623 }
624
625 if self
626 .contact_graph
627 .graph
628 .find_edge(gid1.contact_graph_index, gid2.contact_graph_index)
629 .is_none()
630 {
631 let interaction = if let Some(mut retired) = self.retired_pairs.pop() {
632 retired.reset_for_reuse(pair.collider1, pair.collider2);
633 retired
634 } else {
635 ContactPair::new(pair.collider1, pair.collider2)
636 };
637 // Keep the solver hints aligned with the edges vec at all times: add/remove
638 // events interleave within a step, so deferred growth would desync the swap_remove
639 // mirroring. On mismatch (deserialized), drop them — they rebuild lazily.
640 if self.pair_solver_hints.len() == self.contact_graph.graph.edges.len() {
641 self.pair_solver_hints.push(0);
642 } else {
643 self.pair_solver_hints.clear();
644 }
645 // Same for the force-event back-ref array: the O(1) removal maintenance
646 // mirrors the edges vec's swap_remove on it, so it must track the edge
647 // count exactly (not lazily at the next maintenance).
648 if self.force_event_pos.len() == self.contact_graph.graph.edges.len() {
649 self.force_event_pos.push(u32::MAX);
650 } else {
651 self.force_event_pos.clear();
652 self.force_event_pairs.clear();
653 self.force_list_valid = false;
654 }
655 let _ = self.contact_graph.add_edge(
656 gid1.contact_graph_index,
657 gid2.contact_graph_index,
658 interaction,
659 );
660 }
661 }
662 }
663 }
664
665 pub(crate) fn register_pairs(
666 &mut self,
667 mut islands: Option<&mut IslandManager>,
668 colliders: &ColliderSet,
669 bodies: &mut RigidBodySet,
670 broad_phase_events: &[BroadPhasePairEvent],
671 events: &dyn EventHandler,
672 ) {
673 for event in broad_phase_events {
674 match event {
675 BroadPhasePairEvent::AddPair(pair) => {
676 self.add_pair(colliders, pair);
677 }
678 BroadPhasePairEvent::DeletePair(pair) => {
679 self.remove_pair(
680 islands.as_deref_mut(),
681 colliders,
682 bodies,
683 pair,
684 events,
685 PairRemovalMode::Auto,
686 );
687 }
688 }
689 }
690 }
691}