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
34struct Indexed<C: Collider, I> {
35 index: I,
36 collider: C,
37}
38
39pub 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 pub fn new() -> Self {
54 Self {
55 colliders: Slab::new(),
56 }
57 }
58
59 pub fn with_capacity(capacity: usize) -> Self {
61 Self {
62 colliders: Slab::with_capacity(capacity),
63 }
64 }
65
66 pub fn insert_collider(&mut self, collider: C, index: I) -> usize {
69 self.colliders.insert(Indexed { index, collider })
70 }
71
72 pub fn replace_collider(&mut self, index: usize, collider: C) {
74 self.colliders[index].collider = collider;
75 }
76
77 pub fn remove_collider(&mut self, index: usize) {
79 self.colliders.remove(index);
80 }
81
82 pub fn collider(&self, index: usize) -> &C {
84 &self.colliders[index].collider
85 }
86
87 pub fn collider_mut(&mut self, index: usize) -> &mut C {
89 &mut self.colliders[index].collider
90 }
91
92 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 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 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 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 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 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 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 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 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}