use crate::storage::data_structures::NodePoint;
use glam::Vec3;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OctreeQueryStats {
pub nodes_visited: usize,
pub leaf_nodes_scanned: usize,
pub points_tested: usize,
pub points_returned: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BoundingBox {
pub min: Vec3,
pub max: Vec3,
}
impl BoundingBox {
pub fn new(min: Vec3, max: Vec3) -> Self {
Self { min, max }
}
pub fn centered(center: Vec3, half_size: f32) -> Self {
Self {
min: center - Vec3::splat(half_size),
max: center + Vec3::splat(half_size),
}
}
pub fn contains(&self, point: Vec3) -> bool {
point.x >= self.min.x
&& point.x <= self.max.x
&& point.y >= self.min.y
&& point.y <= self.max.y
&& point.z >= self.min.z
&& point.z <= self.max.z
}
pub fn intersects(&self, other: &BoundingBox) -> bool {
self.min.x <= other.max.x
&& self.max.x >= other.min.x
&& self.min.y <= other.max.y
&& self.max.y >= other.min.y
&& self.min.z <= other.max.z
&& self.max.z >= other.min.z
}
pub fn center(&self) -> Vec3 {
(self.min + self.max) * 0.5
}
pub fn size(&self) -> Vec3 {
self.max - self.min
}
pub fn subdivide(&self) -> [BoundingBox; 8] {
let center = self.center();
let _half_size = (self.max - self.min) * 0.5;
[
BoundingBox::new(self.min, center), BoundingBox::new(
Vec3::new(center.x, self.min.y, self.min.z),
Vec3::new(self.max.x, center.y, center.z),
), BoundingBox::new(
Vec3::new(self.min.x, center.y, self.min.z),
Vec3::new(center.x, self.max.y, center.z),
), BoundingBox::new(
Vec3::new(center.x, center.y, self.min.z),
Vec3::new(self.max.x, self.max.y, center.z),
), BoundingBox::new(
Vec3::new(self.min.x, self.min.y, center.z),
Vec3::new(center.x, center.y, self.max.z),
), BoundingBox::new(
Vec3::new(center.x, self.min.y, center.z),
Vec3::new(self.max.x, center.y, self.max.z),
), BoundingBox::new(
Vec3::new(self.min.x, center.y, center.z),
Vec3::new(center.x, self.max.y, self.max.z),
), BoundingBox::new(center, self.max), ]
}
fn min_distance_sq(&self, point: Vec3) -> f32 {
let closest_x = point.x.clamp(self.min.x, self.max.x);
let closest_y = point.y.clamp(self.min.y, self.max.y);
let closest_z = point.z.clamp(self.min.z, self.max.z);
let closest_point = Vec3::new(closest_x, closest_y, closest_z);
(closest_point - point).length_squared()
}
}
#[derive(Debug, Clone)]
pub struct OctreeNode {
pub bounds: BoundingBox,
pub depth: u32,
pub nodes: Vec<NodePoint>,
pub children: Option<[Box<OctreeNode>; 8]>,
pub is_subdivided: bool,
}
impl OctreeNode {
pub fn new(bounds: BoundingBox, depth: u32) -> Self {
Self {
bounds,
depth,
nodes: Vec::new(),
children: None,
is_subdivided: false,
}
}
pub fn is_leaf(&self) -> bool {
!self.is_subdivided
}
pub fn capacity(&self) -> usize {
(8 + self.depth as usize * 2).min(32)
}
pub fn insert(&mut self, node: NodePoint) -> bool {
let point = Vec3::new(node.x, node.y, node.z);
let mut inserted = false;
if self.bounds.contains(point) {
if self.is_leaf() {
if self.nodes.len() < self.capacity() || self.depth >= 16 {
self.nodes.push(node);
inserted = true;
} else {
self.subdivide();
let mut i = 0;
while i < self.nodes.len() {
let existing_node = self.nodes[i];
let redistributed = self.insert_into_children(existing_node);
if redistributed {
self.nodes.swap_remove(i);
} else {
i += 1;
}
}
inserted = self.insert_into_children(node);
}
} else {
inserted = self.insert_into_children(node);
}
}
inserted
}
fn subdivide(&mut self) {
let child_bounds = self.bounds.subdivide();
let child_depth = self.depth + 1;
let children = [
Box::new(OctreeNode::new(child_bounds[0], child_depth)),
Box::new(OctreeNode::new(child_bounds[1], child_depth)),
Box::new(OctreeNode::new(child_bounds[2], child_depth)),
Box::new(OctreeNode::new(child_bounds[3], child_depth)),
Box::new(OctreeNode::new(child_bounds[4], child_depth)),
Box::new(OctreeNode::new(child_bounds[5], child_depth)),
Box::new(OctreeNode::new(child_bounds[6], child_depth)),
Box::new(OctreeNode::new(child_bounds[7], child_depth)),
];
self.children = Some(children);
self.is_subdivided = true;
}
fn insert_into_children(&mut self, node: NodePoint) -> bool {
if let Some(ref mut children) = self.children {
let point = Vec3::new(node.x, node.y, node.z);
for child in children.iter_mut() {
if child.bounds.contains(point) {
return child.insert(node);
}
}
}
false
}
pub fn query_sphere(&self, center: Vec3, radius: f32, results: &mut Vec<NodePoint>) {
let mut stats = OctreeQueryStats::default();
self.query_sphere_with_stats(center, radius, results, &mut stats);
}
pub fn query_sphere_with_stats(
&self,
center: Vec3,
radius: f32,
results: &mut Vec<NodePoint>,
stats: &mut OctreeQueryStats,
) {
stats.nodes_visited += 1;
if !self.intersects_sphere(center, radius) {
return;
}
if self.is_leaf() {
stats.leaf_nodes_scanned += 1;
for node in &self.nodes {
stats.points_tested += 1;
let node_pos = Vec3::new(node.x, node.y, node.z);
let distance_sq = (node_pos - center).length_squared();
if distance_sq <= radius * radius {
results.push(*node);
stats.points_returned += 1;
}
}
} else {
if let Some(ref children) = self.children {
for child in children {
child.query_sphere_with_stats(center, radius, results, stats);
}
}
}
}
fn intersects_sphere(&self, center: Vec3, radius: f32) -> bool {
let closest_x = center.x.clamp(self.bounds.min.x, self.bounds.max.x);
let closest_y = center.y.clamp(self.bounds.min.y, self.bounds.max.y);
let closest_z = center.z.clamp(self.bounds.min.z, self.bounds.max.z);
let closest_point = Vec3::new(closest_x, closest_y, closest_z);
let distance_sq = (closest_point - center).length_squared();
distance_sq <= radius * radius
}
pub fn node_count(&self) -> usize {
let mut count = self.nodes.len();
if let Some(ref children) = self.children {
for child in children {
count += child.node_count();
}
}
count
}
pub fn leaf_count(&self) -> usize {
if self.is_leaf() {
1
} else {
let mut count = 0;
if let Some(ref children) = self.children {
for child in children {
count += child.leaf_count();
}
}
count
}
}
pub fn max_depth(&self) -> u32 {
if self.is_leaf() {
self.depth
} else {
let mut max_child_depth = self.depth;
if let Some(ref children) = self.children {
for child in children {
max_child_depth = max_child_depth.max(child.max_depth());
}
}
max_child_depth
}
}
}
#[derive(Debug, Clone)]
pub struct Octree {
root: OctreeNode,
node_count: usize,
}
impl Octree {
pub fn new(bounds: BoundingBox) -> Self {
Self {
root: OctreeNode::new(bounds, 0),
node_count: 0,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut nodes = Vec::with_capacity(self.node_count);
self.collect_nodes(&self.root, &mut nodes);
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.root.bounds.min.x.to_le_bytes());
bytes.extend_from_slice(&self.root.bounds.min.y.to_le_bytes());
bytes.extend_from_slice(&self.root.bounds.min.z.to_le_bytes());
bytes.extend_from_slice(&self.root.bounds.max.x.to_le_bytes());
bytes.extend_from_slice(&self.root.bounds.max.y.to_le_bytes());
bytes.extend_from_slice(&self.root.bounds.max.z.to_le_bytes());
bytes.extend_from_slice(&nodes.len().to_le_bytes());
for node in nodes {
bytes.extend_from_slice(&node.id.to_le_bytes());
bytes.extend_from_slice(&node.x.to_le_bytes());
bytes.extend_from_slice(&node.y.to_le_bytes());
bytes.extend_from_slice(&node.z.to_le_bytes());
}
Ok(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
use std::convert::TryInto;
let mut offset = 0;
let min_x = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let min_y = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let min_z = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let max_x = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let max_y = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let max_z = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let bounds = BoundingBox::new(
Vec3::new(min_x, min_y, min_z),
Vec3::new(max_x, max_y, max_z),
);
let node_count = usize::from_le_bytes(bytes[offset..offset + 8].try_into()?);
offset += 8;
let mut nodes = Vec::with_capacity(node_count);
for _ in 0..node_count {
let id = u64::from_le_bytes(bytes[offset..offset + 8].try_into()?);
offset += 8;
let x = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let y = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
let z = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
offset += 4;
nodes.push(NodePoint { id, x, y, z });
}
let mut octree = Self::new(bounds);
for node in nodes {
octree.insert(node);
}
Ok(octree)
}
fn collect_nodes(&self, node: &OctreeNode, output: &mut Vec<NodePoint>) {
output.extend(&node.nodes);
if let Some(ref children) = node.children {
for child in children.iter() {
self.collect_nodes(child, output);
}
}
}
pub fn from_nodes(nodes: &[NodePoint]) -> Self {
if nodes.is_empty() {
return Self::new(BoundingBox::new(Vec3::ZERO, Vec3::ONE));
}
let mut min = Vec3::new(nodes[0].x, nodes[0].y, nodes[0].z);
let mut max = min;
for node in nodes {
let pos = Vec3::new(node.x, node.y, node.z);
min = min.min(pos);
max = max.max(pos);
}
let size = max - min;
let padding = size * 0.1;
let bounds = BoundingBox::new(min - padding, max + padding);
let mut octree = Self::new(bounds);
for &node in nodes {
octree.insert(node);
}
octree
}
pub fn insert(&mut self, node: NodePoint) -> bool {
if self.root.insert(node) {
self.node_count += 1;
true
} else {
false
}
}
pub fn query_sphere(&self, center: Vec3, radius: f32) -> Vec<NodePoint> {
let (results, _stats) = self.query_sphere_with_stats(center, radius);
results
}
pub fn query_sphere_with_stats(
&self,
center: Vec3,
radius: f32,
) -> (Vec<NodePoint>, OctreeQueryStats) {
let mut results = Vec::new();
let mut stats = OctreeQueryStats::default();
self.root
.query_sphere_with_stats(center, radius, &mut results, &mut stats);
(results, stats)
}
pub fn locate_within_distance(
&self,
center: NodePoint,
distance_squared: f32,
) -> impl Iterator<Item = NodePoint> {
let center_vec = Vec3::new(center.x, center.y, center.z);
let radius = distance_squared.sqrt();
self.query_sphere(center_vec, radius).into_iter()
}
pub fn query_aabb(&self, bounds: &BoundingBox) -> Vec<NodePoint> {
let center = bounds.center();
let size = bounds.size();
let radius = size.length() * 0.5;
let candidates = self.query_sphere(center, radius);
candidates
.into_iter()
.filter(|node| {
let pos = Vec3::new(node.x, node.y, node.z);
bounds.contains(pos)
})
.collect()
}
pub fn query_knn(&self, center: Vec3, k: usize) -> Vec<(NodePoint, f32)> {
self.query_knn_with_stats(center, k).0
}
pub fn query_knn_with_stats(
&self,
center: Vec3,
k: usize,
) -> (Vec<(NodePoint, f32)>, OctreeQueryStats) {
let mut stats = OctreeQueryStats::default();
if k == 0 || self.is_empty() {
return (Vec::new(), stats);
}
let results = query_knn_best_bin_first(&self.root, center, k, &mut stats);
(results, stats)
}
pub fn bounds(&self) -> &BoundingBox {
&self.root.bounds
}
pub fn len(&self) -> usize {
self.node_count
}
pub fn is_empty(&self) -> bool {
self.node_count == 0
}
pub fn statistics(&self) -> OctreeStats {
OctreeStats {
node_count: self.node_count,
leaf_count: self.root.leaf_count(),
max_depth: self.root.max_depth(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct OctreeStats {
pub node_count: usize,
pub leaf_count: usize,
pub max_depth: u32,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct OrdF32(u32);
impl OrdF32 {
fn new(v: f32) -> Self {
Self(v.to_bits())
}
}
impl PartialOrd for OrdF32 {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrdF32 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
fn query_knn_best_bin_first(
root: &OctreeNode,
center: Vec3,
k: usize,
stats: &mut OctreeQueryStats,
) -> Vec<(NodePoint, f32)> {
use std::cmp::Reverse;
use std::collections::BinaryHeap;
let mut queue: BinaryHeap<Reverse<(OrdF32, *const OctreeNode)>> = BinaryHeap::new();
queue.push(Reverse((
OrdF32::new(root.bounds.min_distance_sq(center)),
root as *const OctreeNode,
)));
stats.nodes_visited += 1;
let mut best: BinaryHeap<(OrdF32, NodePoint)> = BinaryHeap::with_capacity(k);
while let Some(Reverse((min_dist_sq, node_ptr))) = queue.pop() {
let node = unsafe { &*node_ptr };
if best.len() == k && min_dist_sq > best.peek().unwrap().0 {
break;
}
if node.is_leaf() {
stats.leaf_nodes_scanned += 1;
for point in &node.nodes {
stats.points_tested += 1;
let point_dist_sq =
(Vec3::new(point.x, point.y, point.z) - center).length_squared();
let ord_dist = OrdF32::new(point_dist_sq);
if best.len() < k {
best.push((ord_dist, *point));
} else if ord_dist < best.peek().unwrap().0 {
best.pop();
best.push((ord_dist, *point));
}
}
} else if let Some(ref children) = node.children {
for child in children.iter() {
queue.push(Reverse((
OrdF32::new(child.bounds.min_distance_sq(center)),
child.as_ref() as *const OctreeNode,
)));
stats.nodes_visited += 1;
}
}
}
stats.points_returned = best.len();
let mut results: Vec<(NodePoint, f32)> = best
.into_iter()
.map(|(d, p)| (p, f32::from_bits(d.0)))
.collect();
results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
results
}
#[cfg(test)]
mod tests;