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 // A pure in-place deformation (soft-body surface) is simulation-driven, not a
241 // user change: it must not keep its island awake.
242 let deformed_only = (co.changes
243 & !(ColliderChanges::IN_MODIFIED_SET | ColliderChanges::DEFORMED))
244 .is_empty();
245 if let Some(islands) = islands.as_deref_mut().filter(|_| !deformed_only) {
246 if let Some(co_parent) = &co.parent {
247 islands.wake_up(bodies, co_parent.handle, true);
248 }
249
250 for inter in self
251 .contact_graph
252 .interactions_with(gid.contact_graph_index)
253 {
254 let other_handle = if *handle == inter.0 { inter.1 } else { inter.0 };
255 let other_parent = colliders
256 .get(other_handle)
257 .and_then(|co| co.parent.as_ref());
258
259 if let Some(other_parent) = other_parent {
260 islands.wake_up(bodies, other_parent.handle, true);
261 }
262 }
263 }
264
265 // A parent or effective-dominance change (re-parenting, body type
266 // change) invalidates the solver graph colors of this collider's
267 // pairs: release and re-assign them with the current bodies.
268 if co.changes.intersects(
269 ColliderChanges::PARENT | ColliderChanges::PARENT_EFFECTIVE_DOMINANCE,
270 ) {
271 let mut edges_to_recolor = alloc::vec::Vec::new();
272 for edge in self.contact_graph.graph.edges(gid.contact_graph_index) {
273 edges_to_recolor.push(edge.id().index());
274 }
275
276 for edge_id in edges_to_recolor {
277 let pair = &mut self.contact_graph.graph.edges[edge_id].weight;
278 clear_pair_solver_color(&mut self.body_solver_color_masks, pair);
279
280 let touching = pair.has_any_active_contact();
281 if touching {
282 let body_info = |co: ColliderHandle| {
283 colliders
284 .get(co)
285 .and_then(|co| co.parent.as_ref())
286 .map(|p| {
287 let rb = &bodies[p.handle];
288 (p.handle.into_raw_parts().0, rb.is_fixed())
289 })
290 };
291 let info1 = body_info(pair.collider1);
292 let info2 = body_info(pair.collider2);
293 assign_pair_solver_color(
294 &mut self.body_solver_color_masks,
295 pair,
296 info1,
297 info2,
298 );
299 }
300
301 // Persistent islands: after re-parenting or a body type change,
302 // a link recorded with the old endpoints may no longer describe
303 // connectivity (a link to a fixed body doesn't connect). Update it.
304 if let Some(islands) = islands.as_deref_mut() {
305 let parent = |co: ColliderHandle| {
306 colliders.get(co).and_then(|c| c.parent.map(|p| p.handle))
307 };
308 let pair = &self.contact_graph.graph.edges[edge_id].weight;
309 let (co1, co2) = (pair.collider1, pair.collider2);
310 islands.persistent.unlink_contact(edge_id as u32);
311 if touching {
312 islands.persistent.link_contact(
313 bodies,
314 edge_id as u32,
315 parent(co1),
316 parent(co2),
317 );
318 }
319 }
320 }
321 }
322
323 // For each collider which had their sensor status modified, we need
324 // to transfer their contact/intersection graph edges to the intersection/contact graph.
325 // To achieve this we will remove the relevant contact/intersection pairs form the
326 // contact/intersection graphs, and then add them into the other graph.
327 if co.changes.intersects(ColliderChanges::TYPE) {
328 if co.is_sensor() {
329 // Find the contact pairs for this collider and
330 // push them to `pairs_to_remove`.
331 for inter in self
332 .contact_graph
333 .interactions_with(gid.contact_graph_index)
334 {
335 pairs_to_remove.push((
336 ColliderPair::new(inter.0, inter.1),
337 PairRemovalMode::FromContactGraph,
338 ));
339 }
340 } else {
341 // Find the contact pairs for this collider and
342 // push them to `pairs_to_remove` if both involved
343 // colliders are not sensors.
344 for inter in self
345 .intersection_graph
346 .interactions_with(gid.intersection_graph_index)
347 .filter(|(h1, h2, _)| {
348 !colliders[*h1].is_sensor() && !colliders[*h2].is_sensor()
349 })
350 {
351 pairs_to_remove.push((
352 ColliderPair::new(inter.0, inter.1),
353 PairRemovalMode::FromIntersectionGraph,
354 ));
355 }
356 }
357 }
358
359 // NOTE: if a collider only changed parent, we don’t need to remove it from any
360 // of the graphs as re-parenting doesn’t change the sensor status of a
361 // collider. If needed, their collision/intersection data will be
362 // updated/removed automatically in the contact or intersection update
363 // functions.
364 }
365 }
366 }
367
368 // Remove the pair from the relevant graph.
369 for pair in &pairs_to_remove {
370 self.remove_pair(
371 islands.as_deref_mut(),
372 colliders,
373 bodies,
374 &pair.0,
375 events,
376 pair.1,
377 );
378 }
379
380 // Add the removed pair to the relevant graph.
381 for pair in pairs_to_remove {
382 self.add_pair(colliders, &pair.0);
383 }
384 }
385
386 #[profiling::function]
387 fn remove_pair(
388 &mut self,
389 mut islands: Option<&mut IslandManager>,
390 colliders: &ColliderSet,
391 bodies: &mut RigidBodySet,
392 pair: &ColliderPair,
393 events: &dyn EventHandler,
394 mode: PairRemovalMode,
395 ) {
396 if let (Some(co1), Some(co2)) =
397 (colliders.get(pair.collider1), colliders.get(pair.collider2))
398 {
399 // TODO: could we just unwrap here?
400 // Don't we have the guarantee that we will get a `AddPair` before a `DeletePair`?
401 if let (Some(gid1), Some(gid2)) = (
402 self.graph_indices.get(pair.collider1.0),
403 self.graph_indices.get(pair.collider2.0),
404 ) {
405 if mode == PairRemovalMode::FromIntersectionGraph
406 || (mode == PairRemovalMode::Auto && (co1.is_sensor() || co2.is_sensor()))
407 {
408 let intersection = self
409 .intersection_graph
410 .remove_edge(gid1.intersection_graph_index, gid2.intersection_graph_index);
411
412 // Emit an intersection lost event if we had an intersection before removing the edge.
413 if let Some(mut intersection) = intersection {
414 if intersection.intersecting
415 && (co1.flags.active_events | co2.flags.active_events)
416 .contains(ActiveEvents::COLLISION_EVENTS)
417 {
418 intersection.emit_stop_event(
419 bodies,
420 colliders,
421 pair.collider1,
422 pair.collider2,
423 events,
424 )
425 }
426 }
427 } else {
428 // O(1) maintenance of the persistent solver structures through the edges vec's
429 // swap-remove (with stored-position fixup) — a global rebuild
430 // is O(all pairs). If a rebuild is pending, `ContactRef`/`graph_pos` are garbage: skip it.
431 let solver_graph_valid = self.solver_graph_valid;
432 let graph = &mut self.solver_contact_graph;
433 let force_list = &mut self.force_event_pairs;
434 let force_pos = &mut self.force_event_pos;
435 let pair_solver_hints = &mut self.pair_solver_hints;
436 let num_edges = self.contact_graph.graph.edges.len();
437 let edges_ptr = self.contact_graph.graph.edges.as_mut_ptr();
438 let mut removed_index = usize::MAX;
439 let mut consistent = true;
440 let contact_pair = self.contact_graph.remove_edge_with(
441 gid1.contact_graph_index,
442 gid2.contact_graph_index,
443 &mut |e| {
444 removed_index = e.index();
445 // Mirror the edges vec's swap_remove on the solver hints
446 // (on length mismatch — deserialized state — drop them
447 // and fall back to a full solver-graph rebuild).
448 if e.index() < pair_solver_hints.len() {
449 pair_solver_hints.swap_remove(e.index());
450 } else {
451 pair_solver_hints.clear();
452 consistent = false;
453 }
454
455 if e.index() >= num_edges {
456 consistent = false;
457 return;
458 }
459
460 // Pull the removed pair's manifolds out of the buckets. Raw ordinal
461 // access: the fixup may rewrite another manifold of this same pair via
462 // `edges_ptr`. SAFETY: runs before the swap_remove (indices live); single-threaded.
463 if solver_graph_valid {
464 unsafe {
465 let pair: *mut ContactPair =
466 &mut (*edges_ptr.add(e.index())).weight;
467 let sm = (*pair).solver_manifolds_mut();
468 let (sm_ptr, num) = (sm.as_mut_ptr(), sm.len());
469 for ordinal in 0..num {
470 let mdata: *mut ContactManifoldData =
471 &mut (*sm_ptr.add(ordinal)).data;
472 let pos = (*mdata).graph_pos;
473 if pos.is_some() {
474 Self::remove_and_fixup(graph, edges_ptr, pos);
475 (*mdata).graph_pos = GraphPos::NONE;
476 }
477 }
478 }
479 }
480
481 // Force-event list: drop the removed pair's membership, then
482 // mirror the swap-remove on the back-ref array (grown at pair
483 // add so it matches the edges vec; anything else = deserialized).
484 if force_pos.len() == num_edges {
485 let cur = force_pos[e.index()];
486 if cur != u32::MAX {
487 force_list.swap_remove(cur as usize);
488 if (cur as usize) < force_list.len() {
489 force_pos[force_list[cur as usize] as usize] = cur;
490 }
491 force_pos[e.index()] = u32::MAX;
492 }
493 force_pos.swap_remove(e.index());
494 } else {
495 force_pos.clear();
496 force_list.clear();
497 consistent = false;
498 }
499 },
500 );
501
502 if !consistent {
503 // Deserialized/degenerate bookkeeping: rebuild from scratch.
504 self.solver_graph_valid = false;
505 self.force_list_valid = false;
506 } else if removed_index < self.contact_graph.graph.edges.len() {
507 // A pair was swap-moved into the removed slot: rewrite the
508 // edge index its persistent entries are keyed by.
509 let new_edge = removed_index as u32;
510 let moved = &mut self.contact_graph.graph.edges[removed_index].weight;
511 let graph = &mut self.solver_contact_graph;
512 // Same rebuild-pending gate as the removal loop above:
513 // stale `graph_pos` back-refs must not drive bucket writes.
514 if solver_graph_valid {
515 for m in moved.solver_manifolds_mut() {
516 if m.data.graph_pos.is_some() {
517 graph.rewrite_edge(m.data.graph_pos, new_edge);
518 }
519 }
520 }
521 let fpos = self
522 .force_event_pos
523 .get(removed_index)
524 .copied()
525 .unwrap_or(u32::MAX);
526 if fpos != u32::MAX {
527 self.force_event_pairs[fpos as usize] = new_edge;
528 }
529 }
530
531 // Persistent islands: unlink the removed pair (if it was
532 // touching) and mirror the edges-vec swap-remove on the
533 // link-location table.
534 if removed_index != usize::MAX && num_edges > 0 {
535 if let Some(islands) = islands.as_deref_mut() {
536 islands.persistent.unlink_contact(removed_index as u32);
537 islands
538 .persistent
539 .contact_edge_removed(removed_index as u32, num_edges as u32 - 1);
540 }
541 }
542
543 // Emit a contact stopped event if we had a contact before removing the edge.
544 // Also wake up the dynamic bodies that were in contact.
545 if let Some(mut ctct) = contact_pair {
546 clear_pair_solver_color(&mut self.body_solver_color_masks, &mut ctct);
547
548 if ctct.has_any_active_contact() {
549 if let Some(islands) = islands {
550 if let Some(co_parent1) = &co1.parent {
551 islands.wake_up(bodies, co_parent1.handle, true);
552 }
553
554 if let Some(co_parent2) = co2.parent {
555 islands.wake_up(bodies, co_parent2.handle, true);
556 }
557 }
558
559 if (co1.flags.active_events | co2.flags.active_events)
560 .contains(ActiveEvents::COLLISION_EVENTS)
561 {
562 ctct.emit_stop_event(bodies, colliders, events);
563 }
564 }
565
566 // Retire the pair for reuse by `add_pair` (bounded pool).
567 if self.retired_pairs.len() < 2048 {
568 self.retired_pairs.push(ctct);
569 }
570 }
571 }
572 }
573 }
574 }
575
576 #[profiling::function]
577 fn add_pair(&mut self, colliders: &ColliderSet, pair: &ColliderPair) {
578 if let (Some(co1), Some(co2)) =
579 (colliders.get(pair.collider1), colliders.get(pair.collider2))
580 {
581 // These colliders have no parents - continue.
582
583 let (gid1, gid2) = self.graph_indices.ensure_pair_exists(
584 pair.collider1.0,
585 pair.collider2.0,
586 ColliderGraphIndices::invalid(),
587 );
588
589 if co1.is_sensor() || co2.is_sensor() {
590 // NOTE: the collider won't have a graph index as long
591 // as it does not interact with anything.
592 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid1.intersection_graph_index)
593 {
594 gid1.intersection_graph_index =
595 self.intersection_graph.graph.add_node(pair.collider1);
596 }
597
598 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid2.intersection_graph_index)
599 {
600 gid2.intersection_graph_index =
601 self.intersection_graph.graph.add_node(pair.collider2);
602 }
603
604 if self
605 .intersection_graph
606 .graph
607 .find_edge(gid1.intersection_graph_index, gid2.intersection_graph_index)
608 .is_none()
609 {
610 let _ = self.intersection_graph.add_edge(
611 gid1.intersection_graph_index,
612 gid2.intersection_graph_index,
613 IntersectionPair::new(),
614 );
615 }
616 } else {
617 // NOTE: same code as above, but for the contact graph.
618 // TODO: refactor both pieces of code somehow?
619
620 // NOTE: the collider won't have a graph index as long
621 // as it does not interact with anything.
622 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid1.contact_graph_index) {
623 gid1.contact_graph_index = self.contact_graph.graph.add_node(pair.collider1);
624 }
625
626 if !InteractionGraph::<(), ()>::is_graph_index_valid(gid2.contact_graph_index) {
627 gid2.contact_graph_index = self.contact_graph.graph.add_node(pair.collider2);
628 }
629
630 if self
631 .contact_graph
632 .graph
633 .find_edge(gid1.contact_graph_index, gid2.contact_graph_index)
634 .is_none()
635 {
636 let interaction = if let Some(mut retired) = self.retired_pairs.pop() {
637 retired.reset_for_reuse(pair.collider1, pair.collider2);
638 retired
639 } else {
640 ContactPair::new(pair.collider1, pair.collider2)
641 };
642 // Keep the solver hints aligned with the edges vec at all times: add/remove
643 // events interleave within a step, so deferred growth would desync the swap_remove
644 // mirroring. On mismatch (deserialized), drop them — they rebuild lazily.
645 if self.pair_solver_hints.len() == self.contact_graph.graph.edges.len() {
646 self.pair_solver_hints.push(0);
647 } else {
648 self.pair_solver_hints.clear();
649 }
650 // Same for the force-event back-ref array: the O(1) removal maintenance
651 // mirrors the edges vec's swap_remove on it, so it must track the edge
652 // count exactly (not lazily at the next maintenance).
653 if self.force_event_pos.len() == self.contact_graph.graph.edges.len() {
654 self.force_event_pos.push(u32::MAX);
655 } else {
656 self.force_event_pos.clear();
657 self.force_event_pairs.clear();
658 self.force_list_valid = false;
659 }
660 let _ = self.contact_graph.add_edge(
661 gid1.contact_graph_index,
662 gid2.contact_graph_index,
663 interaction,
664 );
665 }
666 }
667 }
668 }
669
670 pub(crate) fn register_pairs(
671 &mut self,
672 mut islands: Option<&mut IslandManager>,
673 colliders: &ColliderSet,
674 bodies: &mut RigidBodySet,
675 broad_phase_events: &[BroadPhasePairEvent],
676 events: &dyn EventHandler,
677 ) {
678 for event in broad_phase_events {
679 match event {
680 BroadPhasePairEvent::AddPair(pair) => {
681 self.add_pair(colliders, pair);
682 }
683 BroadPhasePairEvent::DeletePair(pair) => {
684 self.remove_pair(
685 islands.as_deref_mut(),
686 colliders,
687 bodies,
688 pair,
689 events,
690 PairRemovalMode::Auto,
691 );
692 }
693 }
694 }
695 }
696}