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