1#[derive(Clone, Debug, PartialEq)]
17pub struct QueryAabb {
18 pub min: [f64; 3],
20 pub max: [f64; 3],
22}
23
24impl QueryAabb {
25 pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
27 QueryAabb { min, max }
28 }
29
30 pub fn fatten(&self, margin: f64) -> Self {
32 QueryAabb {
33 min: [
34 self.min[0] - margin,
35 self.min[1] - margin,
36 self.min[2] - margin,
37 ],
38 max: [
39 self.max[0] + margin,
40 self.max[1] + margin,
41 self.max[2] + margin,
42 ],
43 }
44 }
45
46 pub fn surface_area(&self) -> f64 {
48 let dx = self.max[0] - self.min[0];
49 let dy = self.max[1] - self.min[1];
50 let dz = self.max[2] - self.min[2];
51 2.0 * (dx * dy + dy * dz + dz * dx)
52 }
53
54 pub fn center(&self) -> [f64; 3] {
56 [
57 0.5 * (self.min[0] + self.max[0]),
58 0.5 * (self.min[1] + self.max[1]),
59 0.5 * (self.min[2] + self.max[2]),
60 ]
61 }
62
63 pub fn union(&self, other: &QueryAabb) -> QueryAabb {
65 QueryAabb {
66 min: [
67 self.min[0].min(other.min[0]),
68 self.min[1].min(other.min[1]),
69 self.min[2].min(other.min[2]),
70 ],
71 max: [
72 self.max[0].max(other.max[0]),
73 self.max[1].max(other.max[1]),
74 self.max[2].max(other.max[2]),
75 ],
76 }
77 }
78
79 pub fn overlaps(&self, other: &QueryAabb) -> bool {
81 self.min[0] <= other.max[0]
82 && self.max[0] >= other.min[0]
83 && self.min[1] <= other.max[1]
84 && self.max[1] >= other.min[1]
85 && self.min[2] <= other.max[2]
86 && self.max[2] >= other.min[2]
87 }
88
89 pub fn contains_point(&self, p: [f64; 3]) -> bool {
91 p[0] >= self.min[0]
92 && p[0] <= self.max[0]
93 && p[1] >= self.min[1]
94 && p[1] <= self.max[1]
95 && p[2] >= self.min[2]
96 && p[2] <= self.max[2]
97 }
98
99 pub fn point_dist_sq(&self, p: [f64; 3]) -> f64 {
101 p.iter()
102 .zip(self.min.iter())
103 .zip(self.max.iter())
104 .map(|((&pi, &mini), &maxi)| {
105 if pi < mini {
106 (mini - pi) * (mini - pi)
107 } else if pi > maxi {
108 (pi - maxi) * (pi - maxi)
109 } else {
110 0.0
111 }
112 })
113 .sum()
114 }
115}
116
117pub fn aabb_overlap(a: &QueryAabb, b: &QueryAabb) -> bool {
123 a.overlaps(b)
124}
125
126pub fn frustum_aabb_test(aabb: &QueryAabb, planes: &[([f64; 3], f64)]) -> bool {
131 for &(n, d) in planes {
132 let px = if n[0] > 0.0 { aabb.max[0] } else { aabb.min[0] };
134 let py = if n[1] > 0.0 { aabb.max[1] } else { aabb.min[1] };
135 let pz = if n[2] > 0.0 { aabb.max[2] } else { aabb.min[2] };
136 let dist = n[0] * px + n[1] * py + n[2] * pz + d;
137 if dist < 0.0 {
138 return false;
139 } }
141 true
142}
143
144pub fn sweep_aabb_motion(aabb: &QueryAabb, motion: [f64; 3]) -> QueryAabb {
146 let mut min = aabb.min;
147 let mut max = aabb.max;
148 for ((mn, mx), &m) in min.iter_mut().zip(max.iter_mut()).zip(motion.iter()) {
149 if m < 0.0 {
150 *mn += m;
151 } else {
152 *mx += m;
153 }
154 }
155 QueryAabb { min, max }
156}
157
158pub fn rebalance_tree(_tree: &mut AabbTree) {
162 }
164
165#[derive(Clone, Debug)]
171pub struct DynamicNode {
172 pub fat_aabb: QueryAabb,
174 pub aabb: QueryAabb,
176 pub parent: Option<usize>,
178 pub children: [Option<usize>; 2],
180 pub height: i32,
182 pub user_data: usize,
184 pub is_leaf: bool,
186}
187
188impl DynamicNode {
189 pub fn leaf(aabb: QueryAabb, margin: f64, user_data: usize) -> Self {
191 let fat_aabb = aabb.fatten(margin);
192 DynamicNode {
193 fat_aabb,
194 aabb,
195 parent: None,
196 children: [None, None],
197 height: 0,
198 user_data,
199 is_leaf: true,
200 }
201 }
202
203 pub fn internal(fat_aabb: QueryAabb) -> Self {
205 DynamicNode {
206 fat_aabb: fat_aabb.clone(),
207 aabb: fat_aabb,
208 parent: None,
209 children: [None, None],
210 height: 1,
211 user_data: usize::MAX,
212 is_leaf: false,
213 }
214 }
215}
216
217pub struct AabbTree {
223 pub nodes: Vec<DynamicNode>,
225 pub root: Option<usize>,
227 free_nodes: Vec<usize>,
229 pub margin: f64,
231}
232
233impl AabbTree {
234 pub fn new(margin: f64) -> Self {
236 AabbTree {
237 nodes: Vec::new(),
238 root: None,
239 free_nodes: Vec::new(),
240 margin,
241 }
242 }
243
244 fn alloc_node(&mut self, node: DynamicNode) -> usize {
245 if let Some(idx) = self.free_nodes.pop() {
246 self.nodes[idx] = node;
247 idx
248 } else {
249 self.nodes.push(node);
250 self.nodes.len() - 1
251 }
252 }
253
254 pub fn insert(&mut self, aabb: QueryAabb, user_data: usize) -> usize {
256 let leaf_idx = self.alloc_node(DynamicNode::leaf(aabb.clone(), self.margin, user_data));
257
258 let root = match self.root {
259 None => {
260 self.root = Some(leaf_idx);
261 return leaf_idx;
262 }
263 Some(r) => r,
264 };
265
266 let mut best = root;
268 let mut stack = vec![root];
269 let leaf_aabb = aabb.clone();
270 while let Some(idx) = stack.pop() {
271 let node_aabb = self.nodes[idx].fat_aabb.clone();
272 let combined = node_aabb.union(&leaf_aabb);
273 let combined_sa = combined.surface_area();
274 let cost = combined_sa;
275 if cost < self.nodes[best].fat_aabb.union(&leaf_aabb).surface_area() {
276 best = idx;
277 }
278 if !self.nodes[idx].is_leaf {
279 for c in 0..2 {
280 if let Some(child) = self.nodes[idx].children[c] {
281 stack.push(child);
282 }
283 }
284 }
285 }
286
287 let sibling = best;
289 let old_parent = self.nodes[sibling].parent;
290 let new_parent_aabb = self.nodes[sibling]
291 .fat_aabb
292 .union(&leaf_aabb.fatten(self.margin));
293 let new_parent_idx = self.alloc_node(DynamicNode::internal(new_parent_aabb));
294 self.nodes[new_parent_idx].parent = old_parent;
295 self.nodes[new_parent_idx].children = [Some(sibling), Some(leaf_idx)];
296 self.nodes[new_parent_idx].height = self.nodes[sibling].height + 1;
297 self.nodes[sibling].parent = Some(new_parent_idx);
298 self.nodes[leaf_idx].parent = Some(new_parent_idx);
299
300 if let Some(op) = old_parent {
301 for c in 0..2 {
302 if self.nodes[op].children[c] == Some(sibling) {
303 self.nodes[op].children[c] = Some(new_parent_idx);
304 }
305 }
306 } else {
307 self.root = Some(new_parent_idx);
308 }
309
310 leaf_idx
311 }
312
313 pub fn remove(&mut self, leaf_idx: usize) {
315 if self.root == Some(leaf_idx) {
316 self.root = None;
317 self.free_nodes.push(leaf_idx);
318 return;
319 }
320 if let Some(parent_idx) = self.nodes[leaf_idx].parent {
321 let sibling = {
322 let ch = self.nodes[parent_idx].children;
323 if ch[0] == Some(leaf_idx) {
324 ch[1]
325 } else {
326 ch[0]
327 }
328 };
329 if let Some(grandparent) = self.nodes[parent_idx].parent {
330 for c in 0..2 {
331 if self.nodes[grandparent].children[c] == Some(parent_idx) {
332 self.nodes[grandparent].children[c] = sibling;
333 }
334 }
335 if let Some(s) = sibling {
336 self.nodes[s].parent = Some(grandparent);
337 }
338 } else {
339 self.root = sibling;
340 if let Some(s) = sibling {
341 self.nodes[s].parent = None;
342 }
343 }
344 self.free_nodes.push(parent_idx);
345 }
346 self.free_nodes.push(leaf_idx);
347 }
348
349 pub fn update(&mut self, leaf_idx: usize, new_aabb: QueryAabb) {
351 if self.nodes[leaf_idx].fat_aabb.overlaps(&new_aabb) {
352 self.nodes[leaf_idx].aabb = new_aabb;
353 return;
354 }
355 let user_data = self.nodes[leaf_idx].user_data;
356 self.remove(leaf_idx);
357 self.insert(new_aabb, user_data);
358 }
359
360 pub fn query_aabb(&self, query_aabb: &QueryAabb) -> Vec<usize> {
362 let mut result = Vec::new();
363 let Some(root) = self.root else {
364 return result;
365 };
366 let mut stack = vec![root];
367 while let Some(idx) = stack.pop() {
368 let node = &self.nodes[idx];
369 if !node.fat_aabb.overlaps(query_aabb) {
370 continue;
371 }
372 if node.is_leaf {
373 result.push(node.user_data);
374 } else {
375 for c in 0..2 {
376 if let Some(child) = node.children[c] {
377 stack.push(child);
378 }
379 }
380 }
381 }
382 result
383 }
384
385 pub fn raycast(&self, origin: [f64; 3], dir: [f64; 3], max_t: f64) -> Vec<usize> {
387 let mut result = Vec::new();
388 let Some(root) = self.root else {
389 return result;
390 };
391 let mut stack = vec![root];
392 while let Some(idx) = stack.pop() {
393 let node = &self.nodes[idx];
394 if !ray_aabb_hit(&node.fat_aabb, origin, dir, max_t) {
395 continue;
396 }
397 if node.is_leaf {
398 result.push(node.user_data);
399 } else {
400 for c in 0..2 {
401 if let Some(child) = node.children[c] {
402 stack.push(child);
403 }
404 }
405 }
406 }
407 result
408 }
409
410 pub fn n_nodes(&self) -> usize {
412 self.nodes.len().saturating_sub(self.free_nodes.len())
413 }
414}
415
416fn ray_aabb_hit(aabb: &QueryAabb, origin: [f64; 3], dir: [f64; 3], max_t: f64) -> bool {
417 let mut t_min = 0.0_f64;
418 let mut t_max = max_t;
419 for ((&o, &d), (&mn, &mx)) in origin
420 .iter()
421 .zip(dir.iter())
422 .zip(aabb.min.iter().zip(aabb.max.iter()))
423 {
424 if d.abs() < 1e-14 {
425 if o < mn || o > mx {
426 return false;
427 }
428 } else {
429 let inv = 1.0 / d;
430 let t1 = (mn - o) * inv;
431 let t2 = (mx - o) * inv;
432 let ta = t1.min(t2);
433 let tb = t1.max(t2);
434 t_min = t_min.max(ta);
435 t_max = t_max.min(tb);
436 if t_min > t_max {
437 return false;
438 }
439 }
440 }
441 true
442}
443
444#[derive(Clone, Debug, PartialEq, Eq)]
450pub struct ShapePair {
451 pub a: usize,
453 pub b: usize,
455}
456
457impl ShapePair {
458 pub fn new(a: usize, b: usize) -> Self {
460 if a < b {
461 ShapePair { a, b }
462 } else {
463 ShapePair { a: b, b: a }
464 }
465 }
466}
467
468pub struct PairQuery;
470
471impl PairQuery {
472 pub fn brute_force(aabbs: &[QueryAabb]) -> Vec<ShapePair> {
474 let mut pairs = Vec::new();
475 for (i, a) in aabbs.iter().enumerate() {
476 for (j, b) in aabbs[i + 1..].iter().enumerate() {
477 if a.overlaps(b) {
478 pairs.push(ShapePair::new(i, i + 1 + j));
479 }
480 }
481 }
482 pairs
483 }
484
485 pub fn sweep_and_prune(aabbs: &[QueryAabb]) -> Vec<ShapePair> {
487 let mut sorted: Vec<usize> = (0..aabbs.len()).collect();
488 sorted.sort_unstable_by(|&a, &b| {
489 aabbs[a].min[0]
490 .partial_cmp(&aabbs[b].min[0])
491 .unwrap_or(std::cmp::Ordering::Equal)
492 });
493 let mut pairs = Vec::new();
494 for (i, &a) in sorted.iter().enumerate() {
495 for &b in &sorted[i + 1..] {
496 if aabbs[b].min[0] > aabbs[a].max[0] {
497 break;
498 }
499 if aabbs[a].overlaps(&aabbs[b]) {
500 pairs.push(ShapePair::new(a, b));
501 }
502 }
503 }
504 pairs
505 }
506
507 pub fn aabb_tree(aabbs: &[QueryAabb]) -> Vec<ShapePair> {
509 let mut tree = AabbTree::new(0.01);
510 for (i, aabb) in aabbs.iter().enumerate() {
511 tree.insert(aabb.clone(), i);
512 }
513 let mut pairs = Vec::new();
514 for (i, aabb) in aabbs.iter().enumerate() {
515 let hits = tree.query_aabb(aabb);
516 for h in hits {
517 if h != i && h > i {
518 pairs.push(ShapePair::new(i, h));
519 }
520 }
521 }
522 pairs.sort_unstable_by_key(|p| (p.a, p.b));
524 pairs.dedup();
525 pairs
526 }
527}
528
529pub struct FrustumCulling {
535 pub planes: Vec<([f64; 3], f64)>,
537}
538
539impl FrustumCulling {
540 pub fn new(planes: Vec<([f64; 3], f64)>) -> Self {
542 FrustumCulling { planes }
543 }
544
545 pub fn cull(&self, aabbs: &[QueryAabb]) -> Vec<usize> {
547 aabbs
548 .iter()
549 .enumerate()
550 .filter(|(_, aabb)| frustum_aabb_test(aabb, &self.planes))
551 .map(|(i, _)| i)
552 .collect()
553 }
554
555 pub fn test(&self, aabb: &QueryAabb) -> bool {
557 frustum_aabb_test(aabb, &self.planes)
558 }
559}
560
561#[derive(Clone, Debug)]
567pub struct SphereQuery {
568 pub center: [f64; 3],
570 pub radius: f64,
572}
573
574impl SphereQuery {
575 pub fn new(center: [f64; 3], radius: f64) -> Self {
577 SphereQuery { center, radius }
578 }
579
580 pub fn query_sorted(&self, aabbs: &[QueryAabb]) -> Vec<usize> {
582 let r2 = self.radius * self.radius;
583 let mut hits: Vec<(usize, f64)> = aabbs
584 .iter()
585 .enumerate()
586 .filter_map(|(i, aabb)| {
587 let d2 = aabb.point_dist_sq(self.center);
588 if d2 <= r2 { Some((i, d2)) } else { None }
589 })
590 .collect();
591 hits.sort_unstable_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
592 hits.into_iter().map(|(i, _)| i).collect()
593 }
594
595 pub fn aabb(&self) -> QueryAabb {
597 let r = self.radius;
598 QueryAabb {
599 min: [self.center[0] - r, self.center[1] - r, self.center[2] - r],
600 max: [self.center[0] + r, self.center[1] + r, self.center[2] + r],
601 }
602 }
603}
604
605pub struct CapsuleQuery {
611 pub p0: [f64; 3],
613 pub p1: [f64; 3],
615 pub radius: f64,
617}
618
619impl CapsuleQuery {
620 pub fn new(p0: [f64; 3], p1: [f64; 3], radius: f64) -> Self {
622 CapsuleQuery { p0, p1, radius }
623 }
624
625 pub fn aabb(&self) -> QueryAabb {
627 let r = self.radius;
628 QueryAabb {
629 min: [
630 self.p0[0].min(self.p1[0]) - r,
631 self.p0[1].min(self.p1[1]) - r,
632 self.p0[2].min(self.p1[2]) - r,
633 ],
634 max: [
635 self.p0[0].max(self.p1[0]) + r,
636 self.p0[1].max(self.p1[1]) + r,
637 self.p0[2].max(self.p1[2]) + r,
638 ],
639 }
640 }
641
642 pub fn query(&self, aabbs: &[QueryAabb]) -> Vec<usize> {
644 let caps_aabb = self.aabb();
645 aabbs
646 .iter()
647 .enumerate()
648 .filter(|(_, aabb)| aabb.overlaps(&caps_aabb))
649 .map(|(i, _)| i)
650 .collect()
651 }
652}
653
654pub struct PointQuery {
660 pub point: [f64; 3],
662}
663
664impl PointQuery {
665 pub fn new(point: [f64; 3]) -> Self {
667 PointQuery { point }
668 }
669
670 pub fn nearest_n(&self, aabbs: &[QueryAabb], n: usize) -> Vec<usize> {
672 let mut dists: Vec<(usize, f64)> = aabbs
673 .iter()
674 .enumerate()
675 .map(|(i, aabb)| (i, aabb.point_dist_sq(self.point).sqrt()))
676 .collect();
677 dists.sort_unstable_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
678 dists.into_iter().take(n).map(|(i, _)| i).collect()
679 }
680
681 pub fn distance_to(&self, aabb: &QueryAabb) -> f64 {
683 aabb.point_dist_sq(self.point).sqrt()
684 }
685}
686
687pub struct ConvexSweep {
693 pub shape_aabb: QueryAabb,
695 pub motion: [f64; 3],
697}
698
699impl ConvexSweep {
700 pub fn new(shape_aabb: QueryAabb, motion: [f64; 3]) -> Self {
702 ConvexSweep { shape_aabb, motion }
703 }
704
705 pub fn swept_aabb(&self) -> QueryAabb {
707 sweep_aabb_motion(&self.shape_aabb, self.motion)
708 }
709
710 pub fn query(&self, aabbs: &[QueryAabb]) -> Vec<usize> {
712 let swept = self.swept_aabb();
713 aabbs
714 .iter()
715 .enumerate()
716 .filter(|(_, aabb)| aabb.overlaps(&swept))
717 .map(|(i, _)| i)
718 .collect()
719 }
720}
721
722pub struct ContactPairFilter {
728 pub layer_masks: Vec<u32>,
730 pub groups: Vec<u32>,
732}
733
734impl ContactPairFilter {
735 pub fn new(layer_masks: Vec<u32>, groups: Vec<u32>) -> Self {
737 ContactPairFilter {
738 layer_masks,
739 groups,
740 }
741 }
742
743 pub fn should_collide(&self, a: usize, b: usize) -> bool {
745 let mask_a = self.layer_masks.get(a).copied().unwrap_or(0xFFFF_FFFF);
746 let mask_b = self.layer_masks.get(b).copied().unwrap_or(0xFFFF_FFFF);
747 (mask_a & mask_b) != 0
748 }
749
750 pub fn filter(&self, pairs: &[ShapePair]) -> Vec<ShapePair> {
752 pairs
753 .iter()
754 .filter(|p| self.should_collide(p.a, p.b))
755 .cloned()
756 .collect()
757 }
758}
759
760#[derive(Clone, Debug)]
766pub enum PendingQuery {
767 AabbQuery(QueryAabb),
769 SphereQuery(SphereQuery),
771 PointQuery {
773 point: [f64; 3],
775 n: usize,
777 },
778}
779
780#[derive(Clone, Debug)]
782pub struct QueryResult {
783 pub hits: Vec<usize>,
785}
786
787pub struct QueryPipeline {
789 pub aabbs: Vec<QueryAabb>,
791 pub pending: Vec<PendingQuery>,
793 pub results: Vec<QueryResult>,
795}
796
797impl QueryPipeline {
798 pub fn new() -> Self {
800 QueryPipeline {
801 aabbs: Vec::new(),
802 pending: Vec::new(),
803 results: Vec::new(),
804 }
805 }
806
807 pub fn add_shape(&mut self, aabb: QueryAabb) -> usize {
809 self.aabbs.push(aabb);
810 self.aabbs.len() - 1
811 }
812
813 pub fn submit(&mut self, query: PendingQuery) {
815 self.pending.push(query);
816 }
817
818 pub fn flush(&mut self) {
820 let aabbs = &self.aabbs;
821 let mut new_results = Vec::with_capacity(self.pending.len());
822 for query in self.pending.drain(..) {
823 let hits = match query {
824 PendingQuery::AabbQuery(aabb) => aabbs
825 .iter()
826 .enumerate()
827 .filter(|(_, a)| a.overlaps(&aabb))
828 .map(|(i, _)| i)
829 .collect(),
830 PendingQuery::SphereQuery(sq) => {
831 let sphere_aabb = sq.aabb();
832 aabbs
833 .iter()
834 .enumerate()
835 .filter(|(_, a)| a.overlaps(&sphere_aabb))
836 .map(|(i, _)| i)
837 .collect()
838 }
839 PendingQuery::PointQuery { point, n } => {
840 let pq = PointQuery::new(point);
841 pq.nearest_n(aabbs, n)
842 }
843 };
844 new_results.push(QueryResult { hits });
845 }
846 self.results = new_results;
847 }
848
849 pub fn n_shapes(&self) -> usize {
851 self.aabbs.len()
852 }
853}
854
855impl Default for QueryPipeline {
856 fn default() -> Self {
857 Self::new()
858 }
859}
860
861#[cfg(test)]
866mod tests {
867 use super::*;
868
869 fn unit_aabb() -> QueryAabb {
870 QueryAabb::new([0.0; 3], [1.0; 3])
871 }
872
873 fn shifted_aabb(offset: f64) -> QueryAabb {
874 QueryAabb::new([offset; 3], [offset + 1.0; 3])
875 }
876
877 #[test]
878 fn test_aabb_overlaps_touching() {
879 let a = QueryAabb::new([0.0; 3], [1.0; 3]);
880 let b = QueryAabb::new([1.0; 3], [2.0; 3]);
881 assert!(a.overlaps(&b));
882 }
883
884 #[test]
885 fn test_aabb_no_overlap() {
886 let a = QueryAabb::new([0.0; 3], [1.0; 3]);
887 let b = QueryAabb::new([2.0; 3], [3.0; 3]);
888 assert!(!a.overlaps(&b));
889 }
890
891 #[test]
892 fn test_aabb_union() {
893 let a = QueryAabb::new([0.0; 3], [1.0; 3]);
894 let b = QueryAabb::new([1.0; 3], [2.0; 3]);
895 let u = a.union(&b);
896 assert_eq!(u.min, [0.0; 3]);
897 assert_eq!(u.max, [2.0; 3]);
898 }
899
900 #[test]
901 fn test_aabb_contains_point() {
902 let a = unit_aabb();
903 assert!(a.contains_point([0.5, 0.5, 0.5]));
904 assert!(!a.contains_point([2.0, 0.5, 0.5]));
905 }
906
907 #[test]
908 fn test_aabb_point_dist_sq_inside() {
909 let a = unit_aabb();
910 assert_eq!(a.point_dist_sq([0.5, 0.5, 0.5]), 0.0);
911 }
912
913 #[test]
914 fn test_aabb_point_dist_sq_outside() {
915 let a = unit_aabb();
916 let d2 = a.point_dist_sq([2.0, 0.5, 0.5]);
917 assert!((d2 - 1.0).abs() < 1e-12);
918 }
919
920 #[test]
921 fn test_aabb_fatten() {
922 let a = unit_aabb();
923 let fat = a.fatten(0.5);
924 assert!((fat.min[0] - (-0.5)).abs() < 1e-12);
925 assert!((fat.max[0] - 1.5).abs() < 1e-12);
926 }
927
928 #[test]
929 fn test_aabb_tree_insert_and_query() {
930 let mut tree = AabbTree::new(0.1);
931 let idx = tree.insert(unit_aabb(), 42);
932 let hits = tree.query_aabb(&unit_aabb());
933 assert!(hits.contains(&42), "hits={:?}, leaf_idx={}", hits, idx);
934 }
935
936 #[test]
937 fn test_aabb_tree_remove() {
938 let mut tree = AabbTree::new(0.1);
939 let leaf = tree.insert(unit_aabb(), 0);
940 tree.remove(leaf);
941 let hits = tree.query_aabb(&unit_aabb());
942 assert!(hits.is_empty());
943 }
944
945 #[test]
946 fn test_aabb_tree_multiple_inserts() {
947 let mut tree = AabbTree::new(0.1);
948 tree.insert(unit_aabb(), 0);
949 tree.insert(shifted_aabb(5.0), 1);
950 let hits = tree.query_aabb(&unit_aabb());
951 assert!(hits.contains(&0));
952 assert!(!hits.contains(&1));
953 }
954
955 #[test]
956 fn test_aabb_tree_raycast() {
957 let mut tree = AabbTree::new(0.1);
958 tree.insert(unit_aabb(), 7);
959 let hits = tree.raycast([-5.0, 0.5, 0.5], [1.0, 0.0, 0.0], 20.0);
960 assert!(hits.contains(&7));
961 }
962
963 #[test]
964 fn test_pair_query_brute_force() {
965 let aabbs = vec![unit_aabb(), shifted_aabb(0.5), shifted_aabb(10.0)];
966 let pairs = PairQuery::brute_force(&aabbs);
967 assert!(
969 pairs
970 .iter()
971 .any(|p| (p.a == 0 && p.b == 1) || (p.a == 1 && p.b == 0))
972 );
973 assert!(!pairs.iter().any(|p| p.a == 2 || p.b == 2));
974 }
975
976 #[test]
977 fn test_pair_query_sweep_and_prune() {
978 let aabbs = vec![unit_aabb(), shifted_aabb(0.5)];
979 let pairs = PairQuery::sweep_and_prune(&aabbs);
980 assert!(!pairs.is_empty());
981 }
982
983 #[test]
984 fn test_pair_query_aabb_tree() {
985 let aabbs = vec![unit_aabb(), shifted_aabb(0.5), shifted_aabb(10.0)];
986 let pairs = PairQuery::aabb_tree(&aabbs);
987 assert!(pairs.iter().any(|p| p.a == 0 && p.b == 1));
988 }
989
990 #[test]
991 fn test_frustum_culling_inside() {
992 let planes = vec![([1.0_f64, 0.0, 0.0], 10.0)];
994 let fc = FrustumCulling::new(planes);
995 let visible = fc.cull(&[unit_aabb()]);
996 assert!(visible.contains(&0));
997 }
998
999 #[test]
1000 fn test_frustum_culling_outside() {
1001 let planes = vec![([1.0_f64, 0.0, 0.0], -200.0)];
1003 let fc = FrustumCulling::new(planes);
1004 let visible = fc.cull(&[unit_aabb()]);
1005 assert!(visible.is_empty());
1006 }
1007
1008 #[test]
1009 fn test_sphere_query_hit() {
1010 let sq = SphereQuery::new([0.5, 0.5, 0.5], 0.1);
1011 let hits = sq.query_sorted(&[unit_aabb()]);
1012 assert!(hits.contains(&0));
1013 }
1014
1015 #[test]
1016 fn test_sphere_query_miss() {
1017 let sq = SphereQuery::new([10.0, 10.0, 10.0], 0.1);
1018 let hits = sq.query_sorted(&[unit_aabb()]);
1019 assert!(hits.is_empty());
1020 }
1021
1022 #[test]
1023 fn test_capsule_query_hit() {
1024 let cq = CapsuleQuery::new([0.5, 0.5, -1.0], [0.5, 0.5, 2.0], 0.1);
1025 let hits = cq.query(&[unit_aabb()]);
1026 assert!(hits.contains(&0));
1027 }
1028
1029 #[test]
1030 fn test_capsule_query_miss() {
1031 let cq = CapsuleQuery::new([10.0, 10.0, 10.0], [11.0, 11.0, 11.0], 0.1);
1032 let hits = cq.query(&[unit_aabb()]);
1033 assert!(hits.is_empty());
1034 }
1035
1036 #[test]
1037 fn test_point_query_nearest() {
1038 let aabbs = vec![unit_aabb(), shifted_aabb(5.0)];
1039 let pq = PointQuery::new([0.5, 0.5, 0.5]);
1040 let nearest = pq.nearest_n(&aabbs, 1);
1041 assert_eq!(nearest[0], 0);
1042 }
1043
1044 #[test]
1045 fn test_point_query_distance_inside() {
1046 let aabb = unit_aabb();
1047 let pq = PointQuery::new([0.5, 0.5, 0.5]);
1048 assert_eq!(pq.distance_to(&aabb), 0.0);
1049 }
1050
1051 #[test]
1052 fn test_convex_sweep_hit() {
1053 let cs = ConvexSweep::new(QueryAabb::new([-1.0; 3], [0.0; 3]), [2.0, 2.0, 2.0]);
1054 let hits = cs.query(&[unit_aabb()]);
1055 assert!(hits.contains(&0));
1056 }
1057
1058 #[test]
1059 fn test_convex_sweep_miss() {
1060 let cs = ConvexSweep::new(QueryAabb::new([-10.0; 3], [-9.0; 3]), [-1.0, 0.0, 0.0]);
1061 let hits = cs.query(&[unit_aabb()]);
1062 assert!(hits.is_empty());
1063 }
1064
1065 #[test]
1066 fn test_contact_pair_filter_passes() {
1067 let cf = ContactPairFilter::new(vec![0xF, 0xF], vec![0, 0]);
1068 assert!(cf.should_collide(0, 1));
1069 }
1070
1071 #[test]
1072 fn test_contact_pair_filter_blocks() {
1073 let cf = ContactPairFilter::new(vec![0x1, 0x2], vec![0, 0]);
1074 assert!(!cf.should_collide(0, 1));
1075 }
1076
1077 #[test]
1078 fn test_contact_pair_filter_filter() {
1079 let cf = ContactPairFilter::new(vec![0xF, 0xF, 0x1], vec![0; 3]);
1080 let pairs = vec![ShapePair::new(0, 1), ShapePair::new(0, 2)];
1081 let filtered = cf.filter(&pairs);
1082 assert_eq!(filtered.len(), 2);
1084 }
1085
1086 #[test]
1087 fn test_query_pipeline_flush() {
1088 let mut qp = QueryPipeline::new();
1089 qp.add_shape(unit_aabb());
1090 qp.submit(PendingQuery::AabbQuery(unit_aabb()));
1091 qp.flush();
1092 assert_eq!(qp.results.len(), 1);
1093 assert!(qp.results[0].hits.contains(&0));
1094 }
1095
1096 #[test]
1097 fn test_query_pipeline_sphere_query() {
1098 let mut qp = QueryPipeline::new();
1099 qp.add_shape(unit_aabb());
1100 let sq = SphereQuery::new([0.5, 0.5, 0.5], 0.5);
1101 qp.submit(PendingQuery::SphereQuery(sq));
1102 qp.flush();
1103 assert!(!qp.results.is_empty());
1104 }
1105
1106 #[test]
1107 fn test_query_pipeline_point_query() {
1108 let mut qp = QueryPipeline::new();
1109 qp.add_shape(unit_aabb());
1110 qp.add_shape(shifted_aabb(5.0));
1111 qp.submit(PendingQuery::PointQuery {
1112 point: [0.5, 0.5, 0.5],
1113 n: 1,
1114 });
1115 qp.flush();
1116 assert_eq!(qp.results[0].hits.len(), 1);
1117 assert_eq!(qp.results[0].hits[0], 0);
1118 }
1119
1120 #[test]
1121 fn test_sweep_aabb_motion_positive() {
1122 let aabb = unit_aabb();
1123 let swept = sweep_aabb_motion(&aabb, [2.0, 0.0, 0.0]);
1124 assert!((swept.max[0] - 3.0).abs() < 1e-12);
1125 assert!((swept.min[0] - 0.0).abs() < 1e-12);
1126 }
1127
1128 #[test]
1129 fn test_sweep_aabb_motion_negative() {
1130 let aabb = unit_aabb();
1131 let swept = sweep_aabb_motion(&aabb, [-1.0, 0.0, 0.0]);
1132 assert!((swept.min[0] - (-1.0)).abs() < 1e-12);
1133 }
1134
1135 #[test]
1136 fn test_aabb_overlap_function() {
1137 let a = unit_aabb();
1138 let b = shifted_aabb(0.5);
1139 assert!(aabb_overlap(&a, &b));
1140 }
1141}