Skip to main content

collision_detection/
lib.rs

1#![deny(missing_docs)]
2
3/*!
4This crate defines an infrastructure to handle collision detection.
5
6It's based on the `collide` crate, so your colliders need to implement the `Collider` trait.
7
8The `CollisionManager` can only contain one type of collider.
9When you want to use multiple colliders, you currently need to use trait objects or enums.
10
11Then you can add colliders to the manager. You should store the returned [`ColliderId`]s, which will be used when you modify or remove them.
12When you're done, you can compute the collisions, which you can use to update your objects.
13You can either compute the collisions between all objects internally or between this object with a different layer.
14Before computing the collisions again, you should update your colliders, for example if the position of the object changed.
15**/
16
17use std::{
18    collections::{HashMap, HashSet},
19    hash::Hash,
20    ops::Neg,
21};
22
23use collide::{Bounded, BoundingVolume, Collider, CollisionInfo, SpatialPartition};
24use slab::Slab;
25
26/// The collision info with the index of the other collider.
27pub struct IndexedCollisionInfo<V, I> {
28    /// Index of the object.
29    pub index: I,
30    /// The actual collision info.
31    pub info: CollisionInfo<V>,
32}
33
34/// Opaque handle to a collider stored in a [`CollisionManager`].
35///
36/// Returned by [`CollisionManager::insert_collider`] and used to address that
37/// specific collider later (replace, remove, query). Distinct from the object
38/// index `I`: a `ColliderId` identifies one stored collider uniquely, whereas
39/// several colliders may share the same object index `I`.
40///
41/// An id is only valid until its collider is removed. Ids are **not**
42/// generational: [`CollisionManager::remove_collider`] frees the slot, and the
43/// next insert reuses it. A retained id then silently refers to the new
44/// collider in that slot rather than failing. Dropping ids of removed colliders
45/// is the caller's responsibility; this crate favours efficiency over guarding
46/// against it.
47#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
48pub struct ColliderId(usize);
49
50struct Indexed<C: Collider, I> {
51    index: I,
52    collider: C,
53}
54
55/// The collision manager, which manages collisions.
56/// Can contain multiple colliders.
57pub struct CollisionManager<C: Collider, I> {
58    colliders: Slab<Indexed<C, I>>,
59}
60
61impl<C: Collider, I: Copy> Default for CollisionManager<C, I> {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl<C: Collider, I: Copy> CollisionManager<C, I> {
68    /// Creates a new collision manager.
69    #[must_use]
70    pub const fn new() -> Self {
71        Self {
72            colliders: Slab::new(),
73        }
74    }
75
76    /// Creates a new collision manager with the capacity of expected colliders specified.
77    #[must_use]
78    pub fn with_capacity(capacity: usize) -> Self {
79        Self {
80            colliders: Slab::with_capacity(capacity),
81        }
82    }
83
84    /// Inserts a new collider and returns its [`ColliderId`].
85    /// An object index needs to be specified.
86    pub fn insert_collider(&mut self, collider: C, index: I) -> ColliderId {
87        ColliderId(self.colliders.insert(Indexed { index, collider }))
88    }
89
90    /// Replaces the existing collider at the specified id by a new collider.
91    pub fn replace_collider(&mut self, id: ColliderId, collider: C) {
92        self.colliders[id.0].collider = collider;
93    }
94
95    /// Removes an existing collider, freeing its slot for a new collider.
96    ///
97    /// Any [`ColliderId`] still held for the removed collider goes stale and
98    /// must be dropped; see [`ColliderId`] for what happens if it is reused.
99    pub fn remove_collider(&mut self, id: ColliderId) {
100        self.colliders.remove(id.0);
101    }
102
103    /// Get the collider at the specified id.
104    #[must_use]
105    pub fn collider(&self, id: ColliderId) -> &C {
106        &self.colliders[id.0].collider
107    }
108
109    /// Get the collider at the specified id as mutable.
110    pub fn collider_mut(&mut self, id: ColliderId) -> &mut C {
111        &mut self.colliders[id.0].collider
112    }
113
114    /// Checks if there is a collision at a specific position.
115    pub fn check_collision(&self, check_collider: &C) -> bool {
116        self.colliders
117            .iter()
118            .any(|(_, entry)| check_collider.check_collision(&entry.collider))
119    }
120
121    /// Checks for a collision with collider and returns some index if found.
122    pub fn find_collision(&self, check_collider: &C) -> Option<I> {
123        self.colliders.iter().find_map(|(_, entry)| {
124            check_collider
125                .check_collision(&entry.collider)
126                .then_some(entry.index)
127        })
128    }
129
130    /// Finds all collisions with colliders and returns their indices.
131    pub fn find_collisions<'a>(
132        &'a self,
133        check_collider: &'a C,
134    ) -> impl DoubleEndedIterator<Item = I> + 'a {
135        self.colliders.iter().filter_map(move |(_, entry)| {
136            check_collider
137                .check_collision(&entry.collider)
138                .then_some(entry.index)
139        })
140    }
141
142    /// Computes the internal collisions between all colliders.
143    /// Returns a map for each collider index to its collision infos.
144    #[must_use]
145    pub fn compute_inner_collisions(
146        &self,
147    ) -> HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>
148    where
149        C::Vector: Copy + Neg<Output = C::Vector>,
150    {
151        let mut result: HashMap<ColliderId, Vec<_>> = self
152            .colliders
153            .iter()
154            .map(|(key, _)| (ColliderId(key), Vec::new()))
155            .collect();
156
157        let entries: Vec<_> = self.colliders.iter().collect();
158        for (i, &(key, entry)) in entries.iter().enumerate() {
159            for &(other_key, other_entry) in &entries[i + 1..] {
160                if !entry.collider.check_collision(&other_entry.collider) {
161                    continue;
162                }
163                let Some(info) = entry.collider.collision_info(&other_entry.collider) else {
164                    continue;
165                };
166
167                result
168                    .entry(ColliderId(other_key))
169                    .or_default()
170                    .push(IndexedCollisionInfo {
171                        index: entry.index,
172                        info: -info,
173                    });
174                result
175                    .entry(ColliderId(key))
176                    .or_default()
177                    .push(IndexedCollisionInfo {
178                        index: other_entry.index,
179                        info,
180                    });
181            }
182        }
183
184        result
185    }
186
187    /// Computes the collisions between all colliders with the colliders of another collision manager.
188    /// Returns a map for each collider index to its collision infos.
189    #[must_use]
190    pub fn compute_collisions_with(
191        &self,
192        other: &Self,
193    ) -> HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>> {
194        self.colliders
195            .iter()
196            .map(|(key, entry)| {
197                let infos = other
198                    .colliders
199                    .iter()
200                    .filter(|(_, other_entry)| {
201                        entry.collider.check_collision(&other_entry.collider)
202                    })
203                    .filter_map(|(_, other_entry)| {
204                        entry
205                            .collider
206                            .collision_info(&other_entry.collider)
207                            .map(|info| IndexedCollisionInfo {
208                                index: other_entry.index,
209                                info,
210                            })
211                    })
212                    .collect();
213                (ColliderId(key), infos)
214            })
215            .collect()
216    }
217}
218
219impl<C: Collider + SpatialPartition, I: Copy> CollisionManager<C, I> {
220    fn cell_map(&self) -> HashMap<C::Cell, Vec<ColliderId>> {
221        let mut map: HashMap<C::Cell, Vec<ColliderId>> = HashMap::new();
222        for (key, entry) in &self.colliders {
223            for cell in entry.collider.cells() {
224                map.entry(cell).or_default().push(ColliderId(key));
225            }
226        }
227        map
228    }
229
230    /// Computes internal collisions using spatial partitioning.
231    /// Only checks pairs that share at least one cell, reducing O(n²) to O(n×k).
232    ///
233    /// The cell map is rebuilt from scratch on every call, so this is best
234    /// suited to layers whose colliders move between calls. For a static layer
235    /// queried repeatedly, building the acceleration structure once would be
236    /// cheaper, but that is not yet provided.
237    #[must_use]
238    pub fn compute_inner_collisions_spatial(
239        &self,
240    ) -> HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>
241    where
242        C::Vector: Copy + Neg<Output = C::Vector>,
243        C::Cell: Hash + Eq,
244    {
245        let cell_map = self.cell_map();
246
247        let mut result: HashMap<ColliderId, Vec<_>> = self
248            .colliders
249            .iter()
250            .map(|(key, _)| (ColliderId(key), Vec::new()))
251            .collect();
252
253        let mut checked = HashSet::new();
254        for keys_in_cell in cell_map.values() {
255            for (i, &key) in keys_in_cell.iter().enumerate() {
256                for &other_key in &keys_in_cell[i + 1..] {
257                    let pair = if key < other_key {
258                        [key, other_key]
259                    } else {
260                        [other_key, key]
261                    };
262                    if !checked.insert(pair) {
263                        continue;
264                    }
265
266                    let entry = &self.colliders[key.0];
267                    let other_entry = &self.colliders[other_key.0];
268                    if !entry.collider.check_collision(&other_entry.collider) {
269                        continue;
270                    }
271                    let Some(info) = entry.collider.collision_info(&other_entry.collider) else {
272                        continue;
273                    };
274
275                    result
276                        .entry(other_key)
277                        .or_default()
278                        .push(IndexedCollisionInfo {
279                            index: entry.index,
280                            info: -info,
281                        });
282                    result.entry(key).or_default().push(IndexedCollisionInfo {
283                        index: other_entry.index,
284                        info,
285                    });
286                }
287            }
288        }
289
290        result
291    }
292
293    /// Computes collisions with another manager using spatial partitioning.
294    /// Only checks pairs that share at least one cell, reducing O(n²) to O(n×k).
295    ///
296    /// The other manager's cell map is rebuilt on every call. When the same
297    /// static layer is queried repeatedly, building its acceleration structure
298    /// once would be cheaper, but that is not yet provided.
299    #[must_use]
300    pub fn compute_collisions_with_spatial(
301        &self,
302        other: &Self,
303    ) -> HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>> {
304        let other_cell_map = other.cell_map();
305
306        let mut result: HashMap<ColliderId, Vec<_>> = HashMap::new();
307        let mut checked: HashSet<[ColliderId; 2]> = HashSet::new();
308
309        for (key, entry) in &self.colliders {
310            let key = ColliderId(key);
311            result.entry(key).or_default();
312            for cell in entry.collider.cells() {
313                let Some(other_keys) = other_cell_map.get(&cell) else {
314                    continue;
315                };
316                for &other_key in other_keys {
317                    if !checked.insert([key, other_key]) {
318                        continue;
319                    }
320                    let other_entry = &other.colliders[other_key.0];
321                    if !entry.collider.check_collision(&other_entry.collider) {
322                        continue;
323                    }
324                    if let Some(info) = entry.collider.collision_info(&other_entry.collider) {
325                        result.entry(key).or_default().push(IndexedCollisionInfo {
326                            index: other_entry.index,
327                            info,
328                        });
329                    }
330                }
331            }
332        }
333
334        result
335    }
336}
337
338enum BvhNode<V: BoundingVolume> {
339    Leaf {
340        key: ColliderId,
341        volume: V,
342    },
343    Branch {
344        volume: V,
345        left: Box<Self>,
346        right: Box<Self>,
347    },
348}
349
350impl<V: BoundingVolume> BvhNode<V> {
351    const fn volume(&self) -> &V {
352        match self {
353            Self::Leaf { volume, .. } | Self::Branch { volume, .. } => volume,
354        }
355    }
356
357    fn build(items: &mut [(ColliderId, V)]) -> Self {
358        match items {
359            [] => unreachable!(),
360            [(key, volume)] => Self::Leaf {
361                key: *key,
362                volume: volume.clone(),
363            },
364            _ => {
365                let mid = items.len() / 2;
366                let left = Box::new(Self::build(&mut items[..mid]));
367                let right = Box::new(Self::build(&mut items[mid..]));
368                let volume = left.volume().merged(right.volume());
369                Self::Branch {
370                    volume,
371                    left,
372                    right,
373                }
374            }
375        }
376    }
377
378    fn self_collisions<C: Collider, I: Copy>(
379        &self,
380        colliders: &Slab<Indexed<C, I>>,
381        result: &mut HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>,
382    ) where
383        C::Vector: Copy + Neg<Output = C::Vector>,
384    {
385        match self {
386            Self::Leaf { .. } => {}
387            Self::Branch { left, right, .. } => {
388                left.self_collisions(colliders, result);
389                right.self_collisions(colliders, result);
390                Self::cross_collisions_inner(left, right, colliders, result);
391            }
392        }
393    }
394
395    fn cross_collisions_inner<C: Collider, I: Copy>(
396        a: &Self,
397        b: &Self,
398        colliders: &Slab<Indexed<C, I>>,
399        result: &mut HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>,
400    ) where
401        C::Vector: Copy + Neg<Output = C::Vector>,
402    {
403        if !a.volume().overlaps(b.volume()) {
404            return;
405        }
406
407        match (a, b) {
408            (Self::Leaf { key, .. }, Self::Leaf { key: other_key, .. }) => {
409                let entry = &colliders[key.0];
410                let other_entry = &colliders[other_key.0];
411                if !entry.collider.check_collision(&other_entry.collider) {
412                    return;
413                }
414                let Some(info) = entry.collider.collision_info(&other_entry.collider) else {
415                    return;
416                };
417                result
418                    .entry(*other_key)
419                    .or_default()
420                    .push(IndexedCollisionInfo {
421                        index: entry.index,
422                        info: -info,
423                    });
424                result.entry(*key).or_default().push(IndexedCollisionInfo {
425                    index: other_entry.index,
426                    info,
427                });
428            }
429            (Self::Leaf { .. }, Self::Branch { left, right, .. }) => {
430                Self::cross_collisions_inner(a, left, colliders, result);
431                Self::cross_collisions_inner(a, right, colliders, result);
432            }
433            (Self::Branch { left, right, .. }, Self::Leaf { .. }) => {
434                Self::cross_collisions_inner(left, b, colliders, result);
435                Self::cross_collisions_inner(right, b, colliders, result);
436            }
437            (
438                Self::Branch {
439                    left: a_left,
440                    right: a_right,
441                    ..
442                },
443                Self::Branch {
444                    left: b_left,
445                    right: b_right,
446                    ..
447                },
448            ) => {
449                Self::cross_collisions_inner(a_left, b_left, colliders, result);
450                Self::cross_collisions_inner(a_left, b_right, colliders, result);
451                Self::cross_collisions_inner(a_right, b_left, colliders, result);
452                Self::cross_collisions_inner(a_right, b_right, colliders, result);
453            }
454        }
455    }
456
457    fn cross_collisions_between<C: Collider, I: Copy>(
458        a: &Self,
459        b: &Self,
460        a_colliders: &Slab<Indexed<C, I>>,
461        b_colliders: &Slab<Indexed<C, I>>,
462        result: &mut HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>,
463    ) {
464        if !a.volume().overlaps(b.volume()) {
465            return;
466        }
467
468        match (a, b) {
469            (Self::Leaf { key, .. }, Self::Leaf { key: other_key, .. }) => {
470                let entry = &a_colliders[key.0];
471                let other_entry = &b_colliders[other_key.0];
472                if !entry.collider.check_collision(&other_entry.collider) {
473                    return;
474                }
475                if let Some(info) = entry.collider.collision_info(&other_entry.collider) {
476                    result.entry(*key).or_default().push(IndexedCollisionInfo {
477                        index: other_entry.index,
478                        info,
479                    });
480                }
481            }
482            (Self::Leaf { .. }, Self::Branch { left, right, .. }) => {
483                Self::cross_collisions_between(a, left, a_colliders, b_colliders, result);
484                Self::cross_collisions_between(a, right, a_colliders, b_colliders, result);
485            }
486            (Self::Branch { left, right, .. }, _) => {
487                Self::cross_collisions_between(left, b, a_colliders, b_colliders, result);
488                Self::cross_collisions_between(right, b, a_colliders, b_colliders, result);
489            }
490        }
491    }
492}
493
494impl<C: Collider, I: Copy> CollisionManager<C, I> {
495    fn build_bvh<V: BoundingVolume>(&self) -> Option<BvhNode<V>>
496    where
497        C: Bounded<V>,
498    {
499        let mut items: Vec<_> = self
500            .colliders
501            .iter()
502            .map(|(key, entry)| (ColliderId(key), entry.collider.bounding_volume()))
503            .collect();
504        if items.is_empty() {
505            return None;
506        }
507        Some(BvhNode::build(&mut items))
508    }
509
510    /// Computes internal collisions using a bounding volume hierarchy.
511    /// Skips pairs whose bounding volumes don't overlap, reducing O(n²) to O(n log n).
512    /// The type parameter `V` selects which bounding volume to use.
513    ///
514    /// The BVH is rebuilt on every call, so this fits layers whose colliders
515    /// move between calls. For a static layer queried repeatedly, building the
516    /// BVH once would be cheaper, but that is not yet provided.
517    #[must_use]
518    pub fn compute_inner_collisions_bvh<V: BoundingVolume>(
519        &self,
520    ) -> HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>
521    where
522        C: Bounded<V>,
523        C::Vector: Copy + Neg<Output = C::Vector>,
524    {
525        let mut result: HashMap<ColliderId, Vec<_>> = self
526            .colliders
527            .iter()
528            .map(|(key, _)| (ColliderId(key), Vec::new()))
529            .collect();
530
531        if let Some(root) = self.build_bvh::<V>() {
532            root.self_collisions(&self.colliders, &mut result);
533        }
534
535        result
536    }
537
538    /// Computes collisions with another manager using bounding volume hierarchies.
539    /// Builds a BVH for each manager and traverses them together,
540    /// skipping subtree pairs whose volumes don't overlap.
541    /// The type parameter `V` selects which bounding volume to use.
542    ///
543    /// Both BVHs are rebuilt on every call. When the same static layer is
544    /// queried repeatedly, building its BVH once would be cheaper, but that is
545    /// not yet provided.
546    #[must_use]
547    pub fn compute_collisions_with_bvh<V: BoundingVolume>(
548        &self,
549        other: &Self,
550    ) -> HashMap<ColliderId, Vec<IndexedCollisionInfo<C::Vector, I>>>
551    where
552        C: Bounded<V>,
553    {
554        let mut result: HashMap<ColliderId, Vec<_>> = self
555            .colliders
556            .iter()
557            .map(|(key, _)| (ColliderId(key), Vec::new()))
558            .collect();
559
560        if let Some(self_root) = self.build_bvh::<V>()
561            && let Some(other_root) = other.build_bvh::<V>()
562        {
563            BvhNode::cross_collisions_between(
564                &self_root,
565                &other_root,
566                &self.colliders,
567                &other.colliders,
568                &mut result,
569            );
570        }
571
572        result
573    }
574}
575
576#[cfg(test)]
577#[allow(clippy::unwrap_used, clippy::expect_used)]
578#[expect(
579    clippy::cast_possible_truncation,
580    clippy::cast_precision_loss,
581    clippy::needless_collect
582)]
583mod tests {
584    use super::*;
585    use collide_sphere::Sphere;
586    use inner_space::InnerSpace;
587    use simple_vectors::Vector;
588
589    type Vec3 = Vector<f32, 3>;
590
591    #[derive(Clone, Debug)]
592    struct TestSphere(Sphere<Vec3>);
593
594    impl Collider for TestSphere {
595        type Vector = Vec3;
596
597        fn check_collision(&self, other: &Self) -> bool {
598            self.0.check_collision(&other.0)
599        }
600
601        fn collision_info(&self, other: &Self) -> Option<CollisionInfo<Vec3>> {
602            self.0.collision_info(&other.0)
603        }
604    }
605
606    impl BoundingVolume for TestSphere {
607        fn overlaps(&self, other: &Self) -> bool {
608            self.0.check_collision(&other.0)
609        }
610
611        fn merged(&self, other: &Self) -> Self {
612            Self(collide::BoundingVolume::merged(&self.0, &other.0))
613        }
614    }
615
616    impl Bounded<Self> for TestSphere {
617        fn bounding_volume(&self) -> Self {
618            self.clone()
619        }
620    }
621
622    impl SpatialPartition for TestSphere {
623        type Cell = [i32; 3];
624
625        fn cells(&self) -> impl Iterator<Item = [i32; 3]> {
626            let min_x = (self.0.center[0] - self.0.radius).floor() as i32;
627            let max_x = (self.0.center[0] + self.0.radius).floor() as i32;
628            let min_y = (self.0.center[1] - self.0.radius).floor() as i32;
629            let max_y = (self.0.center[1] + self.0.radius).floor() as i32;
630            let min_z = (self.0.center[2] - self.0.radius).floor() as i32;
631            let max_z = (self.0.center[2] + self.0.radius).floor() as i32;
632
633            let mut result = Vec::new();
634            for x in min_x..=max_x {
635                for y in min_y..=max_y {
636                    for z in min_z..=max_z {
637                        result.push([x, y, z]);
638                    }
639                }
640            }
641            result.into_iter()
642        }
643    }
644
645    fn sphere(x: f32, y: f32, z: f32, radius: f32) -> TestSphere {
646        TestSphere(Sphere::new(Vec3::from([x, y, z]), radius))
647    }
648
649    fn setup_manager() -> CollisionManager<TestSphere, u32> {
650        let mut manager = CollisionManager::new();
651        manager.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), 0);
652        manager.insert_collider(sphere(1.5, 0.0, 0.0, 1.0), 1);
653        manager.insert_collider(sphere(10.0, 0.0, 0.0, 1.0), 2);
654        manager
655    }
656
657    #[test]
658    fn check_collision_finds_overlap() {
659        let manager = setup_manager();
660        assert!(manager.check_collision(&sphere(0.5, 0.0, 0.0, 0.1)));
661    }
662
663    #[test]
664    fn check_collision_misses() {
665        let manager = setup_manager();
666        assert!(!manager.check_collision(&sphere(5.0, 0.0, 0.0, 0.1)));
667    }
668
669    #[test]
670    fn find_collision_returns_index() {
671        let manager = setup_manager();
672        let found = manager.find_collision(&sphere(0.5, 0.0, 0.0, 0.1));
673        assert!(found == Some(0) || found == Some(1));
674    }
675
676    #[test]
677    fn find_collision_returns_none() {
678        let manager = setup_manager();
679        assert_eq!(manager.find_collision(&sphere(5.0, 0.0, 0.0, 0.1)), None);
680    }
681
682    #[test]
683    fn find_collisions_returns_all() {
684        let manager = setup_manager();
685        let found: Vec<_> = manager
686            .find_collisions(&sphere(0.75, 0.0, 0.0, 1.0))
687            .collect();
688        assert!(found.contains(&0));
689        assert!(found.contains(&1));
690        assert!(!found.contains(&2));
691    }
692
693    #[test]
694    fn inner_collisions_detects_pair() {
695        let manager = setup_manager();
696        let collisions = manager.compute_inner_collisions();
697
698        let hits_0: Vec<_> = collisions[&ColliderId(0)].iter().map(|c| c.index).collect();
699        let hits_2: Vec<_> = collisions[&ColliderId(2)].iter().map(|c| c.index).collect();
700
701        assert!(hits_0.contains(&1));
702        assert!(hits_2.is_empty());
703    }
704
705    #[test]
706    fn inner_collisions_bidirectional() {
707        let manager = setup_manager();
708        let collisions = manager.compute_inner_collisions();
709
710        let zero_hits_one = collisions[&ColliderId(0)].iter().any(|c| c.index == 1);
711        let one_hits_zero = collisions[&ColliderId(1)].iter().any(|c| c.index == 0);
712
713        assert!(zero_hits_one);
714        assert!(one_hits_zero);
715    }
716
717    #[test]
718    fn inner_collisions_vectors_opposite() {
719        let manager = setup_manager();
720        let collisions = manager.compute_inner_collisions();
721
722        let vec_0_to_1 = collisions[&ColliderId(0)]
723            .iter()
724            .find(|c| c.index == 1)
725            .unwrap()
726            .info
727            .vector;
728        let vec_1_to_0 = collisions[&ColliderId(1)]
729            .iter()
730            .find(|c| c.index == 0)
731            .unwrap()
732            .info
733            .vector;
734
735        let sum = vec_0_to_1 + vec_1_to_0;
736        assert!(sum.magnitude2() < 0.001);
737    }
738
739    #[test]
740    fn collisions_with_cross_manager() {
741        let mut manager_a = CollisionManager::new();
742        manager_a.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), 10);
743
744        let mut manager_b = CollisionManager::new();
745        manager_b.insert_collider(sphere(1.5, 0.0, 0.0, 1.0), 20);
746        manager_b.insert_collider(sphere(10.0, 0.0, 0.0, 1.0), 30);
747
748        let collisions = manager_a.compute_collisions_with(&manager_b);
749        let hits: Vec<_> = collisions
750            .values()
751            .flat_map(|v| v.iter().map(|c| c.index))
752            .collect();
753
754        assert!(hits.contains(&20));
755        assert!(!hits.contains(&30));
756    }
757
758    #[test]
759    fn insert_and_remove() {
760        let mut manager = CollisionManager::<TestSphere, u32>::new();
761        let idx = manager.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), 0);
762        manager.remove_collider(idx);
763
764        assert!(!manager.check_collision(&sphere(0.0, 0.0, 0.0, 0.1)));
765    }
766
767    #[test]
768    fn replace_collider_updates_position() {
769        let mut manager = CollisionManager::new();
770        let idx = manager.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), 0);
771        manager.insert_collider(sphere(1.5, 0.0, 0.0, 1.0), 1);
772
773        let collisions_before = manager.compute_inner_collisions();
774        assert!(!collisions_before[&idx].is_empty());
775
776        manager.replace_collider(idx, sphere(100.0, 0.0, 0.0, 1.0));
777
778        let collisions_after = manager.compute_inner_collisions();
779        assert!(collisions_after[&idx].is_empty());
780    }
781
782    #[test]
783    fn empty_manager() {
784        let manager = CollisionManager::<Sphere<Vec3>, u32>::new();
785        let collisions = manager.compute_inner_collisions();
786        assert!(collisions.is_empty());
787    }
788
789    #[test]
790    fn bvh_matches_brute_force() {
791        let manager = setup_manager();
792        let brute = manager.compute_inner_collisions();
793        let bvh = manager.compute_inner_collisions_bvh::<TestSphere>();
794
795        for (key, brute_hits) in &brute {
796            let bvh_hits = &bvh[key];
797            let mut brute_indices: Vec<_> = brute_hits.iter().map(|c| c.index).collect();
798            let mut bvh_indices: Vec<_> = bvh_hits.iter().map(|c| c.index).collect();
799            brute_indices.sort_unstable();
800            bvh_indices.sort_unstable();
801            assert_eq!(brute_indices, bvh_indices);
802        }
803    }
804
805    #[test]
806    fn bvh_cross_matches_brute_force() {
807        let mut manager_a = CollisionManager::new();
808        manager_a.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), 10);
809        manager_a.insert_collider(sphere(5.0, 0.0, 0.0, 1.0), 11);
810
811        let mut manager_b = CollisionManager::new();
812        manager_b.insert_collider(sphere(1.5, 0.0, 0.0, 1.0), 20);
813        manager_b.insert_collider(sphere(5.5, 0.0, 0.0, 1.0), 21);
814        manager_b.insert_collider(sphere(100.0, 0.0, 0.0, 1.0), 22);
815
816        let brute = manager_a.compute_collisions_with(&manager_b);
817        let bvh = manager_a.compute_collisions_with_bvh::<TestSphere>(&manager_b);
818
819        for (key, brute_hits) in &brute {
820            let bvh_hits = &bvh[key];
821            let mut brute_indices: Vec<_> = brute_hits.iter().map(|c| c.index).collect();
822            let mut bvh_indices: Vec<_> = bvh_hits.iter().map(|c| c.index).collect();
823            brute_indices.sort_unstable();
824            bvh_indices.sort_unstable();
825            assert_eq!(brute_indices, bvh_indices);
826        }
827    }
828
829    #[test]
830    fn bvh_many_objects() {
831        let mut manager = CollisionManager::new();
832        for i in 0..50u32 {
833            manager.insert_collider(sphere(i as f32 * 1.5, 0.0, 0.0, 1.0), i);
834        }
835
836        let brute = manager.compute_inner_collisions();
837        let bvh = manager.compute_inner_collisions_bvh::<TestSphere>();
838
839        let brute_count: usize = brute.values().map(std::vec::Vec::len).sum();
840        let bvh_count: usize = bvh.values().map(std::vec::Vec::len).sum();
841        assert_eq!(brute_count, bvh_count);
842    }
843
844    #[test]
845    fn bvh_no_collisions() {
846        let mut manager = CollisionManager::new();
847        for i in 0..10u32 {
848            manager.insert_collider(sphere(i as f32 * 10.0, 0.0, 0.0, 1.0), i);
849        }
850
851        let collisions = manager.compute_inner_collisions_bvh::<TestSphere>();
852        let total: usize = collisions.values().map(std::vec::Vec::len).sum();
853        assert_eq!(total, 0);
854    }
855
856    #[test]
857    fn bvh_all_overlapping() {
858        let mut manager = CollisionManager::new();
859        for i in 0..5u32 {
860            manager.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), i);
861        }
862
863        let brute = manager.compute_inner_collisions();
864        let bvh = manager.compute_inner_collisions_bvh::<TestSphere>();
865
866        let brute_count: usize = brute.values().map(std::vec::Vec::len).sum();
867        let bvh_count: usize = bvh.values().map(std::vec::Vec::len).sum();
868        assert_eq!(brute_count, bvh_count);
869        assert_eq!(brute_count, 5 * 4);
870    }
871
872    #[test]
873    fn spatial_matches_brute_force() {
874        let manager = setup_manager();
875        let brute = manager.compute_inner_collisions();
876        let spatial = manager.compute_inner_collisions_spatial();
877
878        for (key, brute_hits) in &brute {
879            let spatial_hits = &spatial[key];
880            let mut brute_indices: Vec<_> = brute_hits.iter().map(|c| c.index).collect();
881            let mut spatial_indices: Vec<_> = spatial_hits.iter().map(|c| c.index).collect();
882            brute_indices.sort_unstable();
883            spatial_indices.sort_unstable();
884            assert_eq!(brute_indices, spatial_indices);
885        }
886    }
887
888    #[test]
889    fn spatial_cross_matches_brute_force() {
890        let mut manager_a = CollisionManager::new();
891        manager_a.insert_collider(sphere(0.0, 0.0, 0.0, 1.0), 10);
892        manager_a.insert_collider(sphere(5.0, 0.0, 0.0, 1.0), 11);
893
894        let mut manager_b = CollisionManager::new();
895        manager_b.insert_collider(sphere(1.5, 0.0, 0.0, 1.0), 20);
896        manager_b.insert_collider(sphere(100.0, 0.0, 0.0, 1.0), 30);
897
898        let brute = manager_a.compute_collisions_with(&manager_b);
899        let spatial = manager_a.compute_collisions_with_spatial(&manager_b);
900
901        for (key, brute_hits) in &brute {
902            let spatial_hits = &spatial[key];
903            let mut brute_indices: Vec<_> = brute_hits.iter().map(|c| c.index).collect();
904            let mut spatial_indices: Vec<_> = spatial_hits.iter().map(|c| c.index).collect();
905            brute_indices.sort_unstable();
906            spatial_indices.sort_unstable();
907            assert_eq!(brute_indices, spatial_indices);
908        }
909    }
910
911    #[test]
912    fn spatial_many_objects() {
913        let mut manager = CollisionManager::new();
914        for i in 0..50u32 {
915            manager.insert_collider(sphere(i as f32 * 1.5, 0.0, 0.0, 1.0), i);
916        }
917
918        let brute = manager.compute_inner_collisions();
919        let spatial = manager.compute_inner_collisions_spatial();
920
921        let brute_count: usize = brute.values().map(std::vec::Vec::len).sum();
922        let spatial_count: usize = spatial.values().map(std::vec::Vec::len).sum();
923        assert_eq!(brute_count, spatial_count);
924    }
925}