1use crate::types::CollisionPair;
6use oxiphysics_core::Aabb;
7use oxiphysics_core::math::{Real, Vec3};
8
9use super::functions::{BroadPhase, aabb_in_frustum, inflate_aabb, ray_aabb_intersect};
10
11#[inline]
13fn aabb_contains_aabb(outer: &Aabb, inner: &Aabb) -> bool {
14 outer.min.x <= inner.min.x
15 && outer.min.y <= inner.min.y
16 && outer.min.z <= inner.min.z
17 && inner.max.x <= outer.max.x
18 && inner.max.y <= outer.max.y
19 && inner.max.z <= outer.max.z
20}
21
22pub struct BruteForceBroadPhase;
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum ObjectType {
27 Static,
29 Dynamic,
31 Kinematic,
33}
34#[derive(Debug, Clone)]
36pub(super) enum BvhNodeData {
37 Internal { left: usize, right: usize },
39 Leaf { object_index: usize },
41}
42#[derive(Debug, Default)]
45pub struct BroadphaseSceneGraph {
46 pub(super) nodes: Vec<SceneGraphNode>,
47}
48impl BroadphaseSceneGraph {
49 pub fn new() -> Self {
51 Self { nodes: Vec::new() }
52 }
53 pub fn add_node(&mut self, aabb: Aabb, parent: Option<usize>) -> usize {
55 let idx = self.nodes.len();
56 if let Some(p) = parent
57 && p < self.nodes.len()
58 {
59 self.nodes[p].children.push(idx);
60 }
61 self.nodes.push(SceneGraphNode {
62 aabb,
63 parent,
64 children: Vec::new(),
65 active: true,
66 });
67 idx
68 }
69 pub fn remove_node(&mut self, idx: usize) {
71 if idx < self.nodes.len() {
72 self.nodes[idx].active = false;
73 }
74 }
75 pub fn update_aabb(&mut self, idx: usize, aabb: Aabb) {
77 if idx < self.nodes.len() {
78 self.nodes[idx].aabb = aabb;
79 }
80 }
81 pub fn node_count(&self) -> usize {
83 self.nodes.iter().filter(|n| n.active).count()
84 }
85 pub fn children_of(&self, parent: usize) -> Vec<usize> {
87 if parent < self.nodes.len() {
88 self.nodes[parent]
89 .children
90 .iter()
91 .copied()
92 .filter(|&c| self.nodes.get(c).is_some_and(|n| n.active))
93 .collect()
94 } else {
95 Vec::new()
96 }
97 }
98 fn active_aabbs(&self) -> (Vec<Aabb>, Vec<usize>) {
100 let mut aabbs = Vec::new();
101 let mut indices = Vec::new();
102 for (i, node) in self.nodes.iter().enumerate() {
103 if node.active {
104 aabbs.push(node.aabb.clone());
105 indices.push(i);
106 }
107 }
108 (aabbs, indices)
109 }
110 pub fn find_pairs_bvh(&self) -> Vec<CollisionPair> {
112 let (aabbs, node_indices) = self.active_aabbs();
113 let mut bvh = BvhBroadphase::new();
114 bvh.build(&aabbs);
115 let raw_pairs = bvh.find_all_pairs(&aabbs);
116 raw_pairs
117 .iter()
118 .map(|p| {
119 let a = node_indices[p.a];
120 let b = node_indices[p.b];
121 if a < b {
122 CollisionPair::new(a, b)
123 } else {
124 CollisionPair::new(b, a)
125 }
126 })
127 .collect()
128 }
129 pub fn query_aabb(&self, query: &Aabb) -> Vec<usize> {
131 let (aabbs, node_indices) = self.active_aabbs();
132 let mut bvh = BvhBroadphase::new();
133 bvh.build(&aabbs);
134 bvh.query(query).iter().map(|&i| node_indices[i]).collect()
135 }
136}
137#[derive(Debug, Default)]
139pub struct BroadphaseProfiler {
140 pub(super) last_elapsed_ns: u64,
142 pub(super) call_count: u64,
144 pub(super) total_elapsed_ns: u64,
146}
147impl BroadphaseProfiler {
148 pub fn new() -> Self {
150 Self::default()
151 }
152 pub fn profile_sap(&mut self, aabbs: &[Aabb], axis: usize) -> Vec<CollisionPair> {
154 let start = std::time::Instant::now();
155 let result = SweepAndPrune::new(axis).find_pairs(aabbs);
156 let elapsed = start.elapsed().as_nanos() as u64;
157 self.last_elapsed_ns = elapsed;
158 self.total_elapsed_ns += elapsed;
159 self.call_count += 1;
160 result
161 }
162 pub fn profile_bvh(&mut self, aabbs: &[Aabb]) -> Vec<CollisionPair> {
164 let start = std::time::Instant::now();
165 let mut bvh = BvhBroadphase::new();
166 bvh.build(aabbs);
167 let result = bvh.find_all_pairs(aabbs);
168 let elapsed = start.elapsed().as_nanos() as u64;
169 self.last_elapsed_ns = elapsed;
170 self.total_elapsed_ns += elapsed;
171 self.call_count += 1;
172 result
173 }
174 pub fn last_elapsed_ns(&self) -> u64 {
176 self.last_elapsed_ns
177 }
178 pub fn call_count(&self) -> u64 {
180 self.call_count
181 }
182 pub fn avg_elapsed_ns(&self) -> f64 {
184 if self.call_count == 0 {
185 0.0
186 } else {
187 self.total_elapsed_ns as f64 / self.call_count as f64
188 }
189 }
190 pub fn reset(&mut self) {
192 *self = Self::default();
193 }
194}
195#[derive(Debug, Clone)]
201pub struct Frustum {
202 pub planes: [(Vec3, Real); 6],
204}
205impl Frustum {
206 pub fn new(planes: [(Vec3, Real); 6]) -> Self {
208 Self { planes }
209 }
210 pub fn from_perspective(fov_y_rad: Real, aspect: Real, near: Real, far: Real) -> Self {
215 let half_fov = fov_y_rad * 0.5;
216 let tan_half = half_fov.tan();
217 let near_plane = (Vec3::new(0.0, 0.0, -1.0), near);
218 let far_plane = (Vec3::new(0.0, 0.0, 1.0), -far);
219 let cos_fov = (1.0 / (1.0 + tan_half * tan_half)).sqrt();
220 let sin_fov = tan_half * cos_fov;
221 let top_plane = (Vec3::new(0.0, -cos_fov, -sin_fov), 0.0);
222 let bottom_plane = (Vec3::new(0.0, cos_fov, -sin_fov), 0.0);
223 let tan_half_x = tan_half * aspect;
224 let cos_fov_x = (1.0 / (1.0 + tan_half_x * tan_half_x)).sqrt();
225 let sin_fov_x = tan_half_x * cos_fov_x;
226 let left_plane = (Vec3::new(cos_fov_x, 0.0, -sin_fov_x), 0.0);
227 let right_plane = (Vec3::new(-cos_fov_x, 0.0, -sin_fov_x), 0.0);
228 Self {
229 planes: [
230 near_plane,
231 far_plane,
232 top_plane,
233 bottom_plane,
234 left_plane,
235 right_plane,
236 ],
237 }
238 }
239 pub fn contains_aabb(&self, aabb: &Aabb) -> bool {
243 for &(ref normal, offset) in &self.planes {
244 let px = if normal.x >= 0.0 {
245 aabb.max.x
246 } else {
247 aabb.min.x
248 };
249 let py = if normal.y >= 0.0 {
250 aabb.max.y
251 } else {
252 aabb.min.y
253 };
254 let pz = if normal.z >= 0.0 {
255 aabb.max.z
256 } else {
257 aabb.min.z
258 };
259 let p = Vec3::new(px, py, pz);
260 if p.dot(normal) < offset {
261 return false;
262 }
263 }
264 true
265 }
266}
267pub struct DynamicAabbTree {
272 pub(super) tight_aabbs: Vec<Aabb>,
274 pub(super) fat_aabbs: Vec<Aabb>,
276 pub(super) margin: Real,
278 pub(super) bvh: BvhBroadphase,
280 pub(super) dirty: bool,
282}
283impl DynamicAabbTree {
284 pub fn new(margin: Real) -> Self {
286 Self {
287 tight_aabbs: Vec::new(),
288 fat_aabbs: Vec::new(),
289 margin,
290 bvh: BvhBroadphase::new(),
291 dirty: true,
292 }
293 }
294 pub fn insert(&mut self, aabb: Aabb) -> usize {
296 let idx = self.tight_aabbs.len();
297 let fat = self.fatten(&aabb);
298 self.tight_aabbs.push(aabb);
299 self.fat_aabbs.push(fat);
300 self.dirty = true;
301 idx
302 }
303 pub fn update(&mut self, index: usize, aabb: Aabb) -> bool {
307 self.tight_aabbs[index] = aabb.clone();
308 if !aabb_contains_aabb(&self.fat_aabbs[index], &aabb) {
309 self.fat_aabbs[index] = self.fatten(&aabb);
310 self.dirty = true;
311 return true;
312 }
313 false
314 }
315 pub fn rebuild_if_dirty(&mut self) {
317 if self.dirty {
318 self.bvh.build(&self.fat_aabbs);
319 self.dirty = false;
320 }
321 }
322 pub fn find_pairs(&mut self) -> Vec<CollisionPair> {
324 self.rebuild_if_dirty();
325 let mut pairs = Vec::new();
326 for i in 0..self.tight_aabbs.len() {
327 let hits = self.bvh.query(&self.tight_aabbs[i]);
328 for j in hits {
329 if j > i && self.tight_aabbs[i].intersects(&self.tight_aabbs[j]) {
330 pairs.push(CollisionPair::new(i, j));
331 }
332 }
333 }
334 pairs
335 }
336 pub fn query(&mut self, aabb: &Aabb) -> Vec<usize> {
338 self.rebuild_if_dirty();
339 self.bvh.query(aabb)
340 }
341 pub fn len(&self) -> usize {
343 self.tight_aabbs.len()
344 }
345 pub fn is_empty(&self) -> bool {
347 self.tight_aabbs.is_empty()
348 }
349 fn fatten(&self, aabb: &Aabb) -> Aabb {
350 let m = Vec3::new(self.margin, self.margin, self.margin);
351 Aabb::new(aabb.min - m, aabb.max + m)
352 }
353}
354impl DynamicAabbTree {
355 pub fn mark_dirty(&mut self) {
357 self.dirty = true;
358 }
359 pub fn is_dirty(&self) -> bool {
361 self.dirty
362 }
363 pub fn find_proximity_pairs(&mut self, inflation: Real) -> Vec<CollisionPair> {
368 self.rebuild_if_dirty();
369 let n = self.tight_aabbs.len();
370 let mut pairs = Vec::new();
371 for i in 0..n {
372 let inflated = inflate_aabb(&self.tight_aabbs[i], inflation);
373 let hits = self.bvh.query(&inflated);
374 for j in hits {
375 if j > i {
376 let inflated_j = inflate_aabb(&self.tight_aabbs[j], inflation);
377 if inflated.intersects(&inflated_j) {
378 pairs.push(CollisionPair::new(i, j));
379 }
380 }
381 }
382 }
383 pairs
384 }
385 pub fn batch_insert(&mut self, aabbs: &[Aabb]) {
390 for aabb in aabbs {
391 let fat = self.fatten(aabb);
392 self.tight_aabbs.push(aabb.clone());
393 self.fat_aabbs.push(fat);
394 }
395 self.dirty = true;
396 }
397 pub fn validate(&mut self) -> bool {
401 self.rebuild_if_dirty();
402 self.bvh.validate()
403 }
404 pub fn tree_info(&self) -> (usize, bool) {
406 (self.tight_aabbs.len(), self.dirty)
407 }
408 pub fn clear(&mut self) {
410 self.tight_aabbs.clear();
411 self.fat_aabbs.clear();
412 self.bvh = BvhBroadphase::new();
413 self.dirty = false;
414 }
415}
416#[derive(Debug, Clone)]
421pub struct GpuBroadphaseHints {
422 pub n_objects: usize,
424 pub scene_min: [Real; 3],
426 pub scene_max: [Real; 3],
428 pub avg_half_extent: Real,
430 pub max_half_extent: Real,
432 pub recommended_threads: usize,
434}
435impl GpuBroadphaseHints {
436 pub fn from_aabbs(aabbs: &[Aabb]) -> Self {
438 if aabbs.is_empty() {
439 return Self {
440 n_objects: 0,
441 scene_min: [0.0; 3],
442 scene_max: [0.0; 3],
443 avg_half_extent: 0.0,
444 max_half_extent: 0.0,
445 recommended_threads: 64,
446 };
447 }
448 let mut scene_min = [Real::MAX; 3];
449 let mut scene_max = [Real::MIN; 3];
450 let mut sum_extent = 0.0_f64;
451 let mut max_extent = 0.0_f64;
452 for aabb in aabbs {
453 for i in 0..3 {
454 if aabb.min[i] < scene_min[i] {
455 scene_min[i] = aabb.min[i];
456 }
457 if aabb.max[i] > scene_max[i] {
458 scene_max[i] = aabb.max[i];
459 }
460 }
461 let half = aabb.half_extents();
462 let extent = half.x.max(half.y).max(half.z);
463 sum_extent += extent;
464 if extent > max_extent {
465 max_extent = extent;
466 }
467 }
468 let avg_half_extent = sum_extent / aabbs.len() as f64;
469 let recommended_threads = aabbs.len().next_power_of_two().max(64);
470 Self {
471 n_objects: aabbs.len(),
472 scene_min,
473 scene_max,
474 avg_half_extent,
475 max_half_extent: max_extent,
476 recommended_threads,
477 }
478 }
479 pub fn suggested_cell_size(&self) -> Real {
481 if self.n_objects == 0 {
482 return 1.0;
483 }
484 (self.avg_half_extent * 2.0).max(1e-6)
485 }
486 pub fn estimated_cells(&self) -> usize {
488 if self.n_objects == 0 {
489 return 0;
490 }
491 let cell = self.suggested_cell_size();
492 let mut count = 1usize;
493 for i in 0..3 {
494 let extent = (self.scene_max[i] - self.scene_min[i]).max(0.0);
495 count *= ((extent / cell).ceil() as usize).max(1);
496 }
497 count
498 }
499 pub fn to_flat_buffer(aabbs: &[Aabb]) -> Vec<f32> {
502 let mut buf = Vec::with_capacity(aabbs.len() * 6);
503 for aabb in aabbs {
504 buf.push(aabb.min.x as f32);
505 buf.push(aabb.min.y as f32);
506 buf.push(aabb.min.z as f32);
507 buf.push(aabb.max.x as f32);
508 buf.push(aabb.max.y as f32);
509 buf.push(aabb.max.z as f32);
510 }
511 buf
512 }
513}
514#[derive(Debug, Clone, Default)]
516pub struct BvhQualityMetrics {
517 pub sah_cost: f64,
519 pub max_depth: usize,
521 pub avg_leaf_depth: f64,
523 pub leaf_count: usize,
525 pub internal_count: usize,
527}
528#[derive(Debug, Clone)]
530pub(super) struct BvhNode {
531 pub(super) aabb: Aabb,
532 pub(super) parent: Option<usize>,
533 pub(super) data: BvhNodeData,
534}
535#[derive(Debug, Clone, Default)]
537pub struct BroadphaseStats {
538 pub num_objects: usize,
540 pub num_pairs: usize,
542 pub num_tests: usize,
544}
545#[derive(Debug, Clone)]
547pub struct SceneGraphNode {
548 pub aabb: Aabb,
550 pub parent: Option<usize>,
552 pub children: Vec<usize>,
554 pub active: bool,
556}
557#[derive(Debug, Clone, Default)]
559pub struct PairCountHistogram {
560 pub static_static: usize,
562 pub static_dynamic: usize,
564 pub static_kinematic: usize,
566 pub dynamic_dynamic: usize,
568 pub dynamic_kinematic: usize,
570 pub kinematic_kinematic: usize,
572}
573impl PairCountHistogram {
574 pub fn total(&self) -> usize {
576 self.static_static
577 + self.static_dynamic
578 + self.static_kinematic
579 + self.dynamic_dynamic
580 + self.dynamic_kinematic
581 + self.kinematic_kinematic
582 }
583}
584pub struct BvhBroadphase {
590 pub(super) nodes: Vec<BvhNode>,
591 pub(super) root: Option<usize>,
592 pub(super) object_to_node: Vec<Option<usize>>,
594}
595impl BvhBroadphase {
596 pub fn new() -> Self {
598 Self {
599 nodes: Vec::new(),
600 root: None,
601 object_to_node: Vec::new(),
602 }
603 }
604 pub fn build(&mut self, aabbs: &[Aabb]) {
606 self.nodes.clear();
607 self.object_to_node.clear();
608 self.object_to_node.resize(aabbs.len(), None);
609 if aabbs.is_empty() {
610 self.root = None;
611 return;
612 }
613 let indices: Vec<usize> = (0..aabbs.len()).collect();
614 self.root = Some(self.build_recursive(aabbs, &indices));
615 }
616 fn build_recursive(&mut self, aabbs: &[Aabb], indices: &[usize]) -> usize {
617 if indices.len() == 1 {
618 let idx = indices[0];
619 let node_idx = self.nodes.len();
620 self.nodes.push(BvhNode {
621 aabb: aabbs[idx].clone(),
622 parent: None,
623 data: BvhNodeData::Leaf { object_index: idx },
624 });
625 self.object_to_node[idx] = Some(node_idx);
626 return node_idx;
627 }
628 let mut combined = aabbs[indices[0]].clone();
629 for &i in &indices[1..] {
630 combined = combined.merge(&aabbs[i]);
631 }
632 let extent = combined.half_extents();
633 let split_axis = if extent.x >= extent.y && extent.x >= extent.z {
634 0
635 } else if extent.y >= extent.z {
636 1
637 } else {
638 2
639 };
640 let mut sorted = indices.to_vec();
641 sorted.sort_by(|&a, &b| {
642 aabbs[a].center()[split_axis]
643 .partial_cmp(&aabbs[b].center()[split_axis])
644 .unwrap_or(std::cmp::Ordering::Equal)
645 });
646 let mid = sorted.len() / 2;
647 let left = self.build_recursive(aabbs, &sorted[..mid]);
648 let right = self.build_recursive(aabbs, &sorted[mid..]);
649 let node_idx = self.nodes.len();
650 self.nodes.push(BvhNode {
651 aabb: combined,
652 parent: None,
653 data: BvhNodeData::Internal { left, right },
654 });
655 self.nodes[left].parent = Some(node_idx);
656 self.nodes[right].parent = Some(node_idx);
657 node_idx
658 }
659 pub fn find_all_pairs(&self, aabbs: &[Aabb]) -> Vec<CollisionPair> {
661 let mut pairs = Vec::new();
662 if self.root.is_none() {
663 return pairs;
664 }
665 let leaves: Vec<(usize, usize)> = self
666 .nodes
667 .iter()
668 .enumerate()
669 .filter_map(|(node_idx, node)| {
670 if let BvhNodeData::Leaf { object_index } = node.data {
671 Some((node_idx, object_index))
672 } else {
673 None
674 }
675 })
676 .collect();
677 for (i, &(_, obj_a)) in leaves.iter().enumerate() {
678 for &(_, obj_b) in &leaves[(i + 1)..] {
679 if aabbs[obj_a].intersects(&aabbs[obj_b]) {
680 pairs.push(CollisionPair::new(obj_a, obj_b));
681 }
682 }
683 }
684 pairs
685 }
686 pub fn query(&self, aabb: &Aabb) -> Vec<usize> {
688 let mut results = Vec::new();
689 if let Some(root) = self.root {
690 self.query_recursive(root, aabb, &mut results);
691 }
692 results
693 }
694 fn query_recursive(&self, node_idx: usize, aabb: &Aabb, results: &mut Vec<usize>) {
695 let node = &self.nodes[node_idx];
696 if !node.aabb.intersects(aabb) {
697 return;
698 }
699 match node.data {
700 BvhNodeData::Leaf { object_index } => {
701 results.push(object_index);
702 }
703 BvhNodeData::Internal { left, right } => {
704 self.query_recursive(left, aabb, results);
705 self.query_recursive(right, aabb, results);
706 }
707 }
708 }
709 pub fn ray_query(&self, ray_origin: &Vec3, ray_direction: &Vec3, max_toi: Real) -> Vec<usize> {
711 let mut results = Vec::new();
712 if let Some(root) = self.root {
713 self.ray_query_recursive(root, ray_origin, ray_direction, max_toi, &mut results);
714 }
715 results
716 }
717 fn ray_query_recursive(
718 &self,
719 node_idx: usize,
720 ray_origin: &Vec3,
721 ray_direction: &Vec3,
722 max_toi: Real,
723 results: &mut Vec<usize>,
724 ) {
725 let node = &self.nodes[node_idx];
726 if !ray_aabb_intersect(ray_origin, ray_direction, &node.aabb, max_toi) {
727 return;
728 }
729 match node.data {
730 BvhNodeData::Leaf { object_index } => {
731 results.push(object_index);
732 }
733 BvhNodeData::Internal { left, right } => {
734 self.ray_query_recursive(left, ray_origin, ray_direction, max_toi, results);
735 self.ray_query_recursive(right, ray_origin, ray_direction, max_toi, results);
736 }
737 }
738 }
739}
740impl BvhBroadphase {
741 pub fn quality_metrics(&self) -> Option<BvhQualityMetrics> {
745 let root = self.root?;
746 let root_sa = self.nodes[root].aabb.surface_area();
747 if root_sa <= 0.0 {
748 return None;
749 }
750 let mut sah = 0.0_f64;
751 let mut max_depth = 0usize;
752 let mut total_leaf_depth = 0usize;
753 let mut leaf_count = 0usize;
754 let mut internal_count = 0usize;
755 let mut stack: Vec<(usize, usize)> = vec![(root, 0)];
756 while let Some((idx, depth)) = stack.pop() {
757 if depth > max_depth {
758 max_depth = depth;
759 }
760 match self.nodes[idx].data {
761 BvhNodeData::Leaf { .. } => {
762 leaf_count += 1;
763 total_leaf_depth += depth;
764 }
765 BvhNodeData::Internal { left, right } => {
766 internal_count += 1;
767 sah += self.nodes[idx].aabb.surface_area() / root_sa;
768 stack.push((left, depth + 1));
769 stack.push((right, depth + 1));
770 }
771 }
772 }
773 let avg_leaf_depth = if leaf_count > 0 {
774 total_leaf_depth as f64 / leaf_count as f64
775 } else {
776 0.0
777 };
778 Some(BvhQualityMetrics {
779 sah_cost: sah,
780 max_depth,
781 avg_leaf_depth,
782 leaf_count,
783 internal_count,
784 })
785 }
786 pub fn rebuild_if_degraded(&mut self, aabbs: &[Aabb], threshold: f64) -> bool {
790 let degraded = match self.quality_metrics() {
791 Some(q) => q.sah_cost > threshold,
792 None => false,
793 };
794 if degraded {
795 self.build(aabbs);
796 }
797 degraded
798 }
799 pub fn sah_cost(&self) -> f64 {
801 self.quality_metrics().map(|q| q.sah_cost).unwrap_or(0.0)
802 }
803 pub fn validate(&self) -> bool {
807 match self.root {
808 None => true,
809 Some(root) => {
810 if self.nodes[root].parent.is_some() {
811 return false;
812 }
813 self.validate_node(root)
814 }
815 }
816 }
817 fn validate_node(&self, idx: usize) -> bool {
818 match self.nodes[idx].data {
819 BvhNodeData::Leaf { .. } => true,
820 BvhNodeData::Internal { left, right } => {
821 if self.nodes[left].parent != Some(idx) {
822 return false;
823 }
824 if self.nodes[right].parent != Some(idx) {
825 return false;
826 }
827 if !aabb_contains_aabb(&self.nodes[idx].aabb, &self.nodes[left].aabb) {
828 return false;
829 }
830 if !aabb_contains_aabb(&self.nodes[idx].aabb, &self.nodes[right].aabb) {
831 return false;
832 }
833 self.validate_node(left) && self.validate_node(right)
834 }
835 }
836 }
837 pub fn ray_query_iterative(
841 &self,
842 ray_origin: &Vec3,
843 ray_direction: &Vec3,
844 max_toi: Real,
845 ) -> Vec<usize> {
846 let mut results = Vec::new();
847 let Some(root) = self.root else {
848 return results;
849 };
850 let mut stack = vec![root];
851 while let Some(idx) = stack.pop() {
852 let node = &self.nodes[idx];
853 if !ray_aabb_intersect(ray_origin, ray_direction, &node.aabb, max_toi) {
854 continue;
855 }
856 match node.data {
857 BvhNodeData::Leaf { object_index } => results.push(object_index),
858 BvhNodeData::Internal { left, right } => {
859 stack.push(left);
860 stack.push(right);
861 }
862 }
863 }
864 results
865 }
866 pub fn frustum_query_iterative(&self, planes: &[(Vec3, Real); 6]) -> Vec<usize> {
871 let mut results = Vec::new();
872 let Some(root) = self.root else {
873 return results;
874 };
875 let mut stack = vec![root];
876 while let Some(idx) = stack.pop() {
877 let node = &self.nodes[idx];
878 if !aabb_in_frustum(&node.aabb, planes) {
879 continue;
880 }
881 match node.data {
882 BvhNodeData::Leaf { object_index } => results.push(object_index),
883 BvhNodeData::Internal { left, right } => {
884 stack.push(left);
885 stack.push(right);
886 }
887 }
888 }
889 results
890 }
891 pub fn batch_insert(&mut self, aabbs: &[Aabb]) {
895 self.build(aabbs);
896 }
897}
898pub struct SweepAndPrune {
900 pub(super) axis: usize,
901}
902impl SweepAndPrune {
903 pub fn new(axis: usize) -> Self {
905 assert!(axis < 3, "axis must be 0, 1, or 2");
906 Self { axis }
907 }
908 pub fn x_axis() -> Self {
910 Self::new(0)
911 }
912}
913#[derive(Debug, Default)]
921pub struct BroadphaseWarmstart {
922 pub(super) cached_pairs: Vec<CollisionPair>,
923}
924impl BroadphaseWarmstart {
925 pub fn new() -> Self {
927 Self::default()
928 }
929 pub fn update(&mut self, pairs: &[CollisionPair]) {
931 self.cached_pairs = pairs.to_vec();
932 }
933 pub fn cached_pair_count(&self) -> usize {
935 self.cached_pairs.len()
936 }
937 pub fn filter_still_overlapping(&self, aabbs: &[Aabb]) -> Vec<CollisionPair> {
939 self.cached_pairs
940 .iter()
941 .filter(|p| {
942 let max_idx = aabbs.len();
943 p.a < max_idx && p.b < max_idx && aabbs[p.a].intersects(&aabbs[p.b])
944 })
945 .copied()
946 .collect()
947 }
948 pub fn merge_with_new(
952 &self,
953 new_pairs: &[CollisionPair],
954 aabbs: &[Aabb],
955 ) -> Vec<CollisionPair> {
956 let mut merged: Vec<CollisionPair> = self.filter_still_overlapping(aabbs);
957 for &p in new_pairs {
958 if !merged.iter().any(|e| e.a == p.a && e.b == p.b) {
959 merged.push(p);
960 }
961 }
962 merged
963 }
964 pub fn clear(&mut self) {
966 self.cached_pairs.clear();
967 }
968 pub fn broadphase_with_warmstart(&mut self, aabbs: &[Aabb], axis: usize) -> Vec<CollisionPair> {
972 let fresh = SweepAndPrune::new(axis).find_pairs(aabbs);
973 let merged = self.merge_with_new(&fresh, aabbs);
974 self.update(&merged);
975 merged
976 }
977}