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)]
41pub struct ColliderId(usize);
42
43struct Indexed<C: Collider, I> {
44 index: I,
45 collider: C,
46}
47
48pub 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 pub fn new() -> Self {
63 Self {
64 colliders: Slab::new(),
65 }
66 }
67
68 pub fn with_capacity(capacity: usize) -> Self {
70 Self {
71 colliders: Slab::with_capacity(capacity),
72 }
73 }
74
75 pub fn insert_collider(&mut self, collider: C, index: I) -> ColliderId {
78 ColliderId(self.colliders.insert(Indexed { index, collider }))
79 }
80
81 pub fn replace_collider(&mut self, id: ColliderId, collider: C) {
83 self.colliders[id.0].collider = collider;
84 }
85
86 pub fn remove_collider(&mut self, id: ColliderId) {
88 self.colliders.remove(id.0);
89 }
90
91 pub fn collider(&self, id: ColliderId) -> &C {
93 &self.colliders[id.0].collider
94 }
95
96 pub fn collider_mut(&mut self, id: ColliderId) -> &mut C {
98 &mut self.colliders[id.0].collider
99 }
100
101 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 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 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 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 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 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 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 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 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}