use gizmo_math::{Aabb, Frustum, Intersection, Vec3};
pub const NO_KEY: u32 = u32::MAX;
const NIL: u32 = u32::MAX;
const ACCEPT: u8 = 0xFF;
#[derive(Clone, Copy)]
struct Node {
aabb: Aabb,
parent: u32,
left: u32,
right: u32,
key: u32,
height: i32,
}
impl Node {
#[inline]
fn is_leaf(&self) -> bool {
self.left == NIL
}
}
impl Default for Node {
fn default() -> Self {
Self {
aabb: Aabb::new(Vec3::ZERO, Vec3::ZERO),
parent: NIL,
left: NIL,
right: NIL,
key: NO_KEY,
height: -1,
}
}
}
#[inline]
fn surface_area(a: &Aabb) -> f32 {
let d = a.max - a.min;
2.0 * (d.x * d.y + d.y * d.z + d.z * d.x)
}
#[inline]
fn merge(a: &Aabb, b: &Aabb) -> Aabb {
Aabb {
min: a.min.min(b.min),
max: a.max.max(b.max),
}
}
#[inline]
fn fatten(a: &Aabb, margin: f32) -> Aabb {
let m = gizmo_math::Vec3A::splat(margin);
Aabb {
min: a.min - m,
max: a.max + m,
}
}
#[inline]
fn contains(outer: &Aabb, inner: &Aabb) -> bool {
inner.min.x >= outer.min.x
&& inner.min.y >= outer.min.y
&& inner.min.z >= outer.min.z
&& inner.max.x <= outer.max.x
&& inner.max.y <= outer.max.y
&& inner.max.z <= outer.max.z
}
#[inline]
fn overlaps(a: &Aabb, b: &Aabb) -> bool {
a.min.x <= b.max.x
&& b.min.x <= a.max.x
&& a.min.y <= b.max.y
&& b.min.y <= a.max.y
&& a.min.z <= b.max.z
&& b.min.z <= a.max.z
}
#[derive(Clone)]
pub struct RenderAabbTree {
nodes: Vec<Node>,
root: u32,
free_list: u32,
leaf_of: Vec<u32>,
keys: Vec<u32>,
key_pos: Vec<u32>,
fat_margin: f32,
}
impl Default for RenderAabbTree {
fn default() -> Self {
Self::new()
}
}
impl RenderAabbTree {
pub const DEFAULT_FAT_MARGIN: f32 = 1.0;
pub fn new() -> Self {
Self::with_fat_margin(Self::DEFAULT_FAT_MARGIN)
}
pub fn with_fat_margin(margin: f32) -> Self {
Self {
nodes: Vec::with_capacity(256),
root: NIL,
free_list: NIL,
leaf_of: Vec::new(),
keys: Vec::new(),
key_pos: Vec::new(),
fat_margin: if margin > 0.0 { margin } else { 0.0 },
}
}
#[inline]
pub fn fat_margin(&self) -> f32 {
self.fat_margin
}
pub fn clear(&mut self) {
self.nodes.clear();
self.root = NIL;
self.free_list = NIL;
for slot in &mut self.leaf_of {
*slot = NIL;
}
for slot in &mut self.key_pos {
*slot = NIL;
}
self.keys.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.keys.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
#[inline]
pub fn contains(&self, key: u32) -> bool {
self.leaf_of.get(key as usize).is_some_and(|&n| n != NIL)
}
#[inline]
pub fn keys(&self) -> &[u32] {
&self.keys
}
#[inline]
pub fn height(&self) -> u32 {
if self.root == NIL {
0
} else {
self.nodes[self.root as usize].height.max(0) as u32
}
}
fn alloc_node(&mut self) -> u32 {
if self.free_list != NIL {
let idx = self.free_list;
self.free_list = self.nodes[idx as usize].parent;
self.nodes[idx as usize] = Node::default();
idx
} else {
let idx = self.nodes.len() as u32;
assert!(idx != NIL, "RenderAabbTree: node slot overflow");
self.nodes.push(Node::default());
idx
}
}
fn free_node(&mut self, idx: u32) {
self.nodes[idx as usize] = Node {
parent: self.free_list,
height: -1,
..Default::default()
};
self.free_list = idx;
}
pub fn insert(&mut self, key: u32, world_aabb: Aabb) -> bool {
if key == NO_KEY {
debug_assert!(false, "RenderAabbTree: NO_KEY is reserved and cannot be indexed");
return false;
}
if world_aabb.is_empty() {
return false;
}
if let Some(&leaf) = self.leaf_of.get(key as usize) {
if leaf != NIL {
if contains(&self.nodes[leaf as usize].aabb, &world_aabb) {
return false;
}
self.remove(key);
}
}
let fat = fatten(&world_aabb, self.fat_margin);
let leaf = self.alloc_node();
self.nodes[leaf as usize].aabb = fat;
self.nodes[leaf as usize].key = key;
self.nodes[leaf as usize].height = 0;
self.insert_leaf(leaf);
let k = key as usize;
if self.leaf_of.len() <= k {
self.leaf_of.resize(k + 1, NIL);
self.key_pos.resize(k + 1, NIL);
}
self.leaf_of[k] = leaf;
self.key_pos[k] = self.keys.len() as u32;
self.keys.push(key);
true
}
pub fn remove(&mut self, key: u32) -> bool {
let k = key as usize;
let leaf = match self.leaf_of.get(k) {
Some(&l) if l != NIL => l,
_ => return false,
};
self.remove_leaf(leaf);
self.free_node(leaf);
self.leaf_of[k] = NIL;
let pos = self.key_pos[k] as usize;
self.key_pos[k] = NIL;
let last = self.keys.len() - 1;
self.keys.swap_remove(pos);
if pos != last {
let moved = self.keys[pos];
self.key_pos[moved as usize] = pos as u32;
}
true
}
pub fn retain(&mut self, keep: impl Fn(u32) -> bool) -> usize {
let stale: Vec<u32> = self.keys.iter().copied().filter(|&k| !keep(k)).collect();
for &k in &stale {
self.remove(k);
}
stale.len()
}
fn insert_leaf(&mut self, leaf: u32) {
if self.root == NIL {
self.root = leaf;
self.nodes[leaf as usize].parent = NIL;
return;
}
let leaf_aabb = self.nodes[leaf as usize].aabb;
let sibling = self.find_best_sibling(&leaf_aabb);
let old_parent = self.nodes[sibling as usize].parent;
let new_parent = self.alloc_node();
self.nodes[new_parent as usize].parent = old_parent;
self.nodes[new_parent as usize].aabb = merge(&leaf_aabb, &self.nodes[sibling as usize].aabb);
self.nodes[new_parent as usize].height = self.nodes[sibling as usize].height + 1;
self.nodes[new_parent as usize].left = sibling;
self.nodes[new_parent as usize].right = leaf;
if old_parent != NIL {
if self.nodes[old_parent as usize].left == sibling {
self.nodes[old_parent as usize].left = new_parent;
} else {
self.nodes[old_parent as usize].right = new_parent;
}
} else {
self.root = new_parent;
}
self.nodes[sibling as usize].parent = new_parent;
self.nodes[leaf as usize].parent = new_parent;
self.refit_ancestors(new_parent);
}
fn remove_leaf(&mut self, leaf: u32) {
if leaf == self.root {
self.root = NIL;
return;
}
let parent = self.nodes[leaf as usize].parent;
let sibling = if self.nodes[parent as usize].left == leaf {
self.nodes[parent as usize].right
} else {
self.nodes[parent as usize].left
};
let grand = self.nodes[parent as usize].parent;
if grand != NIL {
if self.nodes[grand as usize].left == parent {
self.nodes[grand as usize].left = sibling;
} else {
self.nodes[grand as usize].right = sibling;
}
self.nodes[sibling as usize].parent = grand;
self.free_node(parent);
self.refit_ancestors(grand);
} else {
self.root = sibling;
self.nodes[sibling as usize].parent = NIL;
self.free_node(parent);
}
}
fn refit_ancestors(&mut self, mut index: u32) {
while index != NIL {
let left = self.nodes[index as usize].left;
let right = self.nodes[index as usize].right;
self.nodes[index as usize].height =
1 + self.nodes[left as usize].height.max(self.nodes[right as usize].height);
self.nodes[index as usize].aabb =
merge(&self.nodes[left as usize].aabb, &self.nodes[right as usize].aabb);
index = self.balance(index);
index = self.nodes[index as usize].parent;
}
}
fn find_best_sibling(&self, leaf_aabb: &Aabb) -> u32 {
let mut index = self.root;
while !self.nodes[index as usize].is_leaf() {
let node = &self.nodes[index as usize];
let (c1, c2) = (node.left, node.right);
let area = surface_area(&node.aabb);
let combined = surface_area(&merge(&node.aabb, leaf_aabb));
let cost_here = 2.0 * combined;
let inheritance = 2.0 * (combined - area);
let descend_cost = |child: u32| {
let ch = &self.nodes[child as usize];
let merged = surface_area(&merge(leaf_aabb, &ch.aabb));
if ch.is_leaf() {
merged + inheritance
} else {
(merged - surface_area(&ch.aabb)) + inheritance
}
};
let cost1 = descend_cost(c1);
let cost2 = descend_cost(c2);
if cost_here < cost1 && cost_here < cost2 {
break;
}
index = if cost1 < cost2 { c1 } else { c2 };
}
index
}
fn balance(&mut self, a: u32) -> u32 {
if self.nodes[a as usize].is_leaf() || self.nodes[a as usize].height < 2 {
return a;
}
let b = self.nodes[a as usize].left;
let c = self.nodes[a as usize].right;
let bf = self.nodes[c as usize].height - self.nodes[b as usize].height;
if bf > 1 {
return self.rotate_left(a, b, c);
}
if bf < -1 {
return self.rotate_right(a, b, c);
}
a
}
fn rotate_left(&mut self, a: u32, b: u32, c: u32) -> u32 {
let f = self.nodes[c as usize].left;
let g = self.nodes[c as usize].right;
self.nodes[c as usize].left = a;
self.nodes[c as usize].parent = self.nodes[a as usize].parent;
self.nodes[a as usize].parent = c;
let cp = self.nodes[c as usize].parent;
if cp != NIL {
if self.nodes[cp as usize].left == a {
self.nodes[cp as usize].left = c;
} else {
self.nodes[cp as usize].right = c;
}
} else {
self.root = c;
}
let (up, down) = if self.nodes[f as usize].height > self.nodes[g as usize].height {
(f, g)
} else {
(g, f)
};
self.nodes[c as usize].right = up;
self.nodes[a as usize].right = down;
self.nodes[up as usize].parent = c;
self.nodes[down as usize].parent = a;
self.nodes[a as usize].aabb = merge(&self.nodes[b as usize].aabb, &self.nodes[down as usize].aabb);
self.nodes[c as usize].aabb = merge(&self.nodes[a as usize].aabb, &self.nodes[up as usize].aabb);
self.nodes[a as usize].height =
1 + self.nodes[b as usize].height.max(self.nodes[down as usize].height);
self.nodes[c as usize].height =
1 + self.nodes[a as usize].height.max(self.nodes[up as usize].height);
c
}
fn rotate_right(&mut self, a: u32, b: u32, c: u32) -> u32 {
let d = self.nodes[b as usize].left;
let e = self.nodes[b as usize].right;
self.nodes[b as usize].left = a;
self.nodes[b as usize].parent = self.nodes[a as usize].parent;
self.nodes[a as usize].parent = b;
let bp = self.nodes[b as usize].parent;
if bp != NIL {
if self.nodes[bp as usize].left == a {
self.nodes[bp as usize].left = b;
} else {
self.nodes[bp as usize].right = b;
}
} else {
self.root = b;
}
let (up, down) = if self.nodes[d as usize].height > self.nodes[e as usize].height {
(d, e)
} else {
(e, d)
};
self.nodes[b as usize].right = up;
self.nodes[a as usize].left = down;
self.nodes[up as usize].parent = b;
self.nodes[down as usize].parent = a;
self.nodes[a as usize].aabb = merge(&self.nodes[c as usize].aabb, &self.nodes[down as usize].aabb);
self.nodes[b as usize].aabb = merge(&self.nodes[a as usize].aabb, &self.nodes[up as usize].aabb);
self.nodes[a as usize].height =
1 + self.nodes[c as usize].height.max(self.nodes[down as usize].height);
self.nodes[b as usize].height =
1 + self.nodes[a as usize].height.max(self.nodes[up as usize].height);
b
}
pub fn query_frustum(&self, frustum: &Frustum, out: &mut Vec<u32>) {
if self.root == NIL {
return;
}
let mut stack: Vec<(u32, u8)> = Vec::with_capacity(2 * self.height() as usize + 4);
stack.push((self.root, Frustum::FULL_MASK));
while let Some((idx, mask)) = stack.pop() {
if mask == ACCEPT {
self.collect_subtree(idx, out, &mut stack);
continue;
}
let n = &self.nodes[idx as usize];
match frustum.test_aabb_masked(n.aabb, mask) {
(Intersection::Outside, _) => {}
(Intersection::Inside, _) => self.collect_subtree(idx, out, &mut stack),
(Intersection::Partial, reduced) => {
if n.is_leaf() {
out.push(n.key);
} else {
stack.push((n.left, reduced));
stack.push((n.right, reduced));
}
}
}
}
}
pub fn query_frustum_full_mask(&self, frustum: &Frustum, out: &mut Vec<u32>) {
if self.root == NIL {
return;
}
let mut stack: Vec<(u32, u8)> = Vec::with_capacity(2 * self.height() as usize + 4);
stack.push((self.root, Frustum::FULL_MASK));
while let Some((idx, mask)) = stack.pop() {
if mask == ACCEPT {
self.collect_subtree(idx, out, &mut stack);
continue;
}
let n = &self.nodes[idx as usize];
match frustum.test_aabb_masked(n.aabb, Frustum::FULL_MASK) {
(Intersection::Outside, _) => {}
(Intersection::Inside, _) => self.collect_subtree(idx, out, &mut stack),
(Intersection::Partial, _) => {
if n.is_leaf() {
out.push(n.key);
} else {
stack.push((n.left, Frustum::FULL_MASK));
stack.push((n.right, Frustum::FULL_MASK));
}
}
}
}
}
fn collect_subtree(&self, idx: u32, out: &mut Vec<u32>, stack: &mut Vec<(u32, u8)>) {
let n = &self.nodes[idx as usize];
if n.is_leaf() {
out.push(n.key);
} else {
stack.push((n.left, ACCEPT));
stack.push((n.right, ACCEPT));
}
}
pub fn query_frusta(&self, frusta: &[Frustum], out: &mut Vec<u32>) {
out.clear();
for f in frusta {
self.query_frustum(f, out);
}
out.sort_unstable();
out.dedup();
}
pub fn query_aabb(&self, aabb: &Aabb, out: &mut Vec<u32>) {
if self.root == NIL {
return;
}
let mut stack: Vec<u32> = Vec::with_capacity(2 * self.height() as usize + 4);
stack.push(self.root);
while let Some(idx) = stack.pop() {
let n = &self.nodes[idx as usize];
if !overlaps(&n.aabb, aabb) {
continue;
}
if n.is_leaf() {
out.push(n.key);
} else {
stack.push(n.left);
stack.push(n.right);
}
}
}
pub fn leaf_aabb(&self, key: u32) -> Option<Aabb> {
match self.leaf_of.get(key as usize) {
Some(&l) if l != NIL => Some(self.nodes[l as usize].aabb),
_ => None,
}
}
#[cfg(debug_assertions)]
pub fn validate(&self) {
assert_eq!(
self.keys.len(),
self.keys.iter().collect::<std::collections::HashSet<_>>().len(),
"duplicate key in the dense key list"
);
for (pos, &k) in self.keys.iter().enumerate() {
assert_eq!(self.key_pos[k as usize] as usize, pos, "key_pos disagrees for key {k}");
let leaf = self.leaf_of[k as usize];
assert!(leaf != NIL, "key {k} is listed live but has no leaf");
assert_eq!(self.nodes[leaf as usize].key, k, "leaf {leaf} holds the wrong key");
}
if self.root != NIL {
let seen = self.validate_node(self.root, NIL);
assert_eq!(seen, self.keys.len(), "reachable leaves != live keys");
} else {
assert!(self.keys.is_empty(), "empty tree with live keys");
}
}
#[cfg(debug_assertions)]
fn validate_node(&self, idx: u32, expected_parent: u32) -> usize {
let n = &self.nodes[idx as usize];
assert_eq!(n.parent, expected_parent, "node {idx}: wrong parent");
assert!(n.height >= 0, "node {idx}: live node on the free list");
if n.is_leaf() {
assert_eq!(n.height, 0, "leaf {idx}: height must be 0");
assert_ne!(n.key, NO_KEY, "leaf {idx}: no key");
assert_eq!(self.leaf_of[n.key as usize], idx, "leaf {idx}: leaf_of disagrees");
assert_eq!(n.right, NIL, "leaf {idx}: half a child");
return 1;
}
let (l, r) = (n.left, n.right);
assert!(l != NIL && r != NIL, "internal node {idx}: missing a child");
assert_eq!(n.key, NO_KEY, "internal node {idx}: carries a key");
assert_eq!(
n.height,
1 + self.nodes[l as usize].height.max(self.nodes[r as usize].height),
"node {idx}: wrong height"
);
for child in [l, r] {
assert!(
contains(&n.aabb, &self.nodes[child as usize].aabb),
"node {idx} does not enclose child {child}: a subtree can now report Outside \
while holding visible geometry"
);
}
self.validate_node(l, idx) + self.validate_node(r, idx)
}
#[cfg(test)]
fn corrupt_node_aabb(&mut self, key: u32, aabb: Aabb) {
let leaf = self.leaf_of[key as usize];
self.nodes[leaf as usize].aabb = aabb;
}
}
#[cfg(test)]
mod tests;