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 derive_more::Into;
9use log;
10use sorted_vec::{SortedSet, ReverseSortedSet};
11use stash::Stash;
12use vec_map::VecMap;
13#[cfg(feature = "derive_serdes")]
14use serde::{Deserialize, Serialize};
15
16use crate::{component, event, geometry, math, object};
17use math::*;
18use geometry::Aabb3;
19
20pub mod contact;
21pub mod proximity;
22pub mod intersection;
23mod broad;
24
25pub use self::contact::Contact;
26pub use self::proximity::Proximity;
27pub use self::intersection::Intersection;
28use self::broad::Broad;
29
30////////////////////////////////////////////////////////////////////////////////
31//  constants                                                                 //
32////////////////////////////////////////////////////////////////////////////////
33
34/// Distance criterea for contacts and collisions (TOI contacts).
35///
36/// Continuous collision detection will aim to return the TOI for when the objects are
37/// at a distance approximately `0.5 * CONTACT_DISTANCE` in order to prevent
38/// intersection due to numerical error.
39///
40/// Post-impulse position correction will also move objects apart to a distance of
41/// `0.5 * CONTACT_DISTANCE`.
42pub const CONTACT_DISTANCE : f64 = 0.005;   // 5mm
43/// Velocity for determination of whether a normal impulse should be applied
44pub const RESTING_VELOCITY : f64 = (1.0/120.0) * CONTACT_DISTANCE;
45/// The value of the dynamic bit also gives the maximum key value allowed for objects in
46/// the collision system.
47pub const OBJECT_KEY_MAX   : object::KeyType = INTERNAL_ID_DYNAMIC_BIT - 1;
48
49/// Used by `InternalId` to indicate a dynamic object identifier, otherwise if this bit
50/// is zero the object identifier is for a static object
51pub (crate) const INTERNAL_ID_DYNAMIC_BIT : object::KeyType =
52  1 << (object::KeyType::BITS - 1);   // most significant bit
53
54#[cfg(feature = "debug_dump")]
55#[cfg_attr(docsrs, doc(cfg(feature="debug_dump")))]
56pub static DEBUG_DUMP : atomic::AtomicBool = atomic::AtomicBool::new (false);
57#[cfg(feature = "debug_dump")]
58#[cfg_attr(docsrs, doc(cfg(feature="debug_dump")))]
59pub const DEBUG_DUMP_DETECT_RESOLVE_MAX_ITERS : u64 = 200;
60
61////////////////////////////////////////////////////////////////////////////////
62//  structs                                                                   //
63////////////////////////////////////////////////////////////////////////////////
64
65/// Collision subsystem
66#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
67#[derive(Clone, Debug, Default)]
68pub struct Collision {
69  /// Broad-phase detection subsystem
70  broad             : Broad,
71  /// Pseudo-velocities.
72  ///
73  /// Post-impulse position corrections utilize this velocity which is reset to
74  /// zero at the beginning of each collision step.
75  pseudo_velocities : VecMap <Vector3 <f64>>,
76  /// Collision pipeline data: broad, mid, and narrow phase results.
77  ///
78  /// The pipeline should always be empty at the beginning and end of each
79  /// collision step and is skipped when serializing the collision state
80  /// resulting in a default initialized (empty) pipeline when deserialized.
81  #[cfg_attr(feature = "derive_serdes", serde(skip))]
82  pipeline          : Pipeline,
83  /// Persistent contact groups
84  persistent        : contact::Manager,
85  /// Count maximum iterations of collision detect/resolve main loop
86  #[cfg(debug_assertions)]
87  detect_resolve_max_iter_count : u64,
88  /// Count maximum iterations of narrow phase loop
89  #[cfg(debug_assertions)]
90  narrow_toi_max_iter_count     : u64
91}
92
93/// Collision pipeline data.
94///
95/// A collision detection step should begin and end with all vectors empty.
96#[derive(Clone, Debug, Default, PartialEq)]
97struct Pipeline {
98  /// Counts iterations of the `detect_resolve` loop; reset to zero at the
99  /// beginning of a collision step
100  pub detect_resolve_iter : u64,
101  /// Broad phase AABB overlaps on all three axes
102  pub broad_overlap_pairs : Vec <ObjectPair>,
103  /// Sorted in *reverse order* by start time
104  pub mid_toi_pairs       :
105    ReverseSortedSet <(Normalized <f64>, Normalized <f64>, ObjectPair)>,
106  /// Sorted in *reverse order* by TOI
107  pub narrow_toi_contacts :
108    ReverseSortedSet <(Normalized <f64>, ObjectPair, contact::Colliding)>,
109  pub resolved_collisions : Vec <event::CollisionResolve>,
110  /// Nocollide intersections
111  pub overlaps            : Vec <event::Overlap>,
112}
113
114/// (Sorted) static/dynamic or dynamic/dynamic pair
115#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
116#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Into)]
117pub (crate) struct ObjectPair (InternalId, InternalId);
118
119/// Represents a static or dynamic object identifier by using the most
120/// significant bit of the object key type (0 for static, 1 for dynamic).
121#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
122#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
123pub (crate) struct InternalId (object::KeyType);
124
125#[derive(Debug)]
126struct ContactImpulses {
127  pub impulse_normal_a  : Vector3 <f64>,
128  pub impulse_normal_b  : Vector3 <f64>,
129  pub impulse_tangent_a : Vector3 <f64>,
130  pub impulse_tangent_b : Vector3 <f64>
131}
132
133#[derive(Debug)]
134struct ContactPostImpulses {
135  pub pseudo_impulse_a : Vector3 <f64>,
136  pub pseudo_impulse_b : Vector3 <f64>
137}
138
139////////////////////////////////////////////////////////////////////////////////
140//  functions                                                                 //
141////////////////////////////////////////////////////////////////////////////////
142
143pub fn report_sizes() {
144  use std::mem::size_of;
145  println!("collision report sizes...");
146
147  println!("  size of Collision: {}", size_of::<Collision>());
148
149  println!("...collision report sizes");
150}
151
152/// Linear-interpolate an object with the given pseudovelocity
153fn lerp_object <O : object::Temporal> (
154  object : &mut O, pseudovelocity : Vector3 <f64>, t_relative : f64
155) {
156  let component::Position (position) = object.position();
157  object.position_mut().0 = position +
158    t_relative * object.derivatives().velocity + t_relative * pseudovelocity;
159}
160
161fn contact_constrain_velocity (
162  contact          : Contact,
163  restitution      : Normalized <f64>,   // 0.0 for a resting contact
164  mut object_a     : Either <&object::Static, &mut object::Dynamic>,
165  object_b         : &mut object::Dynamic,
166  pseudovelocity_a : Option <Vector3 <f64>>,
167  pseudovelocity_b : Vector3 <f64>
168) -> Option <ContactImpulses> {
169  let velocity_effective_a = match &object_a {
170    Either::Left (_) => {
171      debug_assert!(pseudovelocity_a.is_none());
172      Vector3::zero()
173    }
174    Either::Right (object_a) => object_a.derivatives.velocity +
175      pseudovelocity_a.unwrap_or (Vector3::zero())
176  };
177  let velocity_effective_b = object_b.derivatives.velocity + pseudovelocity_b;
178  let velocity_effective_relative = velocity_effective_a - velocity_effective_b;
179  let velocity_normal_component =
180    velocity_effective_relative.dot (*contact.constraint.normal());
181  let mut impulses        = None;
182  let mut mass_relative_a = 0.0;
183  let mut mass_relative_b = 0.0;
184  if velocity_normal_component < 0.0 {
185    let velocity_normal = velocity_normal_component * *contact.constraint.normal();
186    // normal impulse
187    let impulse_normal = velocity_normal + *restitution * velocity_normal;
188    let impulse_normal_magnitude2 = impulse_normal.magnitude_squared();
189    if impulse_normal_magnitude2 < RESTING_VELOCITY * RESTING_VELOCITY {
190      return None
191    }
192    let (impulse_normal_a, impulse_normal_b) = match &mut object_a {
193      Either::Right (object_a) => {
194        let mass_combined    = object_a.mass + object_b.mass;
195        mass_relative_a      = object_a.mass.mass() * mass_combined.mass_reciprocal();
196        mass_relative_b      = object_b.mass.mass() * mass_combined.mass_reciprocal();
197        let impulse_normal_a = -mass_relative_b * impulse_normal;
198        let impulse_normal_b = mass_relative_a * impulse_normal;
199        object_a.derivatives.velocity += impulse_normal_a;
200        object_b.derivatives.velocity += impulse_normal_b;
201        (impulse_normal_a, impulse_normal_b)
202      }
203      Either::Left (_) => {
204        object_b.derivatives.velocity += impulse_normal;
205        (Vector3::zero(), impulse_normal)
206      }
207    };
208    // friction (tangent) impulse
209    let (impulse_tangent_a, impulse_tangent_b) = {
210      let velocity_tangent = velocity_effective_relative - velocity_normal;
211      let velocity_tangent_magnitude2 = velocity_tangent.magnitude_squared();
212      if velocity_tangent_magnitude2 > 0.0 {
213        let friction_a = match &object_a {
214          Either::Left  (x) => x.material.friction,
215          Either::Right (x) => x.material.friction
216        };
217        let friction_coefficient = friction_a * object_b.material.friction;
218        let impulse_tangent_potential_magnitude2 =
219          *friction_coefficient * impulse_normal_magnitude2;
220        let impulse_tangent = if impulse_tangent_potential_magnitude2 <
221          velocity_tangent_magnitude2
222        {
223          (impulse_tangent_potential_magnitude2.sqrt()
224          / velocity_tangent_magnitude2.sqrt()) * velocity_tangent
225        } else {
226          velocity_tangent
227        };
228        match object_a {
229          Either::Right (object_a) => {
230            let impulse_tangent_a = -mass_relative_b * impulse_tangent;
231            let impulse_tangent_b = mass_relative_a * impulse_tangent;
232            object_a.derivatives.velocity += impulse_tangent_a;
233            object_b.derivatives.velocity += impulse_tangent_b;
234            (impulse_tangent_a, impulse_tangent_b)
235          }
236          Either::Left (_) => {
237            object_b.derivatives.velocity += impulse_tangent;
238            (Vector3::zero(), impulse_tangent)
239          }
240        }
241      } else {
242        (Vector3::zero(), Vector3::zero())
243      }
244    };
245    impulses = Some (ContactImpulses {
246      impulse_normal_a, impulse_normal_b,
247      impulse_tangent_a, impulse_tangent_b
248    });
249  }
250  impulses
251}
252
253fn contact_constrain_position (
254  t_reverse        : f64,
255  mut object_a     : Either <&object::Static, &mut object::Dynamic>,
256  object_b         : &mut object::Dynamic,
257  pseudovelocity_a : Option <&mut Vector3 <f64>>,
258  pseudovelocity_b : &mut Vector3 <f64>
259) -> (Option <ContactPostImpulses>, Proximity) {
260  let t_forward = -t_reverse;
261  let mut impulses  = None;
262  let mut proximity = match &object_a {
263    Either::Left  (object_a) => Proximity::query (*object_a, object_b),
264    Either::Right (object_a) => Proximity::query (*object_a, object_b)
265  };
266  log::trace!(distance=proximity.distance; "post impulse contact @ T==1.0");
267  if proximity.distance < 0.0 {
268    let (pseudo_impulse_a, pseudo_impulse_b) = match &mut object_a {
269      Either::Left  (object_a) => {
270        lerp_object (object_b, *pseudovelocity_b, t_reverse);
271        let pseudo_impulse_b = -(2.0 * proximity.half_axis +
272          0.5 * CONTACT_DISTANCE * *proximity.normal) / t_forward;
273        *pseudovelocity_b += pseudo_impulse_b;
274        lerp_object (object_b, *pseudovelocity_b, t_forward);
275        proximity = Proximity::query (*object_a, object_b);
276        (Vector3::zero(), pseudo_impulse_b)
277      }
278      Either::Right (object_a) => {
279        let pseudovelocity_a = pseudovelocity_a.unwrap();
280        lerp_object (*object_a, *pseudovelocity_a, t_reverse);
281        lerp_object (object_b, *pseudovelocity_b, t_reverse);
282        let mass_combined    = object_a.mass + object_b.mass;
283        let mass_relative_a  = object_a.mass.mass() * mass_combined.mass_reciprocal();
284        let mass_relative_b  = object_b.mass.mass() * mass_combined.mass_reciprocal();
285        let pseudo_impulse   = (2.0 * proximity.half_axis +
286          0.5 * CONTACT_DISTANCE * *proximity.normal) / t_forward;
287        let pseudo_impulse_a = mass_relative_b * pseudo_impulse;
288        let pseudo_impulse_b = -mass_relative_a * pseudo_impulse;
289        *pseudovelocity_a += pseudo_impulse_a;
290        *pseudovelocity_b += pseudo_impulse_b;
291        lerp_object (*object_a, *pseudovelocity_a, t_forward);
292        lerp_object (object_b, *pseudovelocity_b, t_forward);
293        proximity = Proximity::query (*object_a, object_b);
294        (pseudo_impulse_a, pseudo_impulse_b)
295      }
296    };
297    log::trace!(distance=proximity.distance; "position corrected distance @ T==1.0");
298    debug_assert!(proximity.distance >= 0.0);
299    if cfg!(debug_assertions) {
300      approx::assert_abs_diff_eq!(
301        proximity.distance, 0.5 * CONTACT_DISTANCE, epsilon=0.000_000_001);
302    }
303    impulses = Some (ContactPostImpulses { pseudo_impulse_a, pseudo_impulse_b });
304  }
305  (impulses, proximity)
306}
307
308////////////////////////////////////////////////////////////////////////////////
309//  impls                                                                     //
310////////////////////////////////////////////////////////////////////////////////
311
312impl Collision {
313  pub(super) const fn broad (&self) -> &Broad {
314    &self.broad
315  }
316  /// Attempt to add a new static object to the collision subsystem.
317  ///
318  /// Queries distance to each dynamic object and if any intersections are
319  /// found, the object is not added and the intersections are returned.
320  ///
321  /// Only counts intersections if both objects are collidable.
322  pub fn try_add_object_static (&mut self,
323    objects_dynamic : &VecMap <object::Dynamic>,
324    object          : &object::Static,
325    key             : object::Key
326  ) -> Result <(), Vec <(object::Id, Intersection)>> {
327    let mut intersections = Vec::new();
328    if object.collidable {
329      let aabb_discrete = object.aabb_dilated();
330      let overlaps : Vec <object::Id> = self.broad.overlaps_discrete (aabb_discrete)
331        .into_iter().map (object::Id::from).collect::<Vec <_>>();
332      for object_id in overlaps {
333        let proximity = match object_id.kind {
334          object::Kind::Static  => continue,  // skip static/static checks
335          object::Kind::Dynamic => {
336            let object_dynamic = &objects_dynamic[object_id.key.index()];
337            if !object_dynamic.collidable {
338              continue                    // skip non-collidable dynamic objects
339            }
340            Proximity::query (object_dynamic, object)
341          }
342          object::Kind::Nodetect => unreachable!()
343        };
344        if let Ok (intersection) = proximity.try_into() {
345          intersections.push ((object_id, intersection));
346        }
347      }
348    }
349    if intersections.is_empty() {
350      self.add_object_static (object, key);
351      Ok  (())
352    } else {
353      Err (intersections)
354    }
355  }
356
357  #[inline]
358  pub fn remove_object_static (&mut self, object_key : object::Key) {
359    debug_assert!(self.pipeline.is_empty());
360    let id = InternalId::new_static (object_key);
361    self.broad.remove_object (id);
362    let _ = self.persistent.remove_object (id);
363  }
364
365  /// Attempts to add a new dynamic object.
366  ///
367  /// Queries distance to each static and dynamic object and if any intersections are
368  /// found, the object is not added and the intersections are returned.
369  ///
370  /// Only counts intersections if both objects are collidable.
371  pub fn try_add_object_dynamic (&mut self,
372    objects_static  : &VecMap <object::Static>,
373    objects_dynamic : &VecMap <object::Dynamic>,
374    object          : &object::Dynamic,
375    key             : object::Key
376  ) -> Result <(), Vec <(object::Id, Intersection)>> {
377    let mut intersections = Vec::new();
378    if object.collidable {
379      let aabb_discrete     = object.aabb_dilated();
380      let overlaps : Vec <object::Id> = self.broad.overlaps_discrete (aabb_discrete)
381        .into_iter().map (object::Id::from).collect::<Vec <_>>();
382      for object_id in overlaps {
383        let proximity = match object_id.kind {
384          object::Kind::Static  => {
385            let object_static = &objects_static[object_id.key.index()];
386            if !object_static.collidable {
387              continue  // skip non-collidable static objects
388            }
389            Proximity::query (object_static, object)
390          }
391          object::Kind::Dynamic => {
392            let object_dynamic = &objects_dynamic[object_id.key.index()];
393            if !object_dynamic.collidable {
394              continue  // skip non-collidable static objects
395            }
396            Proximity::query (object_dynamic, object)
397          }
398          object::Kind::Nodetect => unreachable!()
399        };
400        if let Ok (intersection) = proximity.try_into() {
401          intersections.push ((object_id, intersection));
402        }
403      }
404    }
405    if intersections.is_empty() {
406      self.add_object_dynamic (object, key);
407      Ok  (())
408    } else {
409      Err (intersections)
410    }
411  }
412
413  #[inline]
414  pub fn remove_object_dynamic (&mut self, key : object::Key) {
415    debug_assert!(self.pipeline.is_empty());
416    let id = InternalId::new_dynamic (key);
417    self.broad.remove_object (id);
418    self.pseudo_velocities.remove (key.index()).unwrap();
419    let _ = self.persistent.remove_object (id);
420  }
421
422  #[inline]
423  pub fn update_object_static (&mut self,
424    object : &object::Static, key : object::Key
425  ) {
426    debug_assert!(self.pipeline.is_empty());
427    self.broad.update_aabb_static (object.aabb_dilated(), key);
428    unimplemented!("TODO: collision detect and resolve any overlaps caused by \
429      updating the static object")
430  }
431
432  #[inline]
433  pub fn update_object_dynamic (&mut self,
434    object : &object::Dynamic, key : object::Key
435  ) {
436    debug_assert!(self.pipeline.is_empty());
437    let aabb = object.aabb_dilated();
438    self.broad.update_aabb_dynamic_discrete (aabb, key);
439    // TODO: why is the continuous AABB the same as discrete here?
440    self.broad.update_aabb_dynamic_continuous (aabb, key);
441    log::warn!("TODO: collision detect and resolve any overlaps caused by \
442      updating the dynamic object")
443  }
444
445  //
446  //  Collision::detect_resolve_loop
447  //
448  /// Main collision loop.
449  ///
450  /// Before the loop starts, `begin_step()`:
451  ///
452  /// 1. Update broad-phase AABB data
453  ///
454  /// Inside the collision loop:
455  ///
456  /// 1. Perform continuous detection
457  /// 2. Resolve earliest detected collision
458  ///
459  /// After collision loop:
460  ///
461  /// 1. Drain pipeline outputs (collisions, overlaps, contacts)
462  /// 2. Zero pseudovelocities
463  pub fn detect_resolve_loop (&mut self,
464    objects_static  : &mut VecMap <object::Static>,
465    objects_dynamic : &mut VecMap <object::Dynamic>,
466    step            : u64,
467    output          : &mut Vec <event::Output>
468  ) {
469    log::trace!(step; "detect/resolve loop");
470
471    // initialize collision for the current step
472    debug_assert!(self.pipeline.is_empty());
473    self.pipeline.detect_resolve_iter = 0;
474
475    // broad: update AABBs based on the given dynamic objects state
476    //
477    // the continuous (swept) AABBs are calculated from min/max of the previous
478    // 'discrete' (instantaneous) AABB and the new "current" discrete AABB
479    self.broad.begin_step (objects_dynamic, step);
480
481    // main detect/resolve loop: resolve one collision per iteration
482    loop {
483      log::debug!(iter=self.pipeline.detect_resolve_iter;
484        "detect/resolve loop iteration");
485      // detect collisions (TOI contacts)
486      self.detect_continuous (objects_static, objects_dynamic);
487      // resolve earliest collision: resolve a velocity constraint at TOI and a
488      // position constraint at t==1.0
489      if !self.resolve_collision (objects_static, objects_dynamic) {
490        debug_assert!(self.pipeline.mid_toi_pairs.is_empty());
491        debug_assert!(self.pipeline.narrow_toi_contacts.is_empty());
492        break
493      }
494      self.pipeline.detect_resolve_iter += 1;
495    }
496
497    log::trace!(iters=self.pipeline.detect_resolve_iter+1;
498      "detect/resolve loop complete");
499    #[cfg(debug_assertions)]
500    {
501      if self.detect_resolve_max_iter_count
502        < (self.pipeline.detect_resolve_iter + 1)
503      {
504        #[cfg(feature = "debug_dump")]
505        if self.pipeline.detect_resolve_iter >
506          DEBUG_DUMP_DETECT_RESOLVE_MAX_ITERS
507        {
508          DEBUG_DUMP.store (true, atomic::Ordering::SeqCst);
509        }
510        self.detect_resolve_max_iter_count = self.pipeline.detect_resolve_iter;
511      }
512      log::debug!(max_iter_count=self.detect_resolve_max_iter_count;
513        "detect/resolve loop max iters");
514    }
515
516    // output collision events
517    for collision_resolve in self.pipeline.resolved_collisions.drain (..) {
518      output.push (collision_resolve.into());
519    }
520    for overlap in self.pipeline.overlaps.drain (..) {
521      output.push (overlap.into());
522    }
523
524    // output contacts
525    self.persistent.output_contacts (output);
526
527    // zero pseudovelocities
528    for (_, pseudovelocity) in self.pseudo_velocities.iter_mut() {
529      *pseudovelocity = Vector3::zero();
530    }
531
532    // done
533    debug_assert!(self.pipeline.is_empty());
534  }
535
536  /// Solve positions constraints for persistent contact groups
537  pub fn constrain_contact_positions (&mut self,
538    objects_static  : &VecMap <object::Static>,
539    objects_dynamic : &mut VecMap <object::Dynamic>
540  ) {
541    let mut remove_groups  = vec![];
542    let mut new_groups     = vec![];
543    let mut contact_groups = self.persistent.contact_groups.take().unwrap();
544    for (group_index, group) in contact_groups.iter_mut() {
545      log::debug!(group_index, contacts_count=group.contacts.len();
546        "group constrain positions");
547      log::trace!(group_index, contacts:?=group.contacts;
548        "group constrain positions contacts");
549      let mut iter = 0;
550      let mut remove_contacts = vec![];
551      loop {
552        remove_contacts.clear();
553        let mut satisfied = true;
554        for (contact_index, (object_pair, contact)) in
555          group.contacts.iter_mut().enumerate()
556        {
557          let (object_id_a, object_id_b) = (*object_pair).into();
558          let index_a = object_id_a.key().index();
559          let index_b = object_id_b.key().index();
560          let mut object_a = match object_id_a.kind() {
561            object::Kind::Static   => Either::Left  (objects_static[index_a].clone()),
562            object::Kind::Dynamic  => Either::Right (objects_dynamic[index_a].clone()),
563            object::Kind::Nodetect => unreachable!()
564          };
565          debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
566          let mut object_b = objects_dynamic[index_b].clone();
567          let mut pseudovelocity_a = match object_a {
568            Either::Left  (_) => None,
569            Either::Right (_) => Some (self.pseudo_velocities[index_a])
570          };
571          let mut pseudovelocity_b = self.pseudo_velocities[index_b];
572          let (maybe_impulses, proximity) = contact_constrain_position (
573            -1.0,
574            object_a.as_mut().map_left (|object_static| &*object_static),
575            &mut object_b, pseudovelocity_a.as_mut(), &mut pseudovelocity_b
576          );
577          if maybe_impulses.is_some() {
578            satisfied = false;
579            match object_a {
580              Either::Right (object_a) => {
581                objects_dynamic[index_a]        = object_a;
582                self.pseudo_velocities[index_a] = pseudovelocity_a.unwrap();
583              }
584              Either::Left  (_) => {}
585            }
586            objects_dynamic[index_b]        = object_b;
587            self.pseudo_velocities[index_b] = pseudovelocity_b;
588          }
589          // NOTE: checking the contact distance here instead of the try_into
590          // result so we don't have to clone to use the proximity object in the
591          // debug log statement
592          if proximity.distance >= CONTACT_DISTANCE {
593            log::debug!(object_pair:?, contact:?, proximity:?;
594              "removing contact @ T==1.0");
595            remove_contacts.push (contact_index as u32);
596          } else {
597            // update contact
598            *contact = proximity.try_into().unwrap();
599          }
600        }
601        iter += 1;
602        if satisfied {
603          // group satisfied
604          log::debug!(group_index, iters=iter;
605            "group position constraint satisfied");
606          if !remove_contacts.is_empty() {
607            self.persistent.remove_contacts (group, remove_contacts.as_slice());
608            if group.contacts.is_empty() {
609              remove_groups.push (group_index as contact::group::KeyType);
610            } else {
611              let partitions = group.partition();
612              if !partitions.is_empty() {
613                remove_groups.push (group_index as contact::group::KeyType);
614                new_groups.extend (partitions);
615              }
616            }
617          }
618          break
619        }
620      }
621    }
622    for group_key in remove_groups {
623      let _ = contact_groups.take (group_key as usize).unwrap();
624    }
625    for new_group in new_groups {
626      let dynamic_ids = new_group.dynamic_ids();
627      let group_key = contact_groups.put (new_group) as contact::group::KeyType;
628      for dynamic_id in dynamic_ids.into_vec() {
629        self.persistent.change_dynamic_group_key (dynamic_id, group_key);
630      }
631    }
632    self.persistent.contact_groups = Some (contact_groups);
633  }
634
635  /// Solve velocity constraints for persistent contact groups
636  pub fn constrain_contact_velocities (&self,
637    objects_static  : &VecMap <object::Static>,
638    objects_dynamic : &mut VecMap <object::Dynamic>
639  ) {
640    for (group_index, group) in self.contact_groups().iter() {
641      log::debug!(group_index, contacts_count=group.contacts.len();
642        "group constrain velocities");
643      log::trace!(group_index, contacts:?=group.contacts;
644        "group constrain velocities contacts");
645      let mut iter = 0;
646      loop {
647        let mut satisfied = true;
648        for (object_pair, contact) in group.contacts.iter() {
649          let (object_id_a, object_id_b) = (*object_pair).into();
650          let index_a = object_id_a.key().index();
651          let index_b = object_id_b.key().index();
652          let mut object_a = match object_id_a.kind() {
653            object::Kind::Static   => Either::Left  (objects_static[index_a].clone()),
654            object::Kind::Dynamic  => Either::Right (objects_dynamic[index_a].clone()),
655            object::Kind::Nodetect => unreachable!()
656          };
657          debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
658          let mut object_b = objects_dynamic[index_b].clone();
659          // NOTE: at this stage in the pipeline, pseudovelocities are zero
660          if contact_constrain_velocity (
661            contact.clone(), Normalized::zero(),
662            object_a.as_mut().map_left (|object_static| &*object_static),
663            &mut object_b, None, Vector3::zero()
664          ).is_some() {
665            match object_a {
666              Either::Right (object_a) => objects_dynamic[index_a] = object_a,
667              Either::Left  (_) => {}
668            }
669            objects_dynamic[index_b] = object_b;
670            satisfied = false;
671          }
672        }
673        iter += 1;
674        if satisfied {
675          // group satisfied
676          log::debug!(group_index, iters=iter; "group velocity constraint satisfied");
677          break
678        }
679      }
680    }
681  }
682
683  pub (crate) const fn contact_groups (&self) -> &Stash <contact::Group> {
684    self.persistent.contact_groups.as_ref().unwrap()
685  }
686
687  //
688  //  private methods
689  //
690
691  /// Detect TOI (time-of-impact) contacts, i.e. *collisions*.
692  fn detect_continuous (&mut self,
693    objects_static  : &VecMap <object::Static>,
694    objects_dynamic : &VecMap <object::Dynamic>
695  ) {
696    // collect broad overlap pairs
697    self.broad_overlap_pairs_continuous();
698    // mid-phase TOI pairs
699    self.mid_toi_pairs (objects_dynamic);
700    // narrow-phase TOI pairs
701    self.narrow_toi_contacts (objects_static, objects_dynamic);
702  }
703
704  /// Resolve the first collision (TOI contact pair) in the pipeline, if any,
705  /// and returns true, otherwise returns false.
706  ///
707  /// If impulses do not prevent intersection at t==1.0, post-impulse position
708  /// correction will move objects to a distance of `0.5 * CONTACT_DISTANCE` by
709  /// applying a pseudo-velocity impulse at the collision TOI.
710  ///
711  /// This function will be called multiple times in the
712  /// `detect_resolve_loop()`.
713  fn resolve_collision (&mut self,
714    objects_static  : &VecMap <object::Static>,
715    objects_dynamic : &mut VecMap <object::Dynamic>
716  ) -> bool {
717    if let Some (Reverse ((toi, object_pair, contact))) =
718      self.pipeline.narrow_toi_contacts.pop()
719    {
720      // process the first narrow toi contact (collision): the container is
721      // reverse-sorted so we use pop
722      let (object_id_a, object_id_b) = object_pair.into();
723      let kind_a  = object_id_a.kind();
724      let kind_b  = object_id_b.kind();
725      let index_a = object_id_a.key().index();
726      let index_b = object_id_b.key().index();
727      // if one or both objects are not collidable, we report overlap and return
728      let collidable = {
729        let collidable_a = match kind_a {
730          object::Kind::Static   => objects_static[index_a].collidable,
731          object::Kind::Dynamic  => objects_dynamic[index_a].collidable,
732          object::Kind::Nodetect => unreachable!()
733        };
734        debug_assert_eq!(kind_b, object::Kind::Dynamic);
735        let collidable_b = objects_dynamic[index_b].collidable;
736        collidable_a && collidable_b
737      };
738      if !collidable {
739        let overlap = event::Overlap {
740          object_id_a: object_id_a.into(),
741          object_id_b: object_id_b.into()
742        };
743        log::debug!(toi=*toi, overlap:?; "nocollide overlap");
744        self.pipeline.overlaps.push (overlap);
745        return true
746      }
747      log::debug!(toi=*toi, object_pair:?=(object_id_a, object_id_b);
748        "resolve collision");
749      // get persistant contact groups
750      let maybe_group_a = self.persistent.get_group_key (object_id_a);
751      let maybe_group_b = {
752        let maybe_group_b = self.persistent.get_group_key (object_id_b);
753        if maybe_group_a == maybe_group_b {
754          // if the groups are the same, then just use group a
755          None
756        } else {
757          maybe_group_b
758        }
759      };
760      // resolve collision
761      let collision_resolve = self.resolve (objects_static, objects_dynamic,
762        toi, object_id_a, object_id_b, contact, maybe_group_a,
763        maybe_group_b);
764      log::trace!(collision_resolve:?; "resolved collision");
765      self.pipeline.resolved_collisions.push (collision_resolve);
766      true
767    } else {
768      // no narrow toi contact to process, return false
769      debug_assert!(self.pipeline.mid_toi_pairs.is_empty());
770      false
771    }
772  }
773
774  #[expect(clippy::too_many_arguments)]
775  fn resolve (
776    &mut self,
777    objects_static  : &VecMap <object::Static>,
778    objects_dynamic : &mut VecMap <object::Dynamic>,
779    toi             : Normalized <f64>,
780    object_id_a     : InternalId,
781    object_id_b     : InternalId,
782    contact         : contact::Colliding,
783    group_key_a     : Option <contact::group::KeyType>,
784    group_key_b     : Option <contact::group::KeyType>
785  ) -> event::CollisionResolve {
786    // 1. lerp all involved dynamic objects to the TOI
787    // 2. constrain velocities
788    // 3. lerp to T==1.0
789    // 4. constrain positions
790    // 5. create or destroy persistent contacts from final proximity check
791    // 6. update AABBs in broad pipeline
792    use num::Zero;
793    let mut dynamic_ids = SortedSet::new();
794    if object_id_a.kind() == object::Kind::Dynamic {
795      dynamic_ids.push (object_id_a);
796    }
797    dynamic_ids.push (object_id_b);
798    let group_a = group_key_a
799      .map (|group_key| self.persistent.get_group (group_key).unwrap());
800    let group_b = group_key_b
801      .map (|group_key| self.persistent.get_group (group_key).unwrap());
802    group_a.map (|group| dynamic_ids.extend (group.dynamic_ids().into_vec()));
803    group_b.map (|group| dynamic_ids.extend (group.dynamic_ids().into_vec()));
804    // track whether a dynamic object was modified; if so it will need to be removed
805    // from intermediate results in the pipeline and checked for new collisions
806    let mut dynamic_modified = SortedSet::new();
807    // 1. lerp to TOI
808    let lerp_objects =
809      |objects_dynamic : &mut VecMap <object::Dynamic>, time_delta|
810      for id in dynamic_ids.iter() {
811        let index          = id.key().index();
812        let object         = &mut objects_dynamic[index];
813        let pseudovelocity = self.pseudo_velocities[index];
814        lerp_object (object, pseudovelocity, time_delta);
815      };
816    let (t_reverse, t_forward) = {
817      let toi_f64 : f64 = *toi;   // [0.0, 1.0]
818      (-1.0 + toi_f64, 1.0 - toi_f64)
819    };
820    lerp_objects (objects_dynamic, t_reverse);
821    let toi_aabbs = dynamic_ids.iter().map (|id|{
822      let index = id.key().index();
823      objects_dynamic[index].aabb_dilated()
824    }).collect::<Vec <_>>();
825    // 2. constrain velocities
826    let constrain_velocity = |
827      objects_dynamic  : &mut VecMap <object::Dynamic>,
828      pseudovelocities : &VecMap <Vector3 <f64>>,
829      contact          : Contact,
830      restitution      : Normalized <f64>,
831      object_id_a      : InternalId,
832      object_id_b      : InternalId
833    | {
834      let key_a    = object_id_a.key();
835      let key_b    = object_id_b.key();
836      let index_a  = key_a.index();
837      let index_b  = key_b.index();
838      let mut object_a = match object_id_a.kind() {
839        object::Kind::Static   => Either::Left  (objects_static[index_a].clone()),
840        object::Kind::Dynamic  => Either::Right (objects_dynamic[index_a].clone()),
841        object::Kind::Nodetect => unreachable!()
842      };
843      debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
844      let mut object_b     = objects_dynamic[index_b].clone();
845      let pseudovelocity_a = if object_id_a.kind() == object::Kind::Dynamic {
846        Some (pseudovelocities[index_a])
847      } else {
848        None
849      };
850      let pseudovelocity_b = pseudovelocities[index_b];
851      let maybe_impulses   = contact_constrain_velocity (
852        contact, restitution,
853        object_a.as_mut().map_left (|object_static| &*object_static),
854        &mut object_b, pseudovelocity_a, pseudovelocity_b
855      );
856      if maybe_impulses.is_some() {
857        match object_a {
858          Either::Right (object_a) => objects_dynamic[index_a] = object_a,
859          Either::Left  (_)        => {}
860        }
861        objects_dynamic[index_b] = object_b;
862      }
863      maybe_impulses
864    };
865    let mut impulse_normal_a  = Vector3::zero();
866    let mut impulse_normal_b  = Vector3::zero();
867    let mut impulse_tangent_a = Vector3::zero();
868    let mut impulse_tangent_b = Vector3::zero();
869    let mut iter = 0;
870    loop {
871      let mut satisfied = true;
872      if let Some (impulses) = constrain_velocity (
873        objects_dynamic, &self.pseudo_velocities, contact.contact.clone(),
874        contact.restitution, object_id_a, object_id_b
875      ) {
876        // if there are no groups, then the collision is isolated and satisfied
877        satisfied = group_a.is_none() && group_b.is_none();
878        impulse_normal_a  += impulses.impulse_normal_a;
879        impulse_normal_b  += impulses.impulse_normal_b;
880        impulse_tangent_a += impulses.impulse_tangent_a;
881        impulse_tangent_b += impulses.impulse_tangent_b;
882      }
883      let mut constrain_group = |group : &contact::Group|
884        for (object_pair, contact) in group.contacts.iter().cloned() {
885          let (object_id_a, object_id_b) = object_pair.into();
886          if constrain_velocity (
887            objects_dynamic, &self.pseudo_velocities, contact, Normalized::zero(),
888            object_id_a, object_id_b
889          ).is_some() {
890            if object_id_a.kind() == object::Kind::Dynamic {
891              dynamic_modified.push (object_id_a);
892            }
893            dynamic_modified.push (object_id_b);
894            satisfied = false;
895          }
896        };
897      if let Some (group) = group_a {
898        constrain_group (group);
899      }
900      if let Some (group) = group_b {
901        constrain_group (group);
902      }
903      iter += 1;
904      if satisfied {
905        log::debug!(iters=iter; "collision velocity constraint satisfied");
906        break
907      }
908    }
909    // 3. lerp to T==1.0
910    lerp_objects (objects_dynamic, t_forward);
911    // 4. constrain positions
912    let constrain_position = |
913      objects_dynamic  : &mut VecMap <object::Dynamic>,
914      pseudovelocities : &mut VecMap <Vector3 <f64>>,
915      object_id_a      : InternalId,
916      object_id_b      : InternalId
917    | {
918      let key_a    = object_id_a.key();
919      let key_b    = object_id_b.key();
920      let index_a  = key_a.index();
921      let index_b  = key_b.index();
922      let mut object_a = match object_id_a.kind() {
923        object::Kind::Static   => Either::Left  (objects_static[index_a].clone()),
924        object::Kind::Dynamic  => Either::Right (objects_dynamic[index_a].clone()),
925        object::Kind::Nodetect => unreachable!()
926      };
927      debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
928      let mut object_b = objects_dynamic[index_b].clone();
929      let mut pseudovelocity_a = if object_id_a.kind() == object::Kind::Dynamic {
930        Some (pseudovelocities[index_a])
931      } else {
932        None
933      };
934      let mut pseudovelocity_b = pseudovelocities[index_b];
935      let (maybe_impulses, proximity) = contact_constrain_position (
936        t_reverse,
937        object_a.as_mut().map_left (|object_static| &*object_static),
938        &mut object_b, pseudovelocity_a.as_mut(), &mut pseudovelocity_b
939      );
940      if maybe_impulses.is_some() {
941        match object_a {
942          Either::Right (object_a) => {
943            objects_dynamic[index_a]  = object_a;
944            pseudovelocities[index_a] = pseudovelocity_a.unwrap();
945          }
946          Either::Left  (_) => {}
947        }
948        objects_dynamic[index_b]  = object_b;
949        pseudovelocities[index_b] = pseudovelocity_b;
950      }
951      (maybe_impulses, proximity)
952    };
953    let mut final_proximities = vec![
954      Proximity {
955        distance:  0.0,
956        half_axis: Vector3::zero(),
957        midpoint:  Point::origin(),
958        normal:    Unit3::axis_z()
959      };
960      1 + group_a.map_or (0, |group| group.contacts.len()) +
961      group_b.map_or (0, |group| group.contacts.len())
962    ];
963    let mut pseudo_impulse_a = Vector3::zero();
964    let mut pseudo_impulse_b = Vector3::zero();
965    let mut iter = 0;
966    loop {
967      let mut satisfied     = true;
968      let mut contact_index = 0;
969      let (maybe_impulses, proximity) = constrain_position (
970        objects_dynamic, &mut self.pseudo_velocities, object_id_a, object_id_b);
971      final_proximities[contact_index] = proximity;
972      contact_index += 1;
973      if let Some (impulses) = maybe_impulses {
974        // if there are no groups, then the collision is isolated and satisfied
975        satisfied = group_a.is_none() && group_b.is_none();
976        pseudo_impulse_a += impulses.pseudo_impulse_a;
977        pseudo_impulse_b += impulses.pseudo_impulse_b;
978      }
979      let mut constrain_group = |group : &contact::Group|
980        for (object_pair, _) in group.contacts.iter() {
981          let (object_id_a, object_id_b)  = (*object_pair).into();
982          let (maybe_impulses, proximity) = constrain_position (
983            objects_dynamic, &mut self.pseudo_velocities,
984            object_id_a, object_id_b
985          );
986          final_proximities[contact_index] = proximity;
987          contact_index += 1;
988          if maybe_impulses.is_some() {
989            if object_id_a.kind() == object::Kind::Dynamic {
990              dynamic_modified.push (object_id_a);
991            }
992            dynamic_modified.push (object_id_b);
993            satisfied = false;
994          }
995        };
996      if let Some (group) = group_a {
997        constrain_group (group);
998      }
999      if let Some (group) = group_b {
1000        constrain_group (group);
1001      }
1002      iter += 1;
1003      if satisfied {
1004        log::debug!(iters=iter; "collision position constraint satisfied");
1005        break
1006      }
1007    }
1008    // 5. create or destroy persistent contacts from final proximity check;
1009    // also add resolved pairs to the broad phase here since we are iterating
1010    // over contact groups one last time
1011    let object_pair = (object_id_a, object_id_b).into();
1012    if !impulse_normal_b.is_zero() || !impulse_tangent_b.is_zero() ||
1013      !pseudo_impulse_b.is_zero()
1014    {
1015      if object_id_a.kind() == object::Kind::Dynamic {
1016        debug_assert!(!impulse_normal_a.is_zero() ||
1017          !impulse_tangent_a.is_zero() || !pseudo_impulse_a.is_zero());
1018        dynamic_modified.push (object_id_a);
1019      }
1020      dynamic_modified.push (object_id_b);
1021    }
1022    self.broad.add_resolved (object_pair);
1023    let mut final_proximities     = final_proximities.into_iter();
1024    let collision_final_proximity = final_proximities.next().unwrap();
1025    // wait until after updating groups to create any new contacts in case the
1026    // groups are merged which would invalidate one of the old group keys
1027    let mut update_group = |
1028      final_proximities : &mut std::vec::IntoIter <Proximity>,
1029      group_key         : contact::group::KeyType
1030    | {
1031      let mut contact_groups = self.persistent.contact_groups.take().unwrap();
1032      let group = &mut contact_groups[group_key as usize];
1033      let mut remove_contacts = vec![];
1034      for (i, proximity) in
1035        final_proximities.take (group.contacts.len()).enumerate()
1036      {
1037        let contact = &mut group.contacts[i];
1038        self.broad.add_resolved (contact.0);
1039        if let Ok (update_contact) = Contact::try_from (proximity) {
1040          contact.1 = update_contact;
1041        } else {
1042          remove_contacts.push (i as u32);
1043        }
1044      }
1045      if !remove_contacts.is_empty() {
1046        self.persistent.remove_contacts (group, remove_contacts.as_slice());
1047        if group.contacts.is_empty() {
1048          let _ = contact_groups.take (group_key as usize).unwrap();
1049        } else {
1050          let partitions = group.partition();
1051          if !partitions.is_empty() {
1052            let _ = contact_groups.take (group_key as usize).unwrap();
1053            for new_group in partitions {
1054              let dynamic_ids = new_group.dynamic_ids();
1055              let group_key   = contact_groups.put (new_group)
1056                as contact::group::KeyType;
1057              for dynamic_id in dynamic_ids.into_vec() {
1058                self.persistent.change_dynamic_group_key (dynamic_id, group_key);
1059              }
1060            }
1061          }
1062        }
1063      }
1064      self.persistent.contact_groups = Some (contact_groups);
1065    };
1066    if let Some (group_key) = group_key_a {
1067      update_group (&mut final_proximities, group_key);
1068    }
1069    if let Some (group_key) = group_key_b {
1070      update_group (&mut final_proximities, group_key);
1071    }
1072    if let Ok (contact) = collision_final_proximity.try_into() {
1073      log::trace!(object_pair:?, contact:?; "creating contact @ T==1.0");
1074      self.persistent.add_contact (object_pair, contact);
1075    }
1076    // 6. update AABBs in broad pipeline;
1077    // also remove resolved dynamic objects from intermediate pipeline results
1078    for id in dynamic_modified.iter() {
1079      let i = dynamic_ids.binary_search_by_key (&id, |x| x).unwrap();
1080      let key    = id.key();
1081      let index  = key.index();
1082      let object = &objects_dynamic[index];
1083      let aabb_discrete   = object.aabb_dilated();
1084      let aabb_continuous = Aabb3::union (toi_aabbs[i], aabb_discrete);
1085      self.broad.update_aabb_dynamic_discrete (aabb_discrete, key);
1086      self.broad.update_aabb_dynamic_continuous (aabb_continuous, key);
1087    }
1088    self.pipeline.remove_resolved_dynamic (&dynamic_modified);
1089    // TODO: update the modified set in-place ?
1090    self.broad.set_modified (dynamic_modified);
1091    // result
1092    let object_id_a = object_id_a.into();
1093    let object_id_b = object_id_b.into();
1094    event::CollisionResolve {
1095      toi, contact,
1096      object_id_a,       object_id_b,
1097      impulse_normal_a,  impulse_normal_b,
1098      impulse_tangent_a, impulse_tangent_b,
1099      pseudo_impulse_a,  pseudo_impulse_b
1100    }
1101  }
1102
1103  /// Detects AABB overlap pairs of static/dynamic and dynamic/dynamic objects.
1104  ///
1105  /// Excludes Nocollide pairs that were already detected but does not filter
1106  /// object pairs that are already in persistent contacts.
1107  #[inline]
1108  fn broad_overlap_pairs_continuous (&mut self) {
1109    self.broad.overlap_pairs_continuous (
1110      &mut self.pipeline.broad_overlap_pairs, self.pipeline.detect_resolve_iter);
1111    // exclude nocollide pairs that were already detected
1112    self.pipeline.broad_overlap_pairs.retain (
1113      |pair|{
1114        let (id_a, id_b) = (*pair).into();
1115        let id_a = object::Id::from (id_a);
1116        let id_b = object::Id::from (id_b);
1117        for event::Overlap { object_id_a, object_id_b } in
1118          self.pipeline.overlaps.iter()
1119        {
1120          if object_id_a == &id_a && object_id_b == &id_b {
1121            return false
1122          }
1123        }
1124        true
1125      }
1126    );
1127    log::trace!(overlap_pairs:?=self.pipeline.broad_overlap_pairs;
1128      "broad overlap pairs");
1129  }
1130
1131  /// Collects swept AABB TOIs from broad overlap pairs.
1132  ///
1133  /// Filters pairs that are already in persistent contact.
1134  ///
1135  /// Note that the algorithm will aim for a distance of `0.5 * CONTACT_DISTANCE` at
1136  /// each TOI.
1137  fn mid_toi_pairs (&mut self, objects_dynamic : &VecMap <object::Dynamic>) {
1138    use sorted_vec::FindOrInsert;
1139    let last_toi = self.pipeline.resolved_collisions.last()
1140      .map_or (0.0, |collision_resolve| *collision_resolve.toi);
1141    'outer: for object_pair in self.pipeline.broad_overlap_pairs.drain (..) {
1142      let (object_id_a, object_id_b) = object_pair.into();
1143      debug_assert_eq!(object_id_b.kind(), object::Kind::Dynamic);
1144      match object_id_a.kind() {
1145        //
1146        //  static v. dynamic
1147        //
1148        object::Kind::Static => {
1149          let id_static     = object::Id::from (object_id_a);
1150          let id_dynamic    = object::Id::from (object_id_b);
1151          let key_static    = id_static.key;
1152          let key_dynamic   = id_dynamic.key;
1153          let _index_static = key_static.index();
1154          let index_dynamic = key_dynamic.index();
1155          if let (Some (static_contact_count), Some (dynamic_group_key)) = (
1156            self.persistent.get_contact_count (object_id_a),
1157            self.persistent.get_group_key (object_id_b)
1158          ) {
1159            debug_assert!(static_contact_count > 0);
1160            let group = self.persistent.get_group (dynamic_group_key).unwrap();
1161            for (pair, _) in group.contacts.iter() {
1162              if *pair == object_pair {
1163                continue 'outer
1164              }
1165            }
1166          }
1167          // should be safe to unwrap
1168          let dynamic_object         = &objects_dynamic[index_dynamic];
1169          let dynamic_velocity       = dynamic_object.derivatives.velocity;
1170          let dynamic_pseudovelocity = self.pseudo_velocities[index_dynamic];
1171          let dynamic_velocity_effective = dynamic_velocity + dynamic_pseudovelocity;
1172          let static_aabb            = self.broad.get_aabb_static (key_static);
1173          let dynamic_aabb_current   = self.broad
1174            .get_aabb_dynamic_discrete (key_dynamic);
1175          let _dynamic_aabb_swept    = self.broad
1176            .get_aabb_dynamic_continuous (key_dynamic);
1177          let dynamic_aabb_previous  = Aabb3::with_minmax_unchecked (
1178            dynamic_aabb_current.min() - dynamic_velocity_effective,
1179            dynamic_aabb_current.max() - dynamic_velocity_effective);
1180          // NB: div by zero will result in `inf` values
1181          let dynamic_velocity_effective_reciprocal = dynamic_velocity_effective.recip();
1182          // these values adjust start/end times to avoid getting too close
1183          let t_margin_x =
1184            (0.5 * CONTACT_DISTANCE * dynamic_velocity_effective_reciprocal.x).abs();
1185          let t_margin_y =
1186            (0.5 * CONTACT_DISTANCE * dynamic_velocity_effective_reciprocal.y).abs();
1187          let t_margin_z =
1188            (0.5 * CONTACT_DISTANCE * dynamic_velocity_effective_reciprocal.z).abs();
1189
1190          let (interval_start_x, interval_end_x) = if
1191            dynamic_velocity_effective.x == 0.0
1192          {
1193            debug_assert!(
1194              dynamic_aabb_current.max().0.x > static_aabb.min().0.x &&
1195              static_aabb.max().0.x > dynamic_aabb_current.min().0.x);
1196            debug_assert!(
1197              dynamic_aabb_previous.max().0.x > static_aabb.min().0.x &&
1198              static_aabb.max().0.x > dynamic_aabb_previous.min().0.x);
1199            (f64::NEG_INFINITY, f64::INFINITY)
1200          } else if dynamic_velocity_effective.x > 0.0 {
1201            ( //(static_aabb.min().0.x - dynamic_aabb_previous.max().0.x) *
1202              //  dynamic_velocity_effective_reciprocal.x - t_margin_x,
1203              (static_aabb.min().0.x - dynamic_aabb_previous.max().0.x)
1204                .mul_add (dynamic_velocity_effective_reciprocal.x, -t_margin_x),
1205              //(static_aabb.max().0.x - dynamic_aabb_previous.min().0.x) *
1206              //  dynamic_velocity_effective_reciprocal.x + t_margin_x
1207              (static_aabb.max().0.x - dynamic_aabb_previous.min().0.x)
1208                .mul_add (dynamic_velocity_effective_reciprocal.x, t_margin_x)
1209            )
1210          } else {
1211            debug_assert!(dynamic_velocity_effective.x < 0.0);
1212            ( //(static_aabb.max().0.x - dynamic_aabb_previous.min().0.x) *
1213              //  dynamic_velocity_effective_reciprocal.x - t_margin_x,
1214              (static_aabb.max().0.x - dynamic_aabb_previous.min().0.x)
1215                .mul_add (dynamic_velocity_effective_reciprocal.x, -t_margin_x),
1216              //(static_aabb.min().0.x - dynamic_aabb_previous.max().0.x) *
1217              //  dynamic_velocity_effective_reciprocal.x + t_margin_x
1218              (static_aabb.min().0.x - dynamic_aabb_previous.max().0.x)
1219                .mul_add (dynamic_velocity_effective_reciprocal.x, t_margin_x)
1220            )
1221          };
1222
1223          let (interval_start_y, interval_end_y) = if
1224            dynamic_velocity_effective.y == 0.0
1225          {
1226            debug_assert!(
1227              dynamic_aabb_current.max().0.y > static_aabb.min().0.y &&
1228              static_aabb.max().0.y > dynamic_aabb_current.min().0.y);
1229            debug_assert!(
1230              dynamic_aabb_previous.max().0.y > static_aabb.min().0.y &&
1231              static_aabb.max().0.y > dynamic_aabb_previous.min().0.y);
1232            (f64::NEG_INFINITY, f64::INFINITY)
1233          } else if dynamic_velocity_effective.y > 0.0 {
1234            ( //dynamic_velocity_effective_reciprocal.y *
1235              //  (static_aabb.min().0.y - dynamic_aabb_previous.max().0.y) - t_margin_y,
1236              dynamic_velocity_effective_reciprocal.y.mul_add (
1237                static_aabb.min().0.y - dynamic_aabb_previous.max().0.y, -t_margin_y),
1238              //dynamic_velocity_effective_reciprocal.y *
1239              //  (static_aabb.max().0.y - dynamic_aabb_previous.min().0.y) + t_margin_y
1240              dynamic_velocity_effective_reciprocal.y.mul_add (
1241                static_aabb.max().0.y - dynamic_aabb_previous.min().0.y, t_margin_y)
1242            )
1243          } else {
1244            debug_assert!(dynamic_velocity_effective.y < 0.0);
1245            ( //dynamic_velocity_effective_reciprocal.y *
1246              //  (static_aabb.max().0.y - dynamic_aabb_previous.min().0.y) - t_margin_y,
1247              dynamic_velocity_effective_reciprocal.y.mul_add (
1248                static_aabb.max().0.y - dynamic_aabb_previous.min().0.y, -t_margin_y),
1249              //dynamic_velocity_effective_reciprocal.y *
1250              //  (static_aabb.min().0.y - dynamic_aabb_previous.max().0.y) + t_margin_y
1251              dynamic_velocity_effective_reciprocal.y.mul_add (
1252                static_aabb.min().0.y - dynamic_aabb_previous.max().0.y, t_margin_y)
1253            )
1254          };
1255
1256          let (interval_start_z, interval_end_z) = if
1257            dynamic_velocity_effective.z == 0.0
1258          {
1259            debug_assert!(
1260              dynamic_aabb_current.max().0.z > static_aabb.min().0.z &&
1261              static_aabb.max().0.z > dynamic_aabb_current.min().0.z);
1262            debug_assert!(
1263              dynamic_aabb_previous.max().0.z > static_aabb.min().0.z &&
1264              static_aabb.max().0.z > dynamic_aabb_previous.min().0.z);
1265            (f64::NEG_INFINITY, f64::INFINITY)
1266          } else if dynamic_velocity_effective.z > 0.0 {
1267            ( //dynamic_velocity_effective_reciprocal.z *
1268              //  (static_aabb.min().0.z - dynamic_aabb_previous.max().0.z) - t_margin_z,
1269              dynamic_velocity_effective_reciprocal.z.mul_add (
1270                static_aabb.min().0.z - dynamic_aabb_previous.max().0.z, -t_margin_z),
1271              //dynamic_velocity_effective_reciprocal.z *
1272              //  (static_aabb.max().0.z - dynamic_aabb_previous.min().0.z) + t_margin_z
1273              dynamic_velocity_effective_reciprocal.z.mul_add (
1274                static_aabb.max().0.z - dynamic_aabb_previous.min().0.z, t_margin_z)
1275            )
1276          } else {
1277            debug_assert!(dynamic_velocity_effective.z < 0.0);
1278            ( //dynamic_velocity_effective_reciprocal.z *
1279              //  (static_aabb.max().0.z - dynamic_aabb_previous.min().0.z) - t_margin_z,
1280              dynamic_velocity_effective_reciprocal.z.mul_add (
1281                static_aabb.max().0.z - dynamic_aabb_previous.min().0.z, -t_margin_z),
1282              //dynamic_velocity_effective_reciprocal.z *
1283              //  (static_aabb.min().0.z - dynamic_aabb_previous.max().0.z) + t_margin_z
1284              dynamic_velocity_effective_reciprocal.z.mul_add (
1285                static_aabb.min().0.z - dynamic_aabb_previous.max().0.z, t_margin_z)
1286            )
1287          };
1288
1289          let interval = {
1290            let interval_xy =
1291              if interval_start_x < interval_end_y && interval_start_y < interval_end_x {
1292                Some ((
1293                  f64::max (interval_start_x, interval_start_y),
1294                  f64::min (interval_end_x, interval_end_y)
1295                ))
1296              } else {
1297                None
1298              };
1299            if let Some ((interval_start_xy, interval_end_xy)) = interval_xy {
1300              if interval_start_xy < interval_end_z &&
1301                 interval_start_z  < interval_end_xy
1302              {
1303                Some ((
1304                  f64::max (interval_start_xy, interval_start_z),
1305                  f64::min (interval_end_xy, interval_end_z)
1306                ))
1307              } else {
1308                None
1309              }
1310            } else {
1311              None
1312            }
1313          };
1314
1315          if let Some ((interval_start, interval_end)) = interval &&
1316            interval_start < 1.0 && 0.0 < interval_end
1317          {
1318            let mid_toi_pair = (
1319              Normalized::clamp (f64::max (interval_start, last_toi)),
1320              Normalized::clamp (interval_end),
1321              object_pair
1322            );
1323            match self.pipeline.mid_toi_pairs.find_or_insert (Reverse (mid_toi_pair)) {
1324              FindOrInsert::Inserted (_) => {}
1325              FindOrInsert::Found    (_) => unreachable!()
1326            }
1327          }
1328        }
1329        //
1330        //  dynamic v. dynamic
1331        //
1332        object::Kind::Dynamic => {
1333          let key_a   = object_id_a.key();
1334          let key_b   = object_id_b.key();
1335          let index_a = key_a.index();
1336          let index_b = key_b.index();
1337          if let (Some (group_key_a), Some (group_key_b)) = (
1338            self.persistent.get_group_key (object_id_a),
1339            self.persistent.get_group_key (object_id_b)
1340          ) && group_key_a == group_key_b {
1341            let group = self.persistent.get_group (group_key_a).unwrap();
1342            for (pair, _) in group.contacts.iter() {
1343              if *pair == object_pair {
1344                continue 'outer
1345              }
1346            }
1347          }
1348          // should be safe to unwrap: objects should not be destroyed during
1349          // collision detection
1350          let object_a          = &objects_dynamic[index_a];
1351          let object_b          = &objects_dynamic[index_b];
1352          let velocity_a        = object_a.derivatives.velocity;
1353          let velocity_b        = object_b.derivatives.velocity;
1354          let pseudovelocity_a  = self.pseudo_velocities[key_a.index()];
1355          let pseudovelocity_b  = self.pseudo_velocities[key_b.index()];
1356          let velocity_effective_a = velocity_a + pseudovelocity_a;
1357          let velocity_effective_b = velocity_b + pseudovelocity_b;
1358          let velocity_effective_relative =
1359            velocity_effective_a - velocity_effective_b;
1360          let velocity_effective_relative_reciprocal =
1361            velocity_effective_relative.recip();
1362          let aabb_current_a    = self.broad.get_aabb_dynamic_discrete   (key_a);
1363          let aabb_current_b    = self.broad.get_aabb_dynamic_discrete   (key_b);
1364          let _aabb_swept_a     = self.broad.get_aabb_dynamic_continuous (key_a);
1365          let _aabb_swept_b     = self.broad.get_aabb_dynamic_continuous (key_b);
1366          let aabb_previous_a   = Aabb3::with_minmax_unchecked (
1367            aabb_current_a.min() - velocity_effective_a,
1368            aabb_current_a.max() - velocity_effective_a);
1369          let aabb_previous_b   = Aabb3::with_minmax_unchecked (
1370            aabb_current_b.min() - velocity_effective_b,
1371            aabb_current_b.max() - velocity_effective_b);
1372
1373          // compute overlap time start/end interval for the given axis
1374          let overlap_interval_axis = |axis : Axis3| {
1375            let axis_index = axis as usize;
1376            // div by zero will result in `inf` values; t_margin adjusts
1377            // start/end times to avoid getting too close
1378            let t_margin = (0.5 * CONTACT_DISTANCE *
1379              velocity_effective_relative_reciprocal[axis_index]).abs();
1380            let velocity_relative = velocity_effective_relative[axis_index];
1381            let velocity_relative_reciprocal =
1382              velocity_effective_relative_reciprocal[axis_index];
1383            let prev_a_min = aabb_previous_a.min().0[axis_index];
1384            let prev_a_max = aabb_previous_a.max().0[axis_index];
1385            let prev_b_min = aabb_previous_b.min().0[axis_index];
1386            let prev_b_max = aabb_previous_b.max().0[axis_index];
1387            if velocity_relative == 0.0 {
1388              // NOTE: even though the relative velocity is zero, the previous AABBs may
1389              // not be overlapping due to rounding errors; we will still consider the
1390              // begin/end time to be infinite
1391              geometry::Interval::with_minmax_unchecked (
1392                f64::NEG_INFINITY, f64::INFINITY)
1393            } else if velocity_relative > 0.0 {
1394              geometry::Interval::with_minmax_unchecked (
1395                //(prev_b_min - prev_a_max) * velocity_relative_reciprocal - t_margin,
1396                (prev_b_min - prev_a_max)
1397                  .mul_add (velocity_relative_reciprocal, -t_margin),
1398                //(prev_b_max - prev_a_min) * velocity_relative_reciprocal + t_margin
1399                (prev_b_max - prev_a_min)
1400                  .mul_add (velocity_relative_reciprocal, t_margin)
1401              )
1402            } else {
1403              debug_assert!(velocity_relative < 0.0);
1404              geometry::Interval::with_minmax_unchecked (
1405                //(prev_b_max - prev_a_min) * velocity_relative_reciprocal - t_margin,
1406                (prev_b_max - prev_a_min)
1407                  .mul_add (velocity_relative_reciprocal, -t_margin),
1408                //(prev_b_min - prev_a_max) * velocity_relative_reciprocal + t_margin
1409                (prev_b_min - prev_a_max)
1410                  .mul_add (velocity_relative_reciprocal, t_margin)
1411              )
1412            }
1413          };
1414          let overlap_interval_x = overlap_interval_axis (Axis3::X);
1415          let overlap_interval_y = overlap_interval_axis (Axis3::Y);
1416          let overlap_interval_z = overlap_interval_axis (Axis3::Z);
1417
1418          if let Some (interval) = overlap_interval_x.intersection (overlap_interval_y)
1419            .and_then (|overlap_interval_xy|
1420              overlap_interval_xy.intersection (overlap_interval_z))
1421            && interval.min() < 1.0 && 0.0 < interval.max()
1422          {
1423            let mid_toi_pair = (
1424              Normalized::clamp (f64::max (interval.min(), last_toi)),
1425              Normalized::clamp (interval.max()),
1426              object_pair
1427            );
1428            match self.pipeline.mid_toi_pairs.find_or_insert (Reverse (mid_toi_pair)) {
1429              FindOrInsert::Inserted (_) => {}
1430              FindOrInsert::Found    (_) => unreachable!()
1431            }
1432          }
1433        }
1434        object::Kind::Nodetect => unreachable!()
1435      }
1436    }
1437
1438    log::trace!(mid_toi_pairs:?=self.pipeline.mid_toi_pairs; "mid TOI pairs");
1439
1440    self.pipeline.broad_overlap_pairs.clear();
1441  }
1442
1443  /// Returns narrow TOI pairs.
1444  ///
1445  /// Note that the algorithm will aim for a distance of `0.5 * CONTACT_DISTANCE` at
1446  /// each TOI.
1447  fn narrow_toi_contacts (&mut self,
1448    objects_static  : &VecMap <object::Static>,
1449    objects_dynamic : &VecMap <object::Dynamic>
1450  ) {
1451    use sorted_vec::FindOrInsert;
1452
1453    let narrow_toi_contacts = &mut self.pipeline.narrow_toi_contacts;
1454    while let Some (mid_toi_pair) = self.pipeline.mid_toi_pairs.last().copied() {
1455      let earliest_toi_contact = if !narrow_toi_contacts.is_empty() {
1456        narrow_toi_contacts[0].0.0
1457      } else {
1458        Normalized::clamp (1.0)
1459      };
1460
1461      if mid_toi_pair.0.0 < earliest_toi_contact {
1462        let (mid_toi_start, _mid_toi_end, object_pair) = mid_toi_pair.0;
1463        let (object_id_a, object_id_b) = object_pair.into();
1464        let kind_a  = object_id_a.kind();
1465        let kind_b  = object_id_b.kind();
1466        match (kind_a, kind_b) {
1467          (object::Kind::Static, object::Kind::Dynamic) => {
1468            let key_static    = object_id_a.key();
1469            let key_dynamic   = object_id_b.key();
1470            let index_static  = key_static.index();
1471            let index_dynamic = key_dynamic.index();
1472            // safe to unwrap
1473            let mut dynamic_object = objects_dynamic[index_dynamic].clone();
1474            let dynamic_velocity   = dynamic_object.derivatives.velocity;
1475            let dynamic_pseudovelocity = self.pseudo_velocities[index_dynamic];
1476            let dynamic_velocity_effective = dynamic_velocity - dynamic_pseudovelocity;
1477            let static_object      = objects_static[index_static].clone();
1478            let mut narrow_toi     = *mid_toi_start;
1479            // rewind object to the narrow_toi: the object is currently at
1480            // t==1.0, so we are lerping a negative value
1481            lerp_object (&mut dynamic_object, dynamic_pseudovelocity, -1.0 + narrow_toi);
1482            let mut t_remaining = 1.0 - narrow_toi;
1483            #[expect(clippy::used_underscore_binding)]
1484            let mut _iter       = 0;
1485            loop {
1486              #[cfg(debug_assertions)]
1487              if self.narrow_toi_max_iter_count < _iter {
1488                self.narrow_toi_max_iter_count = _iter;
1489              }
1490              let proximity = Proximity::query (&static_object, &dynamic_object);
1491              if cfg!(debug_assertions) &&
1492                dynamic_object.collidable && static_object.collidable
1493              {
1494                debug_assert!(proximity.distance >= 0.0,
1495                  "objects should not be overlapping, distance: {}", proximity.distance);
1496              }
1497              let dynamic_velocity_effective_normal =
1498                dynamic_velocity_effective.dot (-*proximity.normal);
1499              if dynamic_velocity_effective_normal > 0.0 &&
1500                dynamic_object.collidable && static_object.collidable
1501              {
1502                // no collision: separating velocity zero or positive
1503                log::trace!(
1504                  t=narrow_toi, velocity=dynamic_velocity_effective_normal;
1505                  "collision rejected, separating velocity");
1506                break
1507              } else if proximity.distance < CONTACT_DISTANCE {
1508                debug_assert!(dynamic_velocity_effective_normal <= 0.0 ||
1509                  !dynamic_object.collidable || !static_object.collidable);
1510                // collision: toi contact
1511                log::trace!(t=narrow_toi, proximity:?; "collision detected");
1512                let contact     = Contact { constraint: proximity.into() };
1513                let restitution = dynamic_object.material.restitution *
1514                  static_object.material.restitution;
1515                let collision   = contact::Colliding { contact, restitution };
1516                let narrow_toi_contact = Reverse ((
1517                  Normalized::noisy (narrow_toi), object_pair, collision
1518                ));
1519                match narrow_toi_contacts.find_or_insert (narrow_toi_contact) {
1520                  FindOrInsert::Inserted (_) => {}
1521                  FindOrInsert::Found    (_) => unreachable!()
1522                }
1523                break
1524              } else if dynamic_velocity_effective_normal == 0.0 {
1525                log::trace!(
1526                  t=narrow_toi, velocity=dynamic_velocity_effective_normal;
1527                  "collision rejected, zero relative velocity and objects not \
1528                  in contact");
1529                break
1530              }
1531              let dynamic_velocity_effective_normal_reciprocal =
1532                1.0 / dynamic_velocity_effective_normal;
1533              let toi_axis =
1534                //narrow_toi +
1535                //(proximity.distance - 0.5 * CONTACT_DISTANCE) *
1536                //(-dynamic_velocity_effective_normal_reciprocal);
1537                0.5f64.mul_add (-CONTACT_DISTANCE, proximity.distance)
1538                  .mul_add (-dynamic_velocity_effective_normal_reciprocal, narrow_toi);
1539              debug_assert!(toi_axis > narrow_toi ||
1540                !dynamic_object.collidable || !static_object.collidable);
1541              if toi_axis > 1.0 || toi_axis < 0.0 {
1542                // no collision this step
1543                log::trace!(t=narrow_toi, proximity:?;
1544                  "collision rejected, final proximity");
1545                break
1546              }
1547              let t_advance = toi_axis - narrow_toi;
1548              lerp_object (&mut dynamic_object, dynamic_pseudovelocity, t_advance);
1549              narrow_toi    = toi_axis;
1550              t_remaining   -= t_advance;
1551              debug_assert!(t_remaining > 0.0);
1552              _iter += 1;
1553            }
1554          }
1555
1556          (object::Kind::Dynamic, object::Kind::Dynamic) => {
1557            let key_a   = object_id_a.key();
1558            let key_b   = object_id_b.key();
1559            let index_a = key_a.index();
1560            let index_b = key_b.index();
1561             // safe to unwrap: objects should not be destroyed during collision
1562             // detection
1563            let mut object_a     = objects_dynamic[index_a].clone();
1564            let mut object_b     = objects_dynamic[index_b].clone();
1565            let pseudovelocity_a = self.pseudo_velocities[index_a];
1566            let pseudovelocity_b = self.pseudo_velocities[index_b];
1567            let velocity_a       = object_a.derivatives.velocity;
1568            let velocity_b       = object_b.derivatives.velocity;
1569            let velocity_effective_a = velocity_a + pseudovelocity_a;
1570            let velocity_effective_b = velocity_b + pseudovelocity_b;
1571            let velocity_effective_relative =
1572              velocity_effective_a - velocity_effective_b;
1573            let mut narrow_toi   = *mid_toi_start;
1574            let mut t_remaining  = 1.0 - narrow_toi;
1575            lerp_object (&mut object_a, pseudovelocity_a, -1.0 + narrow_toi);
1576            lerp_object (&mut object_b, pseudovelocity_b, -1.0 + narrow_toi);
1577            loop {
1578              debug_assert!(narrow_toi <= 1.0);
1579              debug_assert!(t_remaining >= 0.0);
1580              let proximity = Proximity::query (&object_a, &object_b);
1581              if cfg!(debug_assertions) && object_a.collidable && object_b.collidable {
1582                debug_assert!(proximity.distance >= 0.0);
1583              }
1584              let velocity_effective_relative_normal =
1585                velocity_effective_relative.dot (*proximity.normal);
1586              if velocity_effective_relative_normal > 0.0 &&
1587                object_a.collidable && object_b.collidable
1588              {
1589                // no collision: separating velocity zero or positive
1590                log::trace!(
1591                  t=narrow_toi, velocity=velocity_effective_relative_normal;
1592                  "collision rejected, separating velocity");
1593                break
1594              } else if proximity.distance < CONTACT_DISTANCE {
1595                debug_assert!(velocity_effective_relative_normal <= 0.0 ||
1596                  !object_a.collidable || !object_b.collidable);
1597                // collision: toi contact
1598                log::trace!(t=narrow_toi, proximity:?; "collision detected");
1599                let contact     = Contact { constraint: proximity.into() };
1600                let restitution =
1601                  object_a.material.restitution * object_b.material.restitution;
1602                let collision   = contact::Colliding { contact, restitution };
1603                let narrow_toi_contact = Reverse ((
1604                  Normalized::noisy (narrow_toi), object_pair, collision
1605                ));
1606                match narrow_toi_contacts.find_or_insert (narrow_toi_contact) {
1607                  FindOrInsert::Inserted (_) => {}
1608                  FindOrInsert::Found    (_) => unreachable!()
1609                }
1610                break
1611              } else if velocity_effective_relative_normal == 0.0 {
1612                log::trace!(
1613                  t=narrow_toi, velocity=velocity_effective_relative_normal;
1614                  "collision rejected, zero relative velocity and objects not in \
1615                  contact");
1616                break
1617              }
1618              let velocity_effective_relative_normal_reciprocal =
1619                1.0 / velocity_effective_relative_normal;
1620              let toi_axis =
1621                //narrow_toi +
1622                //(proximity.distance - 0.5 * CONTACT_DISTANCE) *
1623                //(-velocity_effective_relative_normal_reciprocal);
1624                0.5f64.mul_add (-CONTACT_DISTANCE, proximity.distance)
1625                  .mul_add (-velocity_effective_relative_normal_reciprocal, narrow_toi);
1626              debug_assert!(toi_axis > narrow_toi ||
1627                !object_a.collidable || !object_b.collidable);
1628              if toi_axis > 1.0 || toi_axis < 0.0 {
1629                // no collision this step
1630                log::trace!(t=narrow_toi, proximity:?;
1631                  "collision rejected, final proximity");
1632                break
1633              }
1634              let t_advance = toi_axis - narrow_toi;
1635              lerp_object (&mut object_a, pseudovelocity_a, t_advance);
1636              lerp_object (&mut object_b, pseudovelocity_b, t_advance);
1637              narrow_toi    = toi_axis;
1638              t_remaining   -= t_advance;
1639            }
1640          }
1641          _ => unreachable!()
1642        }
1643        self.pipeline.mid_toi_pairs.pop();
1644      } else { break }
1645    }
1646    #[cfg(debug_assertions)]
1647    log::debug!(max_iter_count=self.narrow_toi_max_iter_count;
1648      "narrow TOI max iters");
1649    log::trace!(contacts:?=narrow_toi_contacts; "narrow TOI contacts");
1650  }
1651
1652  #[inline]
1653  fn add_object_static (&mut self,
1654    object     : &object::Static,
1655    object_key : object::Key,
1656  ) {
1657    self.broad.add_object_static (object.aabb_dilated(), object_key);
1658  }
1659
1660  #[inline]
1661  fn add_object_dynamic (&mut self,
1662    object     : &object::Dynamic,
1663    object_key : object::Key
1664  ) {
1665    let aabb_discrete   = object.aabb_dilated();
1666    let aabb_continuous = Aabb3::with_minmax_unchecked (
1667      aabb_discrete.min() - object.derivatives.velocity,
1668      aabb_discrete.max() - object.derivatives.velocity);
1669    // NB: this swept AABB should not be seen by the next continuous collision
1670    // detection/resolution loop since `begin_step()` will re-calculate for the
1671    // next frame
1672    self.broad.add_object_dynamic (aabb_discrete, aabb_continuous, object_key);
1673    assert!(self.pseudo_velocities
1674      .insert (object_key.index(), Vector3::zero()).is_none());
1675  }
1676
1677}
1678
1679impl Pipeline {
1680  #[inline]
1681  fn is_empty (&self) -> bool {
1682    self.broad_overlap_pairs.is_empty() &&
1683    self.mid_toi_pairs.is_empty() &&
1684    self.narrow_toi_contacts.is_empty() &&
1685    self.resolved_collisions.is_empty() &&
1686    self.overlaps.is_empty()
1687  }
1688
1689  /// After a collision is resolved, all modified (dynamic) objects should be removed
1690  /// from intermediate results in the pipeline:
1691  ///
1692  /// - `mid_toi_pairs`
1693  /// - `narrow_toi_contacts`
1694  ///
1695  /// These objects will be checked for overlaps in the broad phase of the next
1696  /// detect/resolve loop iteration.
1697  // TODO: because we are only removing resolved dynamic objects and the resolved list
1698  // is sorted, we could be slicing the resolved set to only contain the dynamic ids and
1699  // exclude the static ids from binary search
1700  #[inline]
1701  fn remove_resolved_dynamic (&mut self, resolved : &SortedSet <InternalId>) {
1702    debug_assert!(self.broad_overlap_pairs.is_empty());
1703
1704    let (mut i, mut len);
1705
1706    // remove intermediate mid TOI pairs
1707    i   = 0;
1708    len = self.mid_toi_pairs.len();
1709    while i < len {
1710      let (_, _, object_pair) = self.mid_toi_pairs[i].0;
1711      let (id_a, id_b) = object_pair.into();
1712      if id_a.kind() == object::Kind::Dynamic && resolved.binary_search (&id_a).is_ok() {
1713        self.mid_toi_pairs.remove_index (i);
1714        len -= 1;
1715        continue
1716      }
1717      debug_assert_eq!(id_b.kind(), object::Kind::Dynamic);
1718      if resolved.binary_search (&id_b).is_ok() {
1719        self.mid_toi_pairs.remove_index (i);
1720        len -= 1;
1721        continue
1722      }
1723      i += 1;
1724    }
1725
1726    // remove intermediate narrow TOI contacts
1727    i   = 0;
1728    len = self.narrow_toi_contacts.len();
1729    while i < len {
1730      let (_, object_pair, _) = self.narrow_toi_contacts[i].clone().0;
1731      let (id_a, id_b) = object_pair.into();
1732      if id_a.kind() == object::Kind::Dynamic && resolved.binary_search (&id_a).is_ok() {
1733        self.narrow_toi_contacts.remove_index (i);
1734        len -= 1;
1735        continue
1736      }
1737      debug_assert_eq!(id_b.kind(), object::Kind::Dynamic);
1738      if resolved.binary_search (&id_b).is_ok() {
1739        self.narrow_toi_contacts.remove_index (i);
1740        len -= 1;
1741        continue
1742      }
1743      i += 1;
1744    }
1745  }
1746}
1747
1748impl ObjectPair {
1749  /// Create a new object pair.
1750  ///
1751  /// One object must be dynamic. Internally the object IDs will be stored in sorted
1752  /// order, and a static object will always be first.
1753  fn new (id_a : InternalId, id_b : InternalId) -> Self {
1754    debug_assert!(id_a.kind() == object::Kind::Dynamic ||
1755      id_b.kind() == object::Kind::Dynamic);
1756    if id_a < id_b {
1757      ObjectPair (id_a, id_b)
1758    } else {
1759      debug_assert!(id_a > id_b, "overlap ids should not be identical");
1760      ObjectPair (id_b, id_a)
1761    }
1762  }
1763}
1764
1765impl From <(InternalId, InternalId)> for ObjectPair {
1766  fn from ((id_a, id_b) : (InternalId, InternalId)) -> Self {
1767    ObjectPair::new (id_a, id_b)
1768  }
1769}
1770
1771impl InternalId {
1772  #[inline]
1773  fn new_static (key : object::Key) -> Self {
1774    debug_assert!(key.value() < OBJECT_KEY_MAX);
1775    InternalId (key.value())
1776  }
1777  #[inline]
1778  fn new_dynamic (key : object::Key) -> Self {
1779    debug_assert!(key.value() < OBJECT_KEY_MAX);
1780    InternalId (key.value() | INTERNAL_ID_DYNAMIC_BIT)
1781  }
1782  #[inline]
1783  const fn kind (self) -> object::Kind {
1784    match self.0 & INTERNAL_ID_DYNAMIC_BIT > 0 {
1785      true  => object::Kind::Dynamic,
1786      false => object::Kind::Static
1787    }
1788  }
1789  #[inline]
1790  fn key (self) -> object::Key {
1791    object::Key::from (self.0 & !INTERNAL_ID_DYNAMIC_BIT)
1792  }
1793}
1794impl From <InternalId> for object::Id {
1795  fn from (id : InternalId) -> Self {
1796    object::Id {
1797      kind: id.kind(),
1798      key:  id.key()
1799    }
1800  }
1801}