1#![deny(missing_docs)]
2
3use std::{
18 collections::{HashMap, HashSet},
19 hash::Hash,
20 ops::Neg,
21};
22
23use collide::{Bounded, BoundingVolume, Collider, CollisionInfo, SpatialPartition};
24use slab::Slab;
25
26pub struct IndexedCollisionInfo<V, I> {
28 pub index: I,
30 pub info: CollisionInfo<V>,
32}
33
34#[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
55pub 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 #[must_use]
70 pub const fn new() -> Self {
71 Self {
72 colliders: Slab::new(),
73 }
74 }
75
76 #[must_use]
78 pub fn with_capacity(capacity: usize) -> Self {
79 Self {
80 colliders: Slab::with_capacity(capacity),
81 }
82 }
83
84 pub fn insert_collider(&mut self, collider: C, index: I) -> ColliderId {
87 ColliderId(self.colliders.insert(Indexed { index, collider }))
88 }
89
90 pub fn replace_collider(&mut self, id: ColliderId, collider: C) {
92 self.colliders[id.0].collider = collider;
93 }
94
95 pub fn remove_collider(&mut self, id: ColliderId) {
100 self.colliders.remove(id.0);
101 }
102
103 #[must_use]
105 pub fn collider(&self, id: ColliderId) -> &C {
106 &self.colliders[id.0].collider
107 }
108
109 pub fn collider_mut(&mut self, id: ColliderId) -> &mut C {
111 &mut self.colliders[id.0].collider
112 }
113
114 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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}