#[allow(unused_imports)]
use super::functions::*;
#[allow(unused_imports)]
use super::functions_2::*;
use crate::types::CollisionPair;
use oxiphysics_core::Aabb;
use oxiphysics_core::math::{Real, Vec3};
pub struct BruteForceBroadPhase;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ObjectType {
Static,
Dynamic,
Kinematic,
}
#[derive(Debug, Clone)]
pub(super) enum BvhNodeData {
Internal { left: usize, right: usize },
Leaf { object_index: usize },
}
#[derive(Debug, Default)]
#[allow(dead_code)]
pub struct BroadphaseSceneGraph {
pub(super) nodes: Vec<SceneGraphNode>,
}
#[allow(dead_code)]
impl BroadphaseSceneGraph {
pub fn new() -> Self {
Self { nodes: Vec::new() }
}
pub fn add_node(&mut self, aabb: Aabb, parent: Option<usize>) -> usize {
let idx = self.nodes.len();
if let Some(p) = parent
&& p < self.nodes.len()
{
self.nodes[p].children.push(idx);
}
self.nodes.push(SceneGraphNode {
aabb,
parent,
children: Vec::new(),
active: true,
});
idx
}
pub fn remove_node(&mut self, idx: usize) {
if idx < self.nodes.len() {
self.nodes[idx].active = false;
}
}
pub fn update_aabb(&mut self, idx: usize, aabb: Aabb) {
if idx < self.nodes.len() {
self.nodes[idx].aabb = aabb;
}
}
pub fn node_count(&self) -> usize {
self.nodes.iter().filter(|n| n.active).count()
}
pub fn children_of(&self, parent: usize) -> Vec<usize> {
if parent < self.nodes.len() {
self.nodes[parent]
.children
.iter()
.copied()
.filter(|&c| self.nodes.get(c).is_some_and(|n| n.active))
.collect()
} else {
Vec::new()
}
}
fn active_aabbs(&self) -> (Vec<Aabb>, Vec<usize>) {
let mut aabbs = Vec::new();
let mut indices = Vec::new();
for (i, node) in self.nodes.iter().enumerate() {
if node.active {
aabbs.push(node.aabb.clone());
indices.push(i);
}
}
(aabbs, indices)
}
pub fn find_pairs_bvh(&self) -> Vec<CollisionPair> {
let (aabbs, node_indices) = self.active_aabbs();
let mut bvh = BvhBroadphase::new();
bvh.build(&aabbs);
let raw_pairs = bvh.find_all_pairs(&aabbs);
raw_pairs
.iter()
.map(|p| {
let a = node_indices[p.a];
let b = node_indices[p.b];
if a < b {
CollisionPair::new(a, b)
} else {
CollisionPair::new(b, a)
}
})
.collect()
}
pub fn query_aabb(&self, query: &Aabb) -> Vec<usize> {
let (aabbs, node_indices) = self.active_aabbs();
let mut bvh = BvhBroadphase::new();
bvh.build(&aabbs);
bvh.query(query).iter().map(|&i| node_indices[i]).collect()
}
}
#[derive(Debug, Default)]
#[allow(dead_code)]
pub struct BroadphaseProfiler {
pub(super) last_elapsed_ns: u64,
pub(super) call_count: u64,
pub(super) total_elapsed_ns: u64,
}
#[allow(dead_code)]
impl BroadphaseProfiler {
pub fn new() -> Self {
Self::default()
}
pub fn profile_sap(&mut self, aabbs: &[Aabb], axis: usize) -> Vec<CollisionPair> {
let start = std::time::Instant::now();
let result = SweepAndPrune::new(axis).find_pairs(aabbs);
let elapsed = start.elapsed().as_nanos() as u64;
self.last_elapsed_ns = elapsed;
self.total_elapsed_ns += elapsed;
self.call_count += 1;
result
}
pub fn profile_bvh(&mut self, aabbs: &[Aabb]) -> Vec<CollisionPair> {
let start = std::time::Instant::now();
let mut bvh = BvhBroadphase::new();
bvh.build(aabbs);
let result = bvh.find_all_pairs(aabbs);
let elapsed = start.elapsed().as_nanos() as u64;
self.last_elapsed_ns = elapsed;
self.total_elapsed_ns += elapsed;
self.call_count += 1;
result
}
pub fn last_elapsed_ns(&self) -> u64 {
self.last_elapsed_ns
}
pub fn call_count(&self) -> u64 {
self.call_count
}
pub fn avg_elapsed_ns(&self) -> f64 {
if self.call_count == 0 {
0.0
} else {
self.total_elapsed_ns as f64 / self.call_count as f64
}
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Frustum {
pub planes: [(Vec3, Real); 6],
}
#[allow(dead_code)]
impl Frustum {
pub fn new(planes: [(Vec3, Real); 6]) -> Self {
Self { planes }
}
pub fn from_perspective(fov_y_rad: Real, aspect: Real, near: Real, far: Real) -> Self {
let half_fov = fov_y_rad * 0.5;
let tan_half = half_fov.tan();
let near_plane = (Vec3::new(0.0, 0.0, -1.0), near);
let far_plane = (Vec3::new(0.0, 0.0, 1.0), -far);
let cos_fov = (1.0 / (1.0 + tan_half * tan_half)).sqrt();
let sin_fov = tan_half * cos_fov;
let top_plane = (Vec3::new(0.0, -cos_fov, -sin_fov), 0.0);
let bottom_plane = (Vec3::new(0.0, cos_fov, -sin_fov), 0.0);
let tan_half_x = tan_half * aspect;
let cos_fov_x = (1.0 / (1.0 + tan_half_x * tan_half_x)).sqrt();
let sin_fov_x = tan_half_x * cos_fov_x;
let left_plane = (Vec3::new(cos_fov_x, 0.0, -sin_fov_x), 0.0);
let right_plane = (Vec3::new(-cos_fov_x, 0.0, -sin_fov_x), 0.0);
Self {
planes: [
near_plane,
far_plane,
top_plane,
bottom_plane,
left_plane,
right_plane,
],
}
}
pub fn contains_aabb(&self, aabb: &Aabb) -> bool {
for &(ref normal, offset) in &self.planes {
let px = if normal.x >= 0.0 {
aabb.max.x
} else {
aabb.min.x
};
let py = if normal.y >= 0.0 {
aabb.max.y
} else {
aabb.min.y
};
let pz = if normal.z >= 0.0 {
aabb.max.z
} else {
aabb.min.z
};
let p = Vec3::new(px, py, pz);
if p.dot(normal) < offset {
return false;
}
}
true
}
}
#[allow(dead_code)]
pub struct DynamicAabbTree {
pub(super) tight_aabbs: Vec<Aabb>,
pub(super) fat_aabbs: Vec<Aabb>,
pub(super) margin: Real,
pub(super) bvh: BvhBroadphase,
pub(super) dirty: bool,
}
#[allow(dead_code)]
impl DynamicAabbTree {
pub fn new(margin: Real) -> Self {
Self {
tight_aabbs: Vec::new(),
fat_aabbs: Vec::new(),
margin,
bvh: BvhBroadphase::new(),
dirty: true,
}
}
pub fn insert(&mut self, aabb: Aabb) -> usize {
let idx = self.tight_aabbs.len();
let fat = self.fatten(&aabb);
self.tight_aabbs.push(aabb);
self.fat_aabbs.push(fat);
self.dirty = true;
idx
}
pub fn update(&mut self, index: usize, aabb: Aabb) -> bool {
self.tight_aabbs[index] = aabb.clone();
if !self.fat_aabbs[index].contains_aabb(&aabb) {
self.fat_aabbs[index] = self.fatten(&aabb);
self.dirty = true;
return true;
}
false
}
pub fn rebuild_if_dirty(&mut self) {
if self.dirty {
self.bvh.build(&self.fat_aabbs);
self.dirty = false;
}
}
pub fn find_pairs(&mut self) -> Vec<CollisionPair> {
self.rebuild_if_dirty();
let mut pairs = Vec::new();
for i in 0..self.tight_aabbs.len() {
let hits = self.bvh.query(&self.tight_aabbs[i]);
for j in hits {
if j > i && self.tight_aabbs[i].intersects(&self.tight_aabbs[j]) {
pairs.push(CollisionPair::new(i, j));
}
}
}
pairs
}
pub fn query(&mut self, aabb: &Aabb) -> Vec<usize> {
self.rebuild_if_dirty();
self.bvh.query(aabb)
}
pub fn len(&self) -> usize {
self.tight_aabbs.len()
}
pub fn is_empty(&self) -> bool {
self.tight_aabbs.is_empty()
}
fn fatten(&self, aabb: &Aabb) -> Aabb {
let m = Vec3::new(self.margin, self.margin, self.margin);
Aabb::new(aabb.min - m, aabb.max + m)
}
}
#[allow(dead_code)]
impl DynamicAabbTree {
pub fn mark_dirty(&mut self) {
self.dirty = true;
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn find_proximity_pairs(&mut self, inflation: Real) -> Vec<CollisionPair> {
self.rebuild_if_dirty();
let n = self.tight_aabbs.len();
let mut pairs = Vec::new();
for i in 0..n {
let inflated = inflate_aabb(&self.tight_aabbs[i], inflation);
let hits = self.bvh.query(&inflated);
for j in hits {
if j > i {
let inflated_j = inflate_aabb(&self.tight_aabbs[j], inflation);
if inflated.intersects(&inflated_j) {
pairs.push(CollisionPair::new(i, j));
}
}
}
}
pairs
}
pub fn batch_insert(&mut self, aabbs: &[Aabb]) {
for aabb in aabbs {
let fat = self.fatten(aabb);
self.tight_aabbs.push(aabb.clone());
self.fat_aabbs.push(fat);
}
self.dirty = true;
}
pub fn validate(&mut self) -> bool {
self.rebuild_if_dirty();
self.bvh.validate()
}
pub fn tree_info(&self) -> (usize, bool) {
(self.tight_aabbs.len(), self.dirty)
}
pub fn clear(&mut self) {
self.tight_aabbs.clear();
self.fat_aabbs.clear();
self.bvh = BvhBroadphase::new();
self.dirty = false;
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GpuBroadphaseHints {
pub n_objects: usize,
pub scene_min: [Real; 3],
pub scene_max: [Real; 3],
pub avg_half_extent: Real,
pub max_half_extent: Real,
pub recommended_threads: usize,
}
#[allow(dead_code)]
impl GpuBroadphaseHints {
pub fn from_aabbs(aabbs: &[Aabb]) -> Self {
if aabbs.is_empty() {
return Self {
n_objects: 0,
scene_min: [0.0; 3],
scene_max: [0.0; 3],
avg_half_extent: 0.0,
max_half_extent: 0.0,
recommended_threads: 64,
};
}
let mut scene_min = [Real::MAX; 3];
let mut scene_max = [Real::MIN; 3];
let mut sum_extent = 0.0_f64;
let mut max_extent = 0.0_f64;
for aabb in aabbs {
for i in 0..3 {
if aabb.min[i] < scene_min[i] {
scene_min[i] = aabb.min[i];
}
if aabb.max[i] > scene_max[i] {
scene_max[i] = aabb.max[i];
}
}
let half = aabb.half_extents();
let extent = half.x.max(half.y).max(half.z);
sum_extent += extent;
if extent > max_extent {
max_extent = extent;
}
}
let avg_half_extent = sum_extent / aabbs.len() as f64;
let recommended_threads = aabbs.len().next_power_of_two().max(64);
Self {
n_objects: aabbs.len(),
scene_min,
scene_max,
avg_half_extent,
max_half_extent: max_extent,
recommended_threads,
}
}
pub fn suggested_cell_size(&self) -> Real {
if self.n_objects == 0 {
return 1.0;
}
(self.avg_half_extent * 2.0).max(1e-6)
}
pub fn estimated_cells(&self) -> usize {
if self.n_objects == 0 {
return 0;
}
let cell = self.suggested_cell_size();
let mut count = 1usize;
for i in 0..3 {
let extent = (self.scene_max[i] - self.scene_min[i]).max(0.0);
count *= ((extent / cell).ceil() as usize).max(1);
}
count
}
pub fn to_flat_buffer(aabbs: &[Aabb]) -> Vec<f32> {
let mut buf = Vec::with_capacity(aabbs.len() * 6);
for aabb in aabbs {
buf.push(aabb.min.x as f32);
buf.push(aabb.min.y as f32);
buf.push(aabb.min.z as f32);
buf.push(aabb.max.x as f32);
buf.push(aabb.max.y as f32);
buf.push(aabb.max.z as f32);
}
buf
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BvhQualityMetrics {
pub sah_cost: f64,
pub max_depth: usize,
pub avg_leaf_depth: f64,
pub leaf_count: usize,
pub internal_count: usize,
}
#[derive(Debug, Clone)]
pub(super) struct BvhNode {
pub(super) aabb: Aabb,
pub(super) parent: Option<usize>,
pub(super) data: BvhNodeData,
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct BroadphaseStats {
pub num_objects: usize,
pub num_pairs: usize,
pub num_tests: usize,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SceneGraphNode {
pub aabb: Aabb,
pub parent: Option<usize>,
pub children: Vec<usize>,
pub active: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct PairCountHistogram {
pub static_static: usize,
pub static_dynamic: usize,
pub static_kinematic: usize,
pub dynamic_dynamic: usize,
pub dynamic_kinematic: usize,
pub kinematic_kinematic: usize,
}
impl PairCountHistogram {
#[allow(dead_code)]
pub fn total(&self) -> usize {
self.static_static
+ self.static_dynamic
+ self.static_kinematic
+ self.dynamic_dynamic
+ self.dynamic_kinematic
+ self.kinematic_kinematic
}
}
pub struct BvhBroadphase {
pub(super) nodes: Vec<BvhNode>,
pub(super) root: Option<usize>,
pub(super) object_to_node: Vec<Option<usize>>,
}
impl BvhBroadphase {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
root: None,
object_to_node: Vec::new(),
}
}
pub fn build(&mut self, aabbs: &[Aabb]) {
self.nodes.clear();
self.object_to_node.clear();
self.object_to_node.resize(aabbs.len(), None);
if aabbs.is_empty() {
self.root = None;
return;
}
let indices: Vec<usize> = (0..aabbs.len()).collect();
self.root = Some(self.build_recursive(aabbs, &indices));
}
fn build_recursive(&mut self, aabbs: &[Aabb], indices: &[usize]) -> usize {
if indices.len() == 1 {
let idx = indices[0];
let node_idx = self.nodes.len();
self.nodes.push(BvhNode {
aabb: aabbs[idx].clone(),
parent: None,
data: BvhNodeData::Leaf { object_index: idx },
});
self.object_to_node[idx] = Some(node_idx);
return node_idx;
}
let mut combined = aabbs[indices[0]].clone();
for &i in &indices[1..] {
combined = combined.merge(&aabbs[i]);
}
let extent = combined.half_extents();
let split_axis = if extent.x >= extent.y && extent.x >= extent.z {
0
} else if extent.y >= extent.z {
1
} else {
2
};
let mut sorted = indices.to_vec();
sorted.sort_by(|&a, &b| {
aabbs[a].center()[split_axis]
.partial_cmp(&aabbs[b].center()[split_axis])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mid = sorted.len() / 2;
let left = self.build_recursive(aabbs, &sorted[..mid]);
let right = self.build_recursive(aabbs, &sorted[mid..]);
let node_idx = self.nodes.len();
self.nodes.push(BvhNode {
aabb: combined,
parent: None,
data: BvhNodeData::Internal { left, right },
});
self.nodes[left].parent = Some(node_idx);
self.nodes[right].parent = Some(node_idx);
node_idx
}
pub fn find_all_pairs(&self, aabbs: &[Aabb]) -> Vec<CollisionPair> {
let mut pairs = Vec::new();
if self.root.is_none() {
return pairs;
}
let leaves: Vec<(usize, usize)> = self
.nodes
.iter()
.enumerate()
.filter_map(|(node_idx, node)| {
if let BvhNodeData::Leaf { object_index } = node.data {
Some((node_idx, object_index))
} else {
None
}
})
.collect();
for (i, &(_, obj_a)) in leaves.iter().enumerate() {
for &(_, obj_b) in &leaves[(i + 1)..] {
if aabbs[obj_a].intersects(&aabbs[obj_b]) {
pairs.push(CollisionPair::new(obj_a, obj_b));
}
}
}
pairs
}
pub fn query(&self, aabb: &Aabb) -> Vec<usize> {
let mut results = Vec::new();
if let Some(root) = self.root {
self.query_recursive(root, aabb, &mut results);
}
results
}
fn query_recursive(&self, node_idx: usize, aabb: &Aabb, results: &mut Vec<usize>) {
let node = &self.nodes[node_idx];
if !node.aabb.intersects(aabb) {
return;
}
match node.data {
BvhNodeData::Leaf { object_index } => {
results.push(object_index);
}
BvhNodeData::Internal { left, right } => {
self.query_recursive(left, aabb, results);
self.query_recursive(right, aabb, results);
}
}
}
pub fn ray_query(&self, ray_origin: &Vec3, ray_direction: &Vec3, max_toi: Real) -> Vec<usize> {
let mut results = Vec::new();
if let Some(root) = self.root {
self.ray_query_recursive(root, ray_origin, ray_direction, max_toi, &mut results);
}
results
}
fn ray_query_recursive(
&self,
node_idx: usize,
ray_origin: &Vec3,
ray_direction: &Vec3,
max_toi: Real,
results: &mut Vec<usize>,
) {
let node = &self.nodes[node_idx];
if !ray_aabb_intersect(ray_origin, ray_direction, &node.aabb, max_toi) {
return;
}
match node.data {
BvhNodeData::Leaf { object_index } => {
results.push(object_index);
}
BvhNodeData::Internal { left, right } => {
self.ray_query_recursive(left, ray_origin, ray_direction, max_toi, results);
self.ray_query_recursive(right, ray_origin, ray_direction, max_toi, results);
}
}
}
}
#[allow(dead_code)]
impl BvhBroadphase {
pub fn quality_metrics(&self) -> Option<BvhQualityMetrics> {
let root = self.root?;
let root_sa = self.nodes[root].aabb.surface_area();
if root_sa <= 0.0 {
return None;
}
let mut sah = 0.0_f64;
let mut max_depth = 0usize;
let mut total_leaf_depth = 0usize;
let mut leaf_count = 0usize;
let mut internal_count = 0usize;
let mut stack: Vec<(usize, usize)> = vec![(root, 0)];
while let Some((idx, depth)) = stack.pop() {
if depth > max_depth {
max_depth = depth;
}
match self.nodes[idx].data {
BvhNodeData::Leaf { .. } => {
leaf_count += 1;
total_leaf_depth += depth;
}
BvhNodeData::Internal { left, right } => {
internal_count += 1;
sah += self.nodes[idx].aabb.surface_area() / root_sa;
stack.push((left, depth + 1));
stack.push((right, depth + 1));
}
}
}
let avg_leaf_depth = if leaf_count > 0 {
total_leaf_depth as f64 / leaf_count as f64
} else {
0.0
};
Some(BvhQualityMetrics {
sah_cost: sah,
max_depth,
avg_leaf_depth,
leaf_count,
internal_count,
})
}
pub fn rebuild_if_degraded(&mut self, aabbs: &[Aabb], threshold: f64) -> bool {
let degraded = match self.quality_metrics() {
Some(q) => q.sah_cost > threshold,
None => false,
};
if degraded {
self.build(aabbs);
}
degraded
}
pub fn sah_cost(&self) -> f64 {
self.quality_metrics().map(|q| q.sah_cost).unwrap_or(0.0)
}
pub fn validate(&self) -> bool {
match self.root {
None => true,
Some(root) => {
if self.nodes[root].parent.is_some() {
return false;
}
self.validate_node(root)
}
}
}
fn validate_node(&self, idx: usize) -> bool {
match self.nodes[idx].data {
BvhNodeData::Leaf { .. } => true,
BvhNodeData::Internal { left, right } => {
if self.nodes[left].parent != Some(idx) {
return false;
}
if self.nodes[right].parent != Some(idx) {
return false;
}
if !self.nodes[idx].aabb.contains_aabb(&self.nodes[left].aabb) {
return false;
}
if !self.nodes[idx].aabb.contains_aabb(&self.nodes[right].aabb) {
return false;
}
self.validate_node(left) && self.validate_node(right)
}
}
}
pub fn ray_query_iterative(
&self,
ray_origin: &Vec3,
ray_direction: &Vec3,
max_toi: Real,
) -> Vec<usize> {
let mut results = Vec::new();
let Some(root) = self.root else {
return results;
};
let mut stack = vec![root];
while let Some(idx) = stack.pop() {
let node = &self.nodes[idx];
if !ray_aabb_intersect(ray_origin, ray_direction, &node.aabb, max_toi) {
continue;
}
match node.data {
BvhNodeData::Leaf { object_index } => results.push(object_index),
BvhNodeData::Internal { left, right } => {
stack.push(left);
stack.push(right);
}
}
}
results
}
pub fn frustum_query_iterative(&self, planes: &[(Vec3, Real); 6]) -> Vec<usize> {
let mut results = Vec::new();
let Some(root) = self.root else {
return results;
};
let mut stack = vec![root];
while let Some(idx) = stack.pop() {
let node = &self.nodes[idx];
if !aabb_in_frustum(&node.aabb, planes) {
continue;
}
match node.data {
BvhNodeData::Leaf { object_index } => results.push(object_index),
BvhNodeData::Internal { left, right } => {
stack.push(left);
stack.push(right);
}
}
}
results
}
pub fn batch_insert(&mut self, aabbs: &[Aabb]) {
self.build(aabbs);
}
}
pub struct SweepAndPrune {
pub(super) axis: usize,
}
impl SweepAndPrune {
pub fn new(axis: usize) -> Self {
assert!(axis < 3, "axis must be 0, 1, or 2");
Self { axis }
}
pub fn x_axis() -> Self {
Self::new(0)
}
}
#[derive(Debug, Default)]
#[allow(dead_code)]
pub struct BroadphaseWarmstart {
pub(super) cached_pairs: Vec<CollisionPair>,
}
#[allow(dead_code)]
impl BroadphaseWarmstart {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, pairs: &[CollisionPair]) {
self.cached_pairs = pairs.to_vec();
}
pub fn cached_pair_count(&self) -> usize {
self.cached_pairs.len()
}
pub fn filter_still_overlapping(&self, aabbs: &[Aabb]) -> Vec<CollisionPair> {
self.cached_pairs
.iter()
.filter(|p| {
let max_idx = aabbs.len();
p.a < max_idx && p.b < max_idx && aabbs[p.a].intersects(&aabbs[p.b])
})
.copied()
.collect()
}
pub fn merge_with_new(
&self,
new_pairs: &[CollisionPair],
aabbs: &[Aabb],
) -> Vec<CollisionPair> {
let mut merged: Vec<CollisionPair> = self.filter_still_overlapping(aabbs);
for &p in new_pairs {
if !merged.iter().any(|e| e.a == p.a && e.b == p.b) {
merged.push(p);
}
}
merged
}
pub fn clear(&mut self) {
self.cached_pairs.clear();
}
pub fn broadphase_with_warmstart(&mut self, aabbs: &[Aabb], axis: usize) -> Vec<CollisionPair> {
let fresh = SweepAndPrune::new(axis).find_pairs(aabbs);
let merged = self.merge_with_new(&fresh, aabbs);
self.update(&merged);
merged
}
}