Skip to main content

linear_sim/collision/
mod.rs

1//! Collision detection and resolution
2
3use std;
4#[cfg(feature = "debug_dump")]
5use std::sync::atomic;
6use std::cmp::Reverse;
7use either::Either;
8use log;
9use sorted_vec::{SortedSet, ReverseSortedSet};
10use vec_map::VecMap;
11#[cfg(feature = "derive_serdes")]
12use serde::{Deserialize, Serialize};
13
14use geometry::Aabb3;
15use math::*;
16
17use crate::{constraint, event, geometry, math, Constraint};
18use crate::constraint::contact::CONTACT_DISTANCE;
19use crate::object::{self, InternalId, ObjectPair};
20
21pub mod proximity;
22pub mod intersection;
23mod broad;
24
25pub use self::proximity::Proximity;
26pub use self::intersection::Intersection;
27use self::broad::Broad;
28
29////////////////////////////////////////////////////////////////////////////////////////
30//  constants                                                                         //
31////////////////////////////////////////////////////////////////////////////////////////
32
33#[cfg(feature = "debug_dump")]
34#[cfg_attr(docsrs, doc(cfg(feature="debug_dump")))]
35pub static DEBUG_DUMP : atomic::AtomicBool = atomic::AtomicBool::new (false);
36#[cfg(feature = "debug_dump")]
37#[cfg_attr(docsrs, doc(cfg(feature="debug_dump")))]
38pub const DEBUG_DUMP_DETECT_RESOLVE_MAX_ITERS : u64 = 200;
39
40////////////////////////////////////////////////////////////////////////////////////////
41//  structs                                                                           //
42////////////////////////////////////////////////////////////////////////////////////////
43
44/// Collision subsystem
45#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
46#[derive(Clone, Debug, Default)]
47pub(crate) struct Collision {
48  /// Broad-phase detection subsystem
49  broad : Broad,
50  /// Collision pipeline data: broad, mid, and narrow phase results.
51  ///
52  /// The pipeline should always be empty at the beginning and end of each collision
53  /// step and is skipped when serializing the collision state resulting in a default
54  /// initialized (empty) pipeline when deserialized.
55  #[cfg_attr(feature = "derive_serdes", serde(skip))]
56  pipeline : Pipeline,
57  /// Count maximum iterations of collision detect/resolve main loop
58  #[cfg(debug_assertions)]
59  detect_resolve_max_iter_count : u64,
60  /// Count maximum iterations of narrow phase loop
61  #[cfg(debug_assertions)]
62  narrow_toi_max_iter_count : u64
63}
64
65/// Collision pipeline data.
66///
67/// A collision detection step should begin and end with all vectors empty.
68#[derive(Clone, Debug, Default, PartialEq)]
69struct Pipeline {
70  /// Counts iterations of the `detect_resolve` loop; reset to zero at the beginning of
71  /// a collision step
72  pub detect_resolve_iter : u64,
73  /// Broad phase AABB overlaps on all three axes
74  pub broad_overlap_pairs : Vec <ObjectPair>,
75  /// Sorted in *reverse order* by start time
76  pub mid_toi_pairs       :
77    ReverseSortedSet <(Normalized <f64>, Normalized <f64>, ObjectPair)>,
78  /// Sorted in *reverse order* by TOI
79  pub narrow_toi_contacts :
80    ReverseSortedSet <(Normalized <f64>, ObjectPair, constraint::Contact)>,
81  pub resolved_collisions : Vec <event::CollisionResolve>,
82  /// Nocollide intersections
83  pub overlaps            : Vec <event::Overlap>,
84}
85
86////////////////////////////////////////////////////////////////////////////////////////
87//  functions                                                                         //
88////////////////////////////////////////////////////////////////////////////////////////
89
90pub fn report_sizes() {
91  use std::mem::size_of;
92  println!("collision report sizes...");
93
94  println!("  size of Collision: {}", size_of::<Collision>());
95
96  println!("...collision report sizes");
97}
98
99////////////////////////////////////////////////////////////////////////////////////////
100//  impls                                                                             //
101////////////////////////////////////////////////////////////////////////////////////////
102
103impl Collision {
104  pub(crate) const fn broad (&self) -> &Broad {
105    &self.broad
106  }
107  /// Attempt to add a new static object to the collision subsystem.
108  ///
109  /// Queries distance to each dynamic object and if any intersections are
110  /// found, the object is not added and the intersections are returned.
111  ///
112  /// Only counts intersections if both objects are collidable.
113  pub(crate) fn try_add_object_static (&mut self,
114    objects_dynamic : &VecMap <object::Dynamic>,
115    object          : &object::Static,
116    key             : object::Key
117  ) -> Result <(), Vec <(object::Id, Intersection)>> {
118    let mut intersections = Vec::new();
119    if object.collidable {
120      let aabb_discrete = object.aabb_dilated();
121      let overlaps : Vec <object::Id> = self.broad.overlaps_discrete (aabb_discrete)
122        .into_iter().map (object::Id::from).collect::<Vec <_>>();
123      for object_id in overlaps {
124        let proximity = match object_id.kind {
125          object::Kind::Static  => continue,  // skip static/static checks
126          object::Kind::Dynamic => {
127            let object_dynamic = &objects_dynamic[object_id.key.index()];
128            if !object_dynamic.collidable {
129              continue                    // skip non-collidable dynamic objects
130            }
131            Proximity::query (object_dynamic, object)
132          }
133          object::Kind::Nodetect => unreachable!()
134        };
135        if let Ok (intersection) = proximity.try_into() {
136          intersections.push ((object_id, intersection));
137        }
138      }
139    }
140    if intersections.is_empty() {
141      self.add_object_static (object, key);
142      Ok  (())
143    } else {
144      Err (intersections)
145    }
146  }
147
148  #[inline]
149  pub(crate) fn remove_object_static (&mut self, object_key : object::Key) {
150    debug_assert!(self.pipeline.is_empty());
151    let id = InternalId::new_static (object_key);
152    self.broad.remove_object (id);
153  }
154
155  /// Attempts to add a new dynamic object.
156  ///
157  /// Queries distance to each static and dynamic object and if any intersections are
158  /// found, the object is not added and the intersections are returned.
159  ///
160  /// Only counts intersections if both objects are collidable.
161  pub(crate) fn try_add_object_dynamic (&mut self,
162    pseudo_velocities : &mut VecMap <Vector3 <f64>>,
163    objects_static    : &VecMap <object::Static>,
164    objects_dynamic   : &VecMap <object::Dynamic>,
165    object            : &object::Dynamic,
166    key               : object::Key
167  ) -> Result <(), Vec <(object::Id, Intersection)>> {
168    let mut intersections = Vec::new();
169    if object.collidable {
170      let aabb_discrete     = object.aabb_dilated();
171      let overlaps : Vec <object::Id> = self.broad.overlaps_discrete (aabb_discrete)
172        .into_iter().map (object::Id::from).collect::<Vec <_>>();
173      for object_id in overlaps {
174        let proximity = match object_id.kind {
175          object::Kind::Static  => {
176            let object_static = &objects_static[object_id.key.index()];
177            if !object_static.collidable {
178              continue  // skip non-collidable static objects
179            }
180            Proximity::query (object_static, object)
181          }
182          object::Kind::Dynamic => {
183            let object_dynamic = &objects_dynamic[object_id.key.index()];
184            if !object_dynamic.collidable {
185              continue  // skip non-collidable dynamic objects
186            }
187            Proximity::query (object_dynamic, object)
188          }
189          object::Kind::Nodetect => unreachable!()
190        };
191        if let Ok (intersection) = proximity.try_into() {
192          intersections.push ((object_id, intersection));
193        }
194      }
195    }
196    if intersections.is_empty() {
197      self.add_object_dynamic (pseudo_velocities, object, key);
198      Ok  (())
199    } else {
200      Err (intersections)
201    }
202  }
203
204  #[inline]
205  pub(crate) fn remove_object_dynamic (&mut self, key : object::Key) {
206    debug_assert!(self.pipeline.is_empty());
207    let id = InternalId::new_dynamic (key);
208    self.broad.remove_object (id);
209  }
210
211  #[inline]
212  pub(crate) fn update_object_static (&mut self,
213    object : &object::Static, key : object::Key
214  ) {
215    debug_assert!(self.pipeline.is_empty());
216    self.broad.update_aabb_static (object.aabb_dilated(), key);
217    unimplemented!("TODO: collision detect and resolve any overlaps caused by \
218      updating the static object")
219  }
220
221  #[inline]
222  pub(crate) fn update_object_dynamic (&mut self,
223    object : &object::Dynamic, key : object::Key
224  ) {
225    debug_assert!(self.pipeline.is_empty());
226    let aabb = object.aabb_dilated();
227    self.broad.update_aabb_dynamic_discrete (aabb, key);
228    // TODO: why is the continuous AABB the same as discrete here?
229    self.broad.update_aabb_dynamic_continuous (aabb, key);
230    log::warn!("TODO: collision detect and resolve any overlaps caused by \
231      updating the dynamic object")
232  }
233
234  //
235  //  Collision::detect_resolve_loop
236  //
237  /// Main collision loop.
238  ///
239  /// Before the loop starts, `begin_step()`:
240  ///
241  /// 1. Update broad-phase AABB data
242  ///
243  /// Inside the collision loop:
244  ///
245  /// 1. Perform continuous detection
246  /// 2. Resolve earliest detected collision
247  ///
248  /// After collision loop:
249  ///
250  /// 1. Drain pipeline outputs (collisions, overlaps, contacts)
251  /// 2. Zero pseudovelocities
252  pub(crate) fn detect_resolve_loop (&mut self,
253    constraint_manager : &mut constraint::Manager,
254    objects_static     : &VecMap <object::Static>,
255    objects_dynamic    : &mut VecMap <object::Dynamic>,
256    step               : u64,
257    output             : &mut Vec <event::Output>
258  ) {
259    log::trace!(step; "detect/resolve loop");
260
261    // initialize collision for the current step
262    debug_assert!(self.pipeline.is_empty());
263    self.pipeline.detect_resolve_iter = 0;
264
265    // broad: update AABBs based on the given dynamic objects state
266    //
267    // the continuous (swept) AABBs are calculated from min/max of the previous
268    // 'discrete' (instantaneous) AABB and the new "current" discrete AABB
269    self.broad.begin_step (objects_dynamic, step);
270
271    // main detect/resolve loop: resolve one collision per iteration
272    loop {
273      log::debug!(iter=self.pipeline.detect_resolve_iter;
274        "detect/resolve loop iteration");
275      // detect collisions (TOI contacts)
276      self.detect_continuous (constraint_manager, objects_static, objects_dynamic);
277      // resolve earliest collision: resolve a velocity constraint at TOI and a
278      // position constraint at t==1.0
279      if !self.resolve_first_collision (
280        constraint_manager, objects_static, objects_dynamic
281      ) {
282        debug_assert!(self.pipeline.mid_toi_pairs.is_empty());
283        debug_assert!(self.pipeline.narrow_toi_contacts.is_empty());
284        break
285      }
286      self.pipeline.detect_resolve_iter += 1;
287    }
288
289    log::trace!(iters=self.pipeline.detect_resolve_iter+1;
290      "detect/resolve loop complete");
291    #[cfg(debug_assertions)]
292    {
293      if self.detect_resolve_max_iter_count
294        < (self.pipeline.detect_resolve_iter + 1)
295      {
296        #[cfg(feature = "debug_dump")]
297        if self.pipeline.detect_resolve_iter >
298          DEBUG_DUMP_DETECT_RESOLVE_MAX_ITERS
299        {
300          DEBUG_DUMP.store (true, atomic::Ordering::SeqCst);
301        }
302        self.detect_resolve_max_iter_count = self.pipeline.detect_resolve_iter;
303      }
304      log::debug!(max_iter_count=self.detect_resolve_max_iter_count;
305        "detect/resolve loop max iters");
306    }
307
308    // output collision events
309    for collision_resolve in self.pipeline.resolved_collisions.drain (..) {
310      output.push (collision_resolve.into());
311    }
312    for overlap in self.pipeline.overlaps.drain (..) {
313      output.push (overlap.into());
314    }
315
316    // output contacts
317    constraint_manager.output_contacts (output);
318
319    // zero pseudovelocities
320    for (_, pseudovelocity) in constraint_manager.pseudo_velocities.iter_mut() {
321      *pseudovelocity = Vector3::zero();
322    }
323
324    // done
325    debug_assert!(self.pipeline.is_empty());
326  }
327
328  //
329  //  private methods
330  //
331
332  /// Detect TOI (time-of-impact) contacts, i.e. *collisions*.
333  fn detect_continuous (&mut self,
334    constraint_manager : &constraint::Manager,
335    objects_static     : &VecMap <object::Static>,
336    objects_dynamic    : &VecMap <object::Dynamic>
337  ) {
338    // collect broad overlap pairs
339    self.broad_overlap_pairs_continuous();
340    // mid-phase TOI pairs
341    self.mid_toi_pairs (constraint_manager, objects_dynamic);
342    // narrow-phase TOI pairs
343    self.narrow_toi_contacts (objects_static, objects_dynamic,
344      &constraint_manager.pseudo_velocities);
345  }
346
347  /// Resolve the first collision (TOI contact pair) in the pipeline, if any, and
348  /// returns true, otherwise returns false.
349  ///
350  /// If impulses do not prevent intersection at t==1.0, post-impulse position
351  /// correction will move objects to a distance of `0.5 * CONTACT_DISTANCE` by applying
352  /// a pseudo-velocity impulse at the collision TOI.
353  ///
354  /// This function will be called multiple times in the `detect_resolve_loop()`.
355  fn resolve_first_collision (&mut self,
356    constraint_manager : &mut constraint::Manager,
357    objects_static     : &VecMap <object::Static>,
358    objects_dynamic    : &mut VecMap <object::Dynamic>
359  ) -> bool {
360    if let Some (Reverse ((toi, object_pair, contact))) =
361      self.pipeline.narrow_toi_contacts.pop()
362    {
363      // process the first narrow toi contact (collision): the container is
364      // reverse-sorted so we use pop
365      let (object_id_a, object_id_b) = object_pair.into();
366      let kind_a  = object_id_a.kind();
367      let kind_b  = object_id_b.kind();
368      let index_a = object_id_a.key().index();
369      let index_b = object_id_b.key().index();
370      // if one or both objects are not collidable, we report overlap and return
371      let collidable = {
372        let collidable_a = match kind_a {
373          object::Kind::Static   => objects_static[index_a].collidable,
374          object::Kind::Dynamic  => objects_dynamic[index_a].collidable,
375          object::Kind::Nodetect => unreachable!()
376        };
377        debug_assert_eq!(kind_b, object::Kind::Dynamic);
378        let collidable_b = objects_dynamic[index_b].collidable;
379        collidable_a && collidable_b
380      };
381      if !collidable {
382        let overlap = event::Overlap {
383          object_id_a: object_id_a.into(),
384          object_id_b: object_id_b.into()
385        };
386        log::debug!(toi=*toi, overlap:?; "nocollide overlap");
387        self.pipeline.overlaps.push (overlap);
388        return true
389      }
390      log::debug!(toi=*toi, object_pair:?=(object_id_a, object_id_b);
391        "resolve collision");
392      // get persistant contact islands
393      let maybe_island_a = constraint_manager.get_island_key (object_id_a);
394      let maybe_island_b = {
395        let maybe_island_b = constraint_manager.get_island_key (object_id_b);
396        if maybe_island_a == maybe_island_b {
397          // if the islands are the same, then just use island a
398          None
399        } else {
400          maybe_island_b
401        }
402      };
403      // resolve collision
404      let collision_resolve = self.resolve_collision (
405        constraint_manager, objects_static, objects_dynamic, toi, object_id_a,
406        object_id_b, contact, maybe_island_a, maybe_island_b);
407      log::trace!(collision_resolve:?; "resolved collision");
408      self.pipeline.resolved_collisions.push (collision_resolve);
409      true
410    } else {
411      // no narrow toi contact to process, return false
412      debug_assert!(self.pipeline.mid_toi_pairs.is_empty());
413      false
414    }
415  }
416
417  #[expect(clippy::too_many_arguments)]
418  fn resolve_collision (
419    &mut self,
420    constraint_manager : &mut constraint::Manager,
421    objects_static     : &VecMap <object::Static>,
422    objects_dynamic    : &mut VecMap <object::Dynamic>,
423    toi                : Normalized <f64>,
424    object_id_a        : InternalId,
425    object_id_b        : InternalId,
426    collision          : constraint::Contact,
427    island_key_a       : Option <constraint::island::KeyType>,
428    island_key_b       : Option <constraint::island::KeyType>
429  ) -> event::CollisionResolve {
430    // 1. lerp all involved dynamic objects to the TOI
431    // 2. constrain velocities
432    // 3. lerp to T==1.0
433    // 4. constrain positions
434    // 5. create or destroy persistent contacts from final proximity check
435    // 6. update AABBs in broad pipeline
436    use num::Zero;
437    let mut dynamic_ids = SortedSet::new();
438    if object_id_a.kind() == object::Kind::Dynamic {
439      dynamic_ids.push (object_id_a);
440    }
441    dynamic_ids.push (object_id_b);
442    let mut islands = constraint_manager.islands.take().unwrap();
443    let island_a    = island_key_a.map (|key| &islands[key as usize]);
444    let island_b    = island_key_b.map (|key| &islands[key as usize]);
445    island_a.map (|island| dynamic_ids.extend (island.dynamic_ids().into_vec()));
446    island_b.map (|island| dynamic_ids.extend (island.dynamic_ids().into_vec()));
447    // track whether a dynamic object was modified; if so it will need to be removed
448    // from intermediate results in the pipeline and checked for new collisions
449    let mut dynamic_modified = SortedSet::new();
450    // 1. lerp to TOI
451    let lerp_objects = |objects_dynamic : &mut VecMap <object::Dynamic>, time_delta|
452      for id in dynamic_ids.iter() {
453        let index          = id.key().index();
454        let object         = &mut objects_dynamic[index];
455        let pseudovelocity = constraint_manager.pseudo_velocities[index];
456        object::lerp (object, pseudovelocity, time_delta);
457      };
458    let (t_reverse, t_forward) = {
459      let toi_f64 : f64 = *toi;   // [0.0, 1.0]
460      (-1.0 + toi_f64, 1.0 - toi_f64)
461    };
462    lerp_objects (objects_dynamic, t_reverse);
463    let toi_aabbs = dynamic_ids.iter().map (|id|{
464      let index = id.key().index();
465      objects_dynamic[index].aabb_dilated()
466    }).collect::<Vec <_>>();
467    // 2. constrain velocities
468    let constrain_velocity = |
469      objects_dynamic  : &mut VecMap <object::Dynamic>,
470      pseudovelocities : &VecMap <Vector3 <f64>>,
471      constraint       : constraint::Variant,
472      colliding        : bool,
473      object_id_a      : InternalId,
474      object_id_b      : InternalId
475    | {
476      let key_a    = object_id_a.key();
477      let key_b    = object_id_b.key();
478      let index_a  = key_a.index();
479      let index_b  = key_b.index();
480      let (mut object_a, object_a_restitution) = match object_id_a.kind() {
481        object::Kind::Static   => {
482          let object      = objects_static[index_a].clone();
483          let restitution = object.material.restitution;
484          (Either::Left (object), restitution)
485        }
486        object::Kind::Dynamic  => {
487          let object      = objects_dynamic[index_a].clone();
488          let restitution = object.material.restitution;
489          (Either::Right (object), restitution)
490        }
491        object::Kind::Nodetect => unreachable!()
492      };
493      debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
494      let mut object_b = objects_dynamic[index_b].clone();
495      let restitution  = if colliding {
496        object_a_restitution * object_b.material.restitution
497      } else {
498        Normalized::zero()
499      };
500      let pseudovelocity_a = if object_id_a.kind() == object::Kind::Dynamic {
501        Some (pseudovelocities[index_a])
502      } else {
503        None
504      };
505      let pseudovelocity_b = pseudovelocities[index_b];
506      let maybe_impulses = match constraint {
507        constraint::Variant::Contact (contact) =>
508          contact.constrain_velocity_with_restitution (
509            object_a.as_mut().map_left (|object_static| &*object_static),
510            &mut object_b, pseudovelocity_a, pseudovelocity_b, restitution),
511        constraint::Variant::Distance (distance) => distance.constrain_velocity (
512          object_a.as_mut().map_left (|object_static| &*object_static),
513          &mut object_b, pseudovelocity_a, pseudovelocity_b)
514      };
515      if maybe_impulses.is_some() {
516        match object_a {
517          Either::Right (object_a) => objects_dynamic[index_a] = object_a,
518          Either::Left  (_)        => {}
519        }
520        objects_dynamic[index_b] = object_b;
521      }
522      maybe_impulses
523    };
524    let mut impulse_normal_a  = Vector3::zero();
525    let mut impulse_normal_b  = Vector3::zero();
526    let mut impulse_tangent_a = Vector3::zero();
527    let mut impulse_tangent_b = Vector3::zero();
528    let mut iter = 0;
529    loop {
530      let mut satisfied = true;
531      if let Some (impulses) = constrain_velocity (
532        objects_dynamic, &constraint_manager.pseudo_velocities, collision.clone().into(),
533        true, object_id_a, object_id_b
534      ) {
535        impulse_normal_a  += impulses.impulse_normal_a;
536        impulse_normal_b  += impulses.impulse_normal_b;
537        impulse_tangent_a += impulses.impulse_tangent_a;
538        impulse_tangent_b += impulses.impulse_tangent_b;
539      }
540      let mut constrain_island = |island : &constraint::Island| {
541        for (object_pair, constraint) in island.constraints.iter().cloned() {
542          let (object_id_a, object_id_b) = object_pair.into();
543          if constrain_velocity (
544            objects_dynamic, &constraint_manager.pseudo_velocities, constraint, false,
545            object_id_a, object_id_b
546          ).is_some() {
547            if object_id_a.kind() == object::Kind::Dynamic {
548              dynamic_modified.push (object_id_a);
549            }
550            dynamic_modified.push (object_id_b);
551            satisfied = false;
552          }
553        }
554      };
555      if let Some (island) = island_a {
556        constrain_island (island);
557      }
558      if let Some (island) = island_b {
559        constrain_island (island);
560      }
561      iter += 1;
562      if satisfied {
563        log::debug!(iters=iter; "collision velocity constraint satisfied");
564        break
565      }
566    }
567    // 3. lerp to T==1.0
568    lerp_objects (objects_dynamic, t_forward);
569    // 4. constrain positions
570    let constrain_position = |
571      objects_dynamic  : &mut VecMap <object::Dynamic>,
572      pseudovelocities : &mut VecMap <Vector3 <f64>>,
573      constraint       : &mut constraint::Variant,
574      object_id_a      : InternalId,
575      object_id_b      : InternalId
576    | {
577      let key_a    = object_id_a.key();
578      let key_b    = object_id_b.key();
579      let index_a  = key_a.index();
580      let index_b  = key_b.index();
581      let mut object_a = match object_id_a.kind() {
582        object::Kind::Static   => Either::Left  (objects_static[index_a].clone()),
583        object::Kind::Dynamic  => Either::Right (objects_dynamic[index_a].clone()),
584        object::Kind::Nodetect => unreachable!()
585      };
586      debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
587      let mut object_b = objects_dynamic[index_b].clone();
588      let mut pseudovelocity_a = if object_id_a.kind() == object::Kind::Dynamic {
589        Some (pseudovelocities[index_a])
590      } else {
591        None
592      };
593      let mut pseudovelocity_b = pseudovelocities[index_b];
594      let maybe_impulses = constraint.constrain_position (
595        t_reverse, object_a.as_mut().map_left (|object_static| &*object_static),
596        &mut object_b, pseudovelocity_a.as_mut(), &mut pseudovelocity_b);
597      if maybe_impulses.is_some() {
598        match object_a {
599          Either::Right (object_a) => {
600            objects_dynamic[index_a]  = object_a;
601            pseudovelocities[index_a] = pseudovelocity_a.unwrap();
602          }
603          Either::Left  (_) => {}
604        }
605        objects_dynamic[index_b]  = object_b;
606        pseudovelocities[index_b] = pseudovelocity_b;
607      }
608      maybe_impulses
609    };
610    // contact index and final proximity result
611    let mut pseudo_impulse_a  = Vector3::zero();
612    let mut pseudo_impulse_b  = Vector3::zero();
613    let mut iter = 0;
614    let mut collision_constraint = constraint::Variant::from (collision.clone());
615    loop {
616      let mut satisfied = true;
617      let maybe_impulses = constrain_position (
618        objects_dynamic, &mut constraint_manager.pseudo_velocities,
619        &mut collision_constraint, object_id_a, object_id_b);
620      if let Some (impulses) = maybe_impulses {
621        pseudo_impulse_a += impulses.pseudo_impulse_a;
622        pseudo_impulse_b += impulses.pseudo_impulse_b;
623      }
624      let mut constrain_island = |island : &mut constraint::Island|
625        for (object_pair, constraint) in island.constraints.iter_mut() {
626          let (object_id_a, object_id_b) = (*object_pair).into();
627          let maybe_impulses = constrain_position (
628            objects_dynamic, &mut constraint_manager.pseudo_velocities, constraint,
629            object_id_a, object_id_b
630          );
631          if maybe_impulses.is_some() {
632            if object_id_a.kind() == object::Kind::Dynamic {
633              dynamic_modified.push (object_id_a);
634            }
635            dynamic_modified.push (object_id_b);
636            satisfied = false;
637          }
638        };
639      if let Some (key) = island_key_a {
640        constrain_island (&mut islands[key as usize]);
641      }
642      if let Some (key) = island_key_b {
643        constrain_island (&mut islands[key as usize]);
644      }
645      iter += 1;
646      if satisfied {
647        log::debug!(iters=iter; "collision position constraint satisfied");
648        break
649      }
650    }
651    constraint_manager.islands = Some (islands);
652    // 5. create or destroy persistent contacts from final proximity check;
653    // also add resolved pairs to the broad phase here since we are iterating over
654    // constraint islands one last time
655    let object_pair = (object_id_a, object_id_b).into();
656    if !impulse_normal_b.is_zero() || !impulse_tangent_b.is_zero() ||
657      !pseudo_impulse_b.is_zero()
658    {
659      if object_id_a.kind() == object::Kind::Dynamic {
660        debug_assert!(!impulse_normal_a.is_zero() ||
661          !impulse_tangent_a.is_zero() || !pseudo_impulse_a.is_zero());
662        dynamic_modified.push (object_id_a);
663      }
664      dynamic_modified.push (object_id_b);
665    }
666    self.broad.add_resolved (object_pair);
667    // wait until after updating islands to create the new contact in case the islands
668    // are merged by adding the contact, which would invalidate one of the old island
669    // keys
670    let mut update_island = |island_key : constraint::island::KeyType| {
671      let mut islands = constraint_manager.islands.take().unwrap();
672      let island = &mut islands[island_key as usize];
673      let mut remove_contacts = vec![];
674      for (constraint_index, (object_pair, contact)) in island.constraints.iter()
675        .enumerate().filter_map (|(index, (pair, constraint))|
676          if let constraint::Variant::Contact (contact) = constraint {
677            Some ((index, (pair, contact)))
678          } else {
679            None
680          }
681        )
682      {
683        self.broad.add_resolved (*object_pair);
684        if contact.distance >= CONTACT_DISTANCE {
685          remove_contacts.push (constraint_index as u32)
686        }
687      }
688      if !remove_contacts.is_empty() {
689        constraint_manager.remove_constraints (island, remove_contacts.as_slice());
690        if island.constraints.is_empty() {
691          let _ = islands.take (island_key as usize).unwrap();
692        } else {
693          let partitions = island.partition();
694          if !partitions.is_empty() {
695            let _ = islands.take (island_key as usize).unwrap();
696            for new_island in partitions {
697              let dynamic_ids = new_island.dynamic_ids();
698              let island_key  = islands.put (new_island) as constraint::island::KeyType;
699              for dynamic_id in dynamic_ids.into_vec() {
700                constraint_manager.change_dynamic_island_key (dynamic_id, island_key);
701              }
702            }
703          }
704        }
705      }
706      constraint_manager.islands = Some (islands);
707    };
708    if let Some (island_key) = island_key_a {
709      update_island (island_key);
710    }
711    if let Some (island_key) = island_key_b {
712      update_island (island_key);
713    }
714    let constraint::Variant::Contact (contact) = collision_constraint
715      else { unreachable!() };
716    if contact.distance < CONTACT_DISTANCE {
717      debug_assert!(contact.distance >= 0.0);
718      log::trace!(object_pair:?, contact:?; "creating contact @ T==1.0");
719      constraint_manager.add_constraint (object_pair, contact.into());
720    }
721    // 6. update AABBs in broad pipeline;
722    // also remove resolved dynamic objects from intermediate pipeline results
723    for id in dynamic_modified.iter() {
724      let i = dynamic_ids.binary_search_by_key (&id, |x| x).unwrap();
725      let key    = id.key();
726      let index  = key.index();
727      let object = &objects_dynamic[index];
728      let aabb_discrete   = object.aabb_dilated();
729      let aabb_continuous = Aabb3::union (toi_aabbs[i], aabb_discrete);
730      self.broad.update_aabb_dynamic_discrete (aabb_discrete, key);
731      self.broad.update_aabb_dynamic_continuous (aabb_continuous, key);
732    }
733    self.pipeline.remove_resolved_dynamic (&dynamic_modified);
734    // TODO: update the modified set in-place ?
735    self.broad.set_modified (dynamic_modified);
736    // result
737    let object_id_a = object_id_a.into();
738    let object_id_b = object_id_b.into();
739    event::CollisionResolve {
740      toi, collision,
741      object_id_a,       object_id_b,
742      impulse_normal_a,  impulse_normal_b,
743      impulse_tangent_a, impulse_tangent_b,
744      pseudo_impulse_a,  pseudo_impulse_b
745    }
746  }
747
748  /// Detects AABB overlap pairs of static/dynamic and dynamic/dynamic objects.
749  ///
750  /// Excludes Nocollide pairs that were already detected but does not filter
751  /// object pairs that are already in persistent contacts.
752  #[inline]
753  fn broad_overlap_pairs_continuous (&mut self) {
754    self.broad.overlap_pairs_continuous (
755      &mut self.pipeline.broad_overlap_pairs, self.pipeline.detect_resolve_iter);
756    // exclude nocollide pairs that were already detected
757    self.pipeline.broad_overlap_pairs.retain (
758      |pair|{
759        let (id_a, id_b) = (*pair).into();
760        let id_a = object::Id::from (id_a);
761        let id_b = object::Id::from (id_b);
762        for event::Overlap { object_id_a, object_id_b } in
763          self.pipeline.overlaps.iter()
764        {
765          if object_id_a == &id_a && object_id_b == &id_b {
766            return false
767          }
768        }
769        true
770      }
771    );
772    log::trace!(overlap_pairs:?=self.pipeline.broad_overlap_pairs;
773      "broad overlap pairs");
774  }
775
776  /// Collects swept AABB TOIs from broad overlap pairs.
777  ///
778  /// Filters pairs that are already in persistent contact.
779  ///
780  /// Note that the algorithm will aim for a distance of `0.5 * CONTACT_DISTANCE` at
781  /// each TOI.
782  fn mid_toi_pairs (&mut self,
783    constraint_manager : &constraint::Manager,
784    objects_dynamic    : &VecMap <object::Dynamic>
785  ) {
786    use sorted_vec::FindOrInsert;
787    let last_toi = self.pipeline.resolved_collisions.last()
788      .map_or (0.0, |collision_resolve| *collision_resolve.toi);
789    'outer: for object_pair in self.pipeline.broad_overlap_pairs.drain (..) {
790      let (object_id_a, object_id_b) = object_pair.into();
791      debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
792      match object_id_a.kind() {
793        //
794        //  static v. dynamic
795        //
796        object::Kind::Static => {
797          let id_static     = object::Id::from (object_id_a);
798          let id_dynamic    = object::Id::from (object_id_b);
799          let key_static    = id_static.key;
800          let key_dynamic   = id_dynamic.key;
801          let _index_static = key_static.index();
802          let index_dynamic = key_dynamic.index();
803          if let (Some (static_contact_count), Some (dynamic_island_key)) = (
804            constraint_manager.get_constraint_count (object_id_a),
805            constraint_manager.get_island_key (object_id_b)
806          ) {
807            debug_assert!(static_contact_count > 0);
808            let island = constraint_manager.get_island (dynamic_island_key).unwrap();
809            for (pair, _) in island.constraints.iter() {
810              if *pair == object_pair {
811                continue 'outer
812              }
813            }
814          }
815          // should be safe to unwrap
816          let dynamic_object         = &objects_dynamic[index_dynamic];
817          let dynamic_velocity       = dynamic_object.derivatives.velocity;
818          let dynamic_pseudovelocity = constraint_manager.pseudo_velocities[index_dynamic];
819          let dynamic_velocity_effective = dynamic_velocity + dynamic_pseudovelocity;
820          let static_aabb            = self.broad.get_aabb_static (key_static);
821          let dynamic_aabb_current   = self.broad
822            .get_aabb_dynamic_discrete (key_dynamic);
823          let _dynamic_aabb_swept    = self.broad
824            .get_aabb_dynamic_continuous (key_dynamic);
825          let dynamic_aabb_previous  = Aabb3::with_minmax_unchecked (
826            dynamic_aabb_current.min() - dynamic_velocity_effective,
827            dynamic_aabb_current.max() - dynamic_velocity_effective);
828          // NB: div by zero will result in `inf` values
829          let dynamic_velocity_effective_reciprocal = dynamic_velocity_effective.recip();
830          // these values adjust start/end times to avoid getting too close
831          let t_margin_x =
832            (0.5 * CONTACT_DISTANCE * dynamic_velocity_effective_reciprocal.x).abs();
833          let t_margin_y =
834            (0.5 * CONTACT_DISTANCE * dynamic_velocity_effective_reciprocal.y).abs();
835          let t_margin_z =
836            (0.5 * CONTACT_DISTANCE * dynamic_velocity_effective_reciprocal.z).abs();
837
838          let (interval_start_x, interval_end_x) = if
839            dynamic_velocity_effective.x == 0.0
840          {
841            debug_assert!(
842              dynamic_aabb_current.max().0.x > static_aabb.min().0.x &&
843              static_aabb.max().0.x > dynamic_aabb_current.min().0.x);
844            debug_assert!(
845              dynamic_aabb_previous.max().0.x > static_aabb.min().0.x &&
846              static_aabb.max().0.x > dynamic_aabb_previous.min().0.x);
847            (f64::NEG_INFINITY, f64::INFINITY)
848          } else if dynamic_velocity_effective.x > 0.0 {
849            ( //(static_aabb.min().0.x - dynamic_aabb_previous.max().0.x) *
850              //  dynamic_velocity_effective_reciprocal.x - t_margin_x,
851              (static_aabb.min().0.x - dynamic_aabb_previous.max().0.x)
852                .mul_add (dynamic_velocity_effective_reciprocal.x, -t_margin_x),
853              //(static_aabb.max().0.x - dynamic_aabb_previous.min().0.x) *
854              //  dynamic_velocity_effective_reciprocal.x + t_margin_x
855              (static_aabb.max().0.x - dynamic_aabb_previous.min().0.x)
856                .mul_add (dynamic_velocity_effective_reciprocal.x, t_margin_x)
857            )
858          } else {
859            debug_assert!(dynamic_velocity_effective.x < 0.0);
860            ( //(static_aabb.max().0.x - dynamic_aabb_previous.min().0.x) *
861              //  dynamic_velocity_effective_reciprocal.x - t_margin_x,
862              (static_aabb.max().0.x - dynamic_aabb_previous.min().0.x)
863                .mul_add (dynamic_velocity_effective_reciprocal.x, -t_margin_x),
864              //(static_aabb.min().0.x - dynamic_aabb_previous.max().0.x) *
865              //  dynamic_velocity_effective_reciprocal.x + t_margin_x
866              (static_aabb.min().0.x - dynamic_aabb_previous.max().0.x)
867                .mul_add (dynamic_velocity_effective_reciprocal.x, t_margin_x)
868            )
869          };
870
871          let (interval_start_y, interval_end_y) = if
872            dynamic_velocity_effective.y == 0.0
873          {
874            debug_assert!(
875              dynamic_aabb_current.max().0.y > static_aabb.min().0.y &&
876              static_aabb.max().0.y > dynamic_aabb_current.min().0.y);
877            debug_assert!(
878              dynamic_aabb_previous.max().0.y > static_aabb.min().0.y &&
879              static_aabb.max().0.y > dynamic_aabb_previous.min().0.y);
880            (f64::NEG_INFINITY, f64::INFINITY)
881          } else if dynamic_velocity_effective.y > 0.0 {
882            ( //dynamic_velocity_effective_reciprocal.y *
883              //  (static_aabb.min().0.y - dynamic_aabb_previous.max().0.y) - t_margin_y,
884              dynamic_velocity_effective_reciprocal.y.mul_add (
885                static_aabb.min().0.y - dynamic_aabb_previous.max().0.y, -t_margin_y),
886              //dynamic_velocity_effective_reciprocal.y *
887              //  (static_aabb.max().0.y - dynamic_aabb_previous.min().0.y) + t_margin_y
888              dynamic_velocity_effective_reciprocal.y.mul_add (
889                static_aabb.max().0.y - dynamic_aabb_previous.min().0.y, t_margin_y)
890            )
891          } else {
892            debug_assert!(dynamic_velocity_effective.y < 0.0);
893            ( //dynamic_velocity_effective_reciprocal.y *
894              //  (static_aabb.max().0.y - dynamic_aabb_previous.min().0.y) - t_margin_y,
895              dynamic_velocity_effective_reciprocal.y.mul_add (
896                static_aabb.max().0.y - dynamic_aabb_previous.min().0.y, -t_margin_y),
897              //dynamic_velocity_effective_reciprocal.y *
898              //  (static_aabb.min().0.y - dynamic_aabb_previous.max().0.y) + t_margin_y
899              dynamic_velocity_effective_reciprocal.y.mul_add (
900                static_aabb.min().0.y - dynamic_aabb_previous.max().0.y, t_margin_y)
901            )
902          };
903
904          let (interval_start_z, interval_end_z) = if
905            dynamic_velocity_effective.z == 0.0
906          {
907            debug_assert!(
908              dynamic_aabb_current.max().0.z > static_aabb.min().0.z &&
909              static_aabb.max().0.z > dynamic_aabb_current.min().0.z);
910            debug_assert!(
911              dynamic_aabb_previous.max().0.z > static_aabb.min().0.z &&
912              static_aabb.max().0.z > dynamic_aabb_previous.min().0.z);
913            (f64::NEG_INFINITY, f64::INFINITY)
914          } else if dynamic_velocity_effective.z > 0.0 {
915            ( //dynamic_velocity_effective_reciprocal.z *
916              //  (static_aabb.min().0.z - dynamic_aabb_previous.max().0.z) - t_margin_z,
917              dynamic_velocity_effective_reciprocal.z.mul_add (
918                static_aabb.min().0.z - dynamic_aabb_previous.max().0.z, -t_margin_z),
919              //dynamic_velocity_effective_reciprocal.z *
920              //  (static_aabb.max().0.z - dynamic_aabb_previous.min().0.z) + t_margin_z
921              dynamic_velocity_effective_reciprocal.z.mul_add (
922                static_aabb.max().0.z - dynamic_aabb_previous.min().0.z, t_margin_z)
923            )
924          } else {
925            debug_assert!(dynamic_velocity_effective.z < 0.0);
926            ( //dynamic_velocity_effective_reciprocal.z *
927              //  (static_aabb.max().0.z - dynamic_aabb_previous.min().0.z) - t_margin_z,
928              dynamic_velocity_effective_reciprocal.z.mul_add (
929                static_aabb.max().0.z - dynamic_aabb_previous.min().0.z, -t_margin_z),
930              //dynamic_velocity_effective_reciprocal.z *
931              //  (static_aabb.min().0.z - dynamic_aabb_previous.max().0.z) + t_margin_z
932              dynamic_velocity_effective_reciprocal.z.mul_add (
933                static_aabb.min().0.z - dynamic_aabb_previous.max().0.z, t_margin_z)
934            )
935          };
936
937          let interval = {
938            let interval_xy =
939              if interval_start_x < interval_end_y && interval_start_y < interval_end_x {
940                Some ((
941                  f64::max (interval_start_x, interval_start_y),
942                  f64::min (interval_end_x, interval_end_y)
943                ))
944              } else {
945                None
946              };
947            if let Some ((interval_start_xy, interval_end_xy)) = interval_xy {
948              if interval_start_xy < interval_end_z &&
949                 interval_start_z  < interval_end_xy
950              {
951                Some ((
952                  f64::max (interval_start_xy, interval_start_z),
953                  f64::min (interval_end_xy, interval_end_z)
954                ))
955              } else {
956                None
957              }
958            } else {
959              None
960            }
961          };
962
963          if let Some ((interval_start, interval_end)) = interval &&
964            interval_start < 1.0 && 0.0 < interval_end
965          {
966            let mid_toi_pair = (
967              Normalized::clamp (f64::max (interval_start, last_toi)),
968              Normalized::clamp (interval_end),
969              object_pair
970            );
971            match self.pipeline.mid_toi_pairs.find_or_insert (Reverse (mid_toi_pair)) {
972              FindOrInsert::Inserted (_) => {}
973              FindOrInsert::Found    (_) => unreachable!()
974            }
975          }
976        }
977        //
978        //  dynamic v. dynamic
979        //
980        object::Kind::Dynamic => {
981          let key_a   = object_id_a.key();
982          let key_b   = object_id_b.key();
983          let index_a = key_a.index();
984          let index_b = key_b.index();
985          if let (Some (island_key_a), Some (island_key_b)) = (
986            constraint_manager.get_island_key (object_id_a),
987            constraint_manager.get_island_key (object_id_b)
988          ) && island_key_a == island_key_b {
989            let island = constraint_manager.get_island (island_key_a).unwrap();
990            for (pair, _) in island.constraints.iter() {
991              if *pair == object_pair {
992                continue 'outer
993              }
994            }
995          }
996          // should be safe to unwrap: objects should not be destroyed during
997          // collision detection
998          let object_a          = &objects_dynamic[index_a];
999          let object_b          = &objects_dynamic[index_b];
1000          let velocity_a        = object_a.derivatives.velocity;
1001          let velocity_b        = object_b.derivatives.velocity;
1002          let pseudovelocity_a  = constraint_manager.pseudo_velocities[key_a.index()];
1003          let pseudovelocity_b  = constraint_manager.pseudo_velocities[key_b.index()];
1004          let velocity_effective_a = velocity_a + pseudovelocity_a;
1005          let velocity_effective_b = velocity_b + pseudovelocity_b;
1006          let velocity_effective_relative =
1007            velocity_effective_a - velocity_effective_b;
1008          let velocity_effective_relative_reciprocal =
1009            velocity_effective_relative.recip();
1010          let aabb_current_a    = self.broad.get_aabb_dynamic_discrete   (key_a);
1011          let aabb_current_b    = self.broad.get_aabb_dynamic_discrete   (key_b);
1012          let _aabb_swept_a     = self.broad.get_aabb_dynamic_continuous (key_a);
1013          let _aabb_swept_b     = self.broad.get_aabb_dynamic_continuous (key_b);
1014          let aabb_previous_a   = Aabb3::with_minmax_unchecked (
1015            aabb_current_a.min() - velocity_effective_a,
1016            aabb_current_a.max() - velocity_effective_a);
1017          let aabb_previous_b   = Aabb3::with_minmax_unchecked (
1018            aabb_current_b.min() - velocity_effective_b,
1019            aabb_current_b.max() - velocity_effective_b);
1020
1021          // compute overlap time start/end interval for the given axis
1022          let overlap_interval_axis = |axis : Axis3| {
1023            let axis_index = axis as usize;
1024            // div by zero will result in `inf` values; t_margin adjusts
1025            // start/end times to avoid getting too close
1026            let t_margin = (0.5 * CONTACT_DISTANCE *
1027              velocity_effective_relative_reciprocal[axis_index]).abs();
1028            let velocity_relative = velocity_effective_relative[axis_index];
1029            let velocity_relative_reciprocal =
1030              velocity_effective_relative_reciprocal[axis_index];
1031            let prev_a_min = aabb_previous_a.min().0[axis_index];
1032            let prev_a_max = aabb_previous_a.max().0[axis_index];
1033            let prev_b_min = aabb_previous_b.min().0[axis_index];
1034            let prev_b_max = aabb_previous_b.max().0[axis_index];
1035            if velocity_relative == 0.0 {
1036              // NOTE: even though the relative velocity is zero, the previous AABBs may
1037              // not be overlapping due to rounding errors; we will still consider the
1038              // begin/end time to be infinite
1039              geometry::Interval::with_minmax_unchecked (
1040                f64::NEG_INFINITY, f64::INFINITY)
1041            } else if velocity_relative > 0.0 {
1042              geometry::Interval::with_minmax_unchecked (
1043                //(prev_b_min - prev_a_max) * velocity_relative_reciprocal - t_margin,
1044                (prev_b_min - prev_a_max)
1045                  .mul_add (velocity_relative_reciprocal, -t_margin),
1046                //(prev_b_max - prev_a_min) * velocity_relative_reciprocal + t_margin
1047                (prev_b_max - prev_a_min)
1048                  .mul_add (velocity_relative_reciprocal, t_margin)
1049              )
1050            } else {
1051              debug_assert!(velocity_relative < 0.0);
1052              geometry::Interval::with_minmax_unchecked (
1053                //(prev_b_max - prev_a_min) * velocity_relative_reciprocal - t_margin,
1054                (prev_b_max - prev_a_min)
1055                  .mul_add (velocity_relative_reciprocal, -t_margin),
1056                //(prev_b_min - prev_a_max) * velocity_relative_reciprocal + t_margin
1057                (prev_b_min - prev_a_max)
1058                  .mul_add (velocity_relative_reciprocal, t_margin)
1059              )
1060            }
1061          };
1062          let overlap_interval_x = overlap_interval_axis (Axis3::X);
1063          let overlap_interval_y = overlap_interval_axis (Axis3::Y);
1064          let overlap_interval_z = overlap_interval_axis (Axis3::Z);
1065
1066          if let Some (interval) = overlap_interval_x.intersection (overlap_interval_y)
1067            .and_then (|overlap_interval_xy|
1068              overlap_interval_xy.intersection (overlap_interval_z))
1069            && interval.min() < 1.0 && 0.0 < interval.max()
1070          {
1071            let mid_toi_pair = (
1072              Normalized::clamp (f64::max (interval.min(), last_toi)),
1073              Normalized::clamp (interval.max()),
1074              object_pair
1075            );
1076            match self.pipeline.mid_toi_pairs.find_or_insert (Reverse (mid_toi_pair)) {
1077              FindOrInsert::Inserted (_) => {}
1078              FindOrInsert::Found    (_) => unreachable!()
1079            }
1080          }
1081        }
1082        object::Kind::Nodetect => unreachable!()
1083      }
1084    }
1085
1086    log::trace!(mid_toi_pairs:?=self.pipeline.mid_toi_pairs; "mid TOI pairs");
1087
1088    self.pipeline.broad_overlap_pairs.clear();
1089  }
1090
1091  /// Returns narrow TOI pairs.
1092  ///
1093  /// Note that the algorithm will aim for a distance of `0.5 * CONTACT_DISTANCE` at
1094  /// each TOI.
1095  fn narrow_toi_contacts (&mut self,
1096    objects_static    : &VecMap <object::Static>,
1097    objects_dynamic   : &VecMap <object::Dynamic>,
1098    pseudo_velocities : &VecMap <Vector3 <f64>>
1099  ) {
1100    use sorted_vec::FindOrInsert;
1101
1102    let narrow_toi_contacts = &mut self.pipeline.narrow_toi_contacts;
1103    while let Some (mid_toi_pair) = self.pipeline.mid_toi_pairs.last().copied() {
1104      let earliest_toi_contact = if !narrow_toi_contacts.is_empty() {
1105        narrow_toi_contacts[0].0.0
1106      } else {
1107        Normalized::clamp (1.0)
1108      };
1109
1110      if mid_toi_pair.0.0 < earliest_toi_contact {
1111        let (mid_toi_start, _mid_toi_end, object_pair) = mid_toi_pair.0;
1112        let (object_id_a, object_id_b) = object_pair.into();
1113        let kind_a  = object_id_a.kind();
1114        let kind_b  = object_id_b.kind();
1115        match (kind_a, kind_b) {
1116          (object::Kind::Static, object::Kind::Dynamic) => {
1117            let key_static    = object_id_a.key();
1118            let key_dynamic   = object_id_b.key();
1119            let index_static  = key_static.index();
1120            let index_dynamic = key_dynamic.index();
1121            // safe to unwrap
1122            let mut dynamic_object         = objects_dynamic[index_dynamic].clone();
1123            let dynamic_velocity           = dynamic_object.derivatives.velocity;
1124            let dynamic_pseudovelocity     = pseudo_velocities[index_dynamic];
1125            let dynamic_velocity_effective = dynamic_velocity - dynamic_pseudovelocity;
1126            let static_object              = objects_static[index_static].clone();
1127            let mut narrow_toi             = *mid_toi_start;
1128            // rewind object to the narrow_toi: the object is currently at
1129            // t==1.0, so we are lerping a negative value
1130            object::lerp (&mut dynamic_object, dynamic_pseudovelocity, -1.0 + narrow_toi);
1131            let mut t_remaining = 1.0 - narrow_toi;
1132            #[expect(clippy::used_underscore_binding)]
1133            let mut _iter       = 0;
1134            loop {
1135              #[cfg(debug_assertions)]
1136              if self.narrow_toi_max_iter_count < _iter {
1137                self.narrow_toi_max_iter_count = _iter;
1138              }
1139              let proximity = Proximity::query (&static_object, &dynamic_object);
1140              if cfg!(debug_assertions) &&
1141                dynamic_object.collidable && static_object.collidable
1142              {
1143                debug_assert!(proximity.distance >= 0.0,
1144                  "objects should not be overlapping, distance: {}", proximity.distance);
1145              }
1146              let dynamic_velocity_effective_normal =
1147                dynamic_velocity_effective.dot (-*proximity.normal);
1148              if dynamic_velocity_effective_normal > 0.0 &&
1149                dynamic_object.collidable && static_object.collidable
1150              {
1151                // no collision: separating velocity zero or positive
1152                log::trace!(
1153                  t=narrow_toi, velocity=dynamic_velocity_effective_normal;
1154                  "collision rejected, separating velocity");
1155                break
1156              } else if proximity.distance < CONTACT_DISTANCE {
1157                debug_assert!(dynamic_velocity_effective_normal <= 0.0 ||
1158                  !dynamic_object.collidable || !static_object.collidable);
1159                // collision: toi contact
1160                log::trace!(t=narrow_toi, proximity:?; "collision detected");
1161                // TODO: it appears that sometimes the proximity distance can be
1162                // negative here, is that correct?
1163                // let collision = constraint::Contact::try_from (proximity).unwrap();
1164                let collision = constraint::Contact {
1165                  normal:   proximity.normal,
1166                  distance: proximity.distance
1167                };
1168                let narrow_toi_contact = Reverse ((
1169                  Normalized::noisy (narrow_toi), object_pair, collision
1170                ));
1171                match narrow_toi_contacts.find_or_insert (narrow_toi_contact) {
1172                  FindOrInsert::Inserted (_) => {}
1173                  FindOrInsert::Found    (_) => unreachable!()
1174                }
1175                break
1176              } else if dynamic_velocity_effective_normal == 0.0 {
1177                log::trace!(
1178                  t=narrow_toi, velocity=dynamic_velocity_effective_normal;
1179                  "collision rejected, zero relative velocity and objects not \
1180                  in contact");
1181                break
1182              }
1183              let dynamic_velocity_effective_normal_reciprocal =
1184                1.0 / dynamic_velocity_effective_normal;
1185              let toi_axis =
1186                //narrow_toi +
1187                //(proximity.distance - 0.5 * CONTACT_DISTANCE) *
1188                //(-dynamic_velocity_effective_normal_reciprocal);
1189                0.5f64.mul_add (-CONTACT_DISTANCE, proximity.distance)
1190                  .mul_add (-dynamic_velocity_effective_normal_reciprocal, narrow_toi);
1191              debug_assert!(toi_axis > narrow_toi ||
1192                !dynamic_object.collidable || !static_object.collidable);
1193              if toi_axis > 1.0 || toi_axis < 0.0 {
1194                // no collision this step
1195                log::trace!(t=narrow_toi, proximity:?;
1196                  "collision rejected, final proximity");
1197                break
1198              }
1199              let t_advance = toi_axis - narrow_toi;
1200              object::lerp (&mut dynamic_object, dynamic_pseudovelocity, t_advance);
1201              narrow_toi    = toi_axis;
1202              t_remaining   -= t_advance;
1203              debug_assert!(t_remaining > 0.0);
1204              _iter += 1;
1205            }
1206          }
1207
1208          (object::Kind::Dynamic, object::Kind::Dynamic) => {
1209            let key_a   = object_id_a.key();
1210            let key_b   = object_id_b.key();
1211            let index_a = key_a.index();
1212            let index_b = key_b.index();
1213             // safe to unwrap: objects should not be destroyed during collision
1214             // detection
1215            let mut object_a         = objects_dynamic[index_a].clone();
1216            let mut object_b         = objects_dynamic[index_b].clone();
1217            let pseudovelocity_a     = pseudo_velocities[index_a];
1218            let pseudovelocity_b     = pseudo_velocities[index_b];
1219            let velocity_a           = object_a.derivatives.velocity;
1220            let velocity_b           = object_b.derivatives.velocity;
1221            let velocity_effective_a = velocity_a + pseudovelocity_a;
1222            let velocity_effective_b = velocity_b + pseudovelocity_b;
1223            let velocity_effective_relative =
1224              velocity_effective_a - velocity_effective_b;
1225            let mut narrow_toi  = *mid_toi_start;
1226            let mut t_remaining = 1.0 - narrow_toi;
1227            object::lerp (&mut object_a, pseudovelocity_a, -1.0 + narrow_toi);
1228            object::lerp (&mut object_b, pseudovelocity_b, -1.0 + narrow_toi);
1229            loop {
1230              debug_assert!(narrow_toi <= 1.0);
1231              debug_assert!(t_remaining >= 0.0);
1232              let proximity = Proximity::query (&object_a, &object_b);
1233              if cfg!(debug_assertions) && object_a.collidable && object_b.collidable {
1234                debug_assert!(proximity.distance >= 0.0);
1235              }
1236              let velocity_effective_relative_normal =
1237                velocity_effective_relative.dot (*proximity.normal);
1238              if velocity_effective_relative_normal > 0.0 &&
1239                object_a.collidable && object_b.collidable
1240              {
1241                // no collision: separating velocity zero or positive
1242                log::trace!(
1243                  t=narrow_toi, velocity=velocity_effective_relative_normal;
1244                  "collision rejected, separating velocity");
1245                break
1246              } else if proximity.distance < CONTACT_DISTANCE {
1247                debug_assert!(velocity_effective_relative_normal <= 0.0 ||
1248                  !object_a.collidable || !object_b.collidable);
1249                // collision: toi contact
1250                log::trace!(t=narrow_toi, proximity:?; "collision detected");
1251                let contact = constraint::Contact::try_from (proximity).unwrap();
1252                let narrow_toi_contact = Reverse ((
1253                  Normalized::noisy (narrow_toi), object_pair, contact
1254                ));
1255                match narrow_toi_contacts.find_or_insert (narrow_toi_contact) {
1256                  FindOrInsert::Inserted (_) => {}
1257                  FindOrInsert::Found    (_) => unreachable!()
1258                }
1259                break
1260              } else if velocity_effective_relative_normal == 0.0 {
1261                log::trace!(
1262                  t=narrow_toi, velocity=velocity_effective_relative_normal;
1263                  "collision rejected, zero relative velocity and objects not in \
1264                  contact");
1265                break
1266              }
1267              let velocity_effective_relative_normal_reciprocal =
1268                1.0 / velocity_effective_relative_normal;
1269              let toi_axis =
1270                //narrow_toi +
1271                //(proximity.distance - 0.5 * CONTACT_DISTANCE) *
1272                //(-velocity_effective_relative_normal_reciprocal);
1273                0.5f64.mul_add (-CONTACT_DISTANCE, proximity.distance)
1274                  .mul_add (-velocity_effective_relative_normal_reciprocal, narrow_toi);
1275              debug_assert!(toi_axis > narrow_toi ||
1276                !object_a.collidable || !object_b.collidable);
1277              if toi_axis > 1.0 || toi_axis < 0.0 {
1278                // no collision this step
1279                log::trace!(t=narrow_toi, proximity:?;
1280                  "collision rejected, final proximity");
1281                break
1282              }
1283              let t_advance = toi_axis - narrow_toi;
1284              object::lerp (&mut object_a, pseudovelocity_a, t_advance);
1285              object::lerp (&mut object_b, pseudovelocity_b, t_advance);
1286              narrow_toi    = toi_axis;
1287              t_remaining   -= t_advance;
1288            }
1289          }
1290          _ => unreachable!()
1291        }
1292        self.pipeline.mid_toi_pairs.pop();
1293      } else { break }
1294    }
1295    #[cfg(debug_assertions)]
1296    log::debug!(max_iter_count=self.narrow_toi_max_iter_count;
1297      "narrow TOI max iters");
1298    log::trace!(contacts:?=narrow_toi_contacts; "narrow TOI contacts");
1299  }
1300
1301  #[inline]
1302  fn add_object_static (&mut self,
1303    object     : &object::Static,
1304    object_key : object::Key,
1305  ) {
1306    self.broad.add_object_static (object.aabb_dilated(), object_key);
1307  }
1308
1309  #[inline]
1310  fn add_object_dynamic (&mut self,
1311    pseudo_velocities : &mut VecMap <Vector3 <f64>>,
1312    object            : &object::Dynamic,
1313    object_key        : object::Key
1314  ) {
1315    let aabb_discrete   = object.aabb_dilated();
1316    let aabb_continuous = Aabb3::with_minmax_unchecked (
1317      aabb_discrete.min() - object.derivatives.velocity,
1318      aabb_discrete.max() - object.derivatives.velocity);
1319    // NB: this swept AABB should not be seen by the next continuous collision
1320    // detection/resolution loop since `begin_step()` will re-calculate for the
1321    // next frame
1322    self.broad.add_object_dynamic (aabb_discrete, aabb_continuous, object_key);
1323    assert!(pseudo_velocities.insert (object_key.index(), Vector3::zero()).is_none());
1324  }
1325
1326}
1327
1328impl Pipeline {
1329  #[inline]
1330  fn is_empty (&self) -> bool {
1331    self.broad_overlap_pairs.is_empty() &&
1332    self.mid_toi_pairs.is_empty() &&
1333    self.narrow_toi_contacts.is_empty() &&
1334    self.resolved_collisions.is_empty() &&
1335    self.overlaps.is_empty()
1336  }
1337
1338  /// After a collision is resolved, all modified (dynamic) objects should be removed
1339  /// from intermediate results in the pipeline:
1340  ///
1341  /// - `mid_toi_pairs`
1342  /// - `narrow_toi_contacts`
1343  ///
1344  /// These objects will be checked for overlaps in the broad phase of the next
1345  /// detect/resolve loop iteration.
1346  // TODO: because we are only removing resolved dynamic objects and the resolved list
1347  // is sorted, we could be slicing the resolved set to only contain the dynamic ids and
1348  // exclude the static ids from binary search
1349  #[inline]
1350  fn remove_resolved_dynamic (&mut self, resolved : &SortedSet <InternalId>) {
1351    debug_assert!(self.broad_overlap_pairs.is_empty());
1352
1353    let (mut i, mut len);
1354
1355    // remove intermediate mid TOI pairs
1356    i   = 0;
1357    len = self.mid_toi_pairs.len();
1358    while i < len {
1359      let (_, _, object_pair) = self.mid_toi_pairs[i].0;
1360      let (id_a, id_b) = object_pair.into();
1361      if id_a.kind() == object::Kind::Dynamic && resolved.binary_search (&id_a).is_ok() {
1362        self.mid_toi_pairs.remove_index (i);
1363        len -= 1;
1364        continue
1365      }
1366      debug_assert_eq!(id_b.kind(), object::Kind::Dynamic);
1367      if resolved.binary_search (&id_b).is_ok() {
1368        self.mid_toi_pairs.remove_index (i);
1369        len -= 1;
1370        continue
1371      }
1372      i += 1;
1373    }
1374
1375    // remove intermediate narrow TOI contacts
1376    i   = 0;
1377    len = self.narrow_toi_contacts.len();
1378    while i < len {
1379      let (_, object_pair, _) = self.narrow_toi_contacts[i].clone().0;
1380      let (id_a, id_b) = object_pair.into();
1381      if id_a.kind() == object::Kind::Dynamic && resolved.binary_search (&id_a).is_ok() {
1382        self.narrow_toi_contacts.remove_index (i);
1383        len -= 1;
1384        continue
1385      }
1386      debug_assert_eq!(id_b.kind(), object::Kind::Dynamic);
1387      if resolved.binary_search (&id_b).is_ok() {
1388        self.narrow_toi_contacts.remove_index (i);
1389        len -= 1;
1390        continue
1391      }
1392      i += 1;
1393    }
1394  }
1395}