use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::fmt;
use std::sync::OnceLock;
use crate::Point3D;
pub(crate) type Aabb = [f64; 6];
pub(crate) fn box_distance(p: Point3D, b: &Aabb) -> f64 {
let gap = |x: f64, lo: f64, hi: f64| (lo - x).max(x - hi).max(0.0);
let (dx, dy, dz) = (
gap(p.x, b[0], b[3]),
gap(p.y, b[1], b[4]),
gap(p.z, b[2], b[5]),
);
(dx * dx + dy * dy + dz * dz).sqrt()
}
#[derive(Clone)]
pub(crate) struct BoxTree {
nodes: Vec<Node>,
}
#[derive(Clone)]
struct Node {
bbox: Aabb,
kind: Kind,
}
#[derive(Clone, Copy)]
enum Kind {
Leaf(usize),
Split(usize, usize),
}
impl BoxTree {
pub(crate) fn new(leaves: &[Aabb], cols: usize) -> Self {
debug_assert!(!leaves.is_empty() && leaves.len().is_multiple_of(cols));
let mut nodes = Vec::with_capacity(2 * leaves.len());
build(
leaves,
cols,
(0, leaves.len() / cols),
(0, cols),
&mut nodes,
);
BoxTree { nodes }
}
pub(crate) fn bounds(&self) -> &Aabb {
&self.nodes[0].bbox
}
pub(crate) fn nearest_first(&self, point: Point3D, mut visit: impl FnMut(usize) -> f64) {
let mut best = f64::INFINITY;
let leaves = self.nodes.len().div_ceil(2);
let mut heap = BinaryHeap::with_capacity(leaves.min(64));
heap.push(Pending {
lower: box_distance(point, &self.nodes[0].bbox),
node: 0,
});
while let Some(Pending { lower, node }) = heap.pop() {
if lower >= best {
break;
}
match self.nodes[node].kind {
Kind::Split(left, right) => {
for child in [left, right] {
heap.push(Pending {
lower: box_distance(point, &self.nodes[child].bbox),
node: child,
});
}
}
Kind::Leaf(k) => best = visit(k),
}
}
}
pub(crate) fn overlapping(&self, b: &Aabb, gap: f64, mut visit: impl FnMut(usize)) {
let mut stack = vec![0];
while let Some(node) = stack.pop() {
if boxes_apart(&self.nodes[node].bbox, b, gap) {
continue;
}
match self.nodes[node].kind {
Kind::Split(left, right) => stack.extend([right, left]),
Kind::Leaf(k) => visit(k),
}
}
}
}
pub(crate) fn boxes_apart(a: &Aabb, b: &Aabb, gap: f64) -> bool {
(0..3).any(|c| a[c] > b[c + 3] + gap || b[c] > a[c + 3] + gap)
}
fn build(
leaves: &[Aabb],
cols: usize,
rows: (usize, usize),
cols_range: (usize, usize),
nodes: &mut Vec<Node>,
) -> usize {
let at = nodes.len();
if rows.1 - rows.0 == 1 && cols_range.1 - cols_range.0 == 1 {
let k = rows.0 * cols + cols_range.0;
nodes.push(Node {
bbox: leaves[k],
kind: Kind::Leaf(k),
});
return at;
}
nodes.push(Node {
bbox: [0.0; 6],
kind: Kind::Split(0, 0),
});
let (first, second) = if rows.1 - rows.0 >= cols_range.1 - cols_range.0 {
let mid = (rows.0 + rows.1) / 2;
(((rows.0, mid), cols_range), ((mid, rows.1), cols_range))
} else {
let mid = (cols_range.0 + cols_range.1) / 2;
((rows, (cols_range.0, mid)), (rows, (mid, cols_range.1)))
};
let left = build(leaves, cols, first.0, first.1, nodes);
let right = build(leaves, cols, second.0, second.1, nodes);
let (a, b) = (nodes[left].bbox, nodes[right].bbox);
nodes[at] = Node {
bbox: std::array::from_fn(|i| {
if i < 3 {
a[i].min(b[i])
} else {
a[i].max(b[i])
}
}),
kind: Kind::Split(left, right),
};
at
}
struct Pending {
lower: f64,
node: usize,
}
impl PartialEq for Pending {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Pending {}
impl PartialOrd for Pending {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Pending {
fn cmp(&self, other: &Self) -> Ordering {
other.lower.total_cmp(&self.lower)
}
}
#[derive(Clone)]
pub(crate) struct Lazy<T>(OnceLock<Box<T>>);
impl<T> Lazy<T> {
pub(crate) fn get_or_init(&self, init: impl FnOnce() -> T) -> &T {
self.0.get_or_init(|| Box::new(init()))
}
}
impl<T> Default for Lazy<T> {
fn default() -> Self {
Lazy(OnceLock::new())
}
}
impl<T> PartialEq for Lazy<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T> fmt::Debug for Lazy<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Lazy")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nearest_first_visits_by_box_distance_and_prunes() {
let leaves: Vec<Aabb> = (0..4)
.map(|i| {
let x = 2.0 * i as f64;
[x, 0.0, 0.0, x + 1.0, 1.0, 1.0]
})
.collect();
let tree = BoxTree::new(&leaves, 4);
let mut seen = Vec::new();
tree.nearest_first(Point3D::new(10.0, 0.5, 0.5), |k| {
seen.push(k);
box_distance(Point3D::new(10.0, 0.5, 0.5), &leaves[k])
});
assert_eq!(seen, vec![3]);
let mut all = Vec::new();
tree.nearest_first(Point3D::new(10.0, 0.5, 0.5), |k| {
all.push(k);
f64::INFINITY
});
assert_eq!(all, vec![3, 2, 1, 0]);
}
#[test]
fn test_overlapping_visits_only_boxes_within_the_gap() {
let leaves: Vec<Aabb> = (0..6)
.map(|k| {
let (x, y) = (2.0 * (k / 3) as f64, 2.0 * (k % 3) as f64);
[x, y, 0.0, x + 1.0, y + 1.0, 1.0]
})
.collect();
let tree = BoxTree::new(&leaves, 3);
let query = [1.2, 1.2, 0.5, 1.8, 2.5, 0.6];
let mut seen = Vec::new();
tree.overlapping(&query, 0.0, |k| seen.push(k));
assert!(seen.is_empty());
tree.overlapping(&query, 0.25, |k| seen.push(k));
seen.sort();
assert_eq!(seen, vec![0, 1, 3, 4]);
let mut all = Vec::new();
tree.overlapping(tree.bounds(), 0.0, |k| all.push(k));
all.sort();
assert_eq!(all, (0..6).collect::<Vec<_>>());
}
#[test]
fn test_box_distance() {
let b = [0.0, 0.0, 0.0, 1.0, 1.0, 1.0];
assert_eq!(box_distance(Point3D::new(0.5, 0.5, 0.5), &b), 0.0);
assert_eq!(box_distance(Point3D::new(4.0, 5.0, 0.5), &b), 5.0);
}
}