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