use kiddo::{ImmutableKdTree, SquaredEuclidean};
use nalgebra::Vector3;
use rigidity_core::{Neighbor, NeighborSearch, PointCloud, neighbors::compare_neighbors};
use std::num::NonZeroUsize;
#[derive(Debug, thiserror::Error)]
pub enum SpatialError {
#[error("kd-tree construction failed: {0}")]
Construction(String),
}
pub struct KdTree {
tree: ImmutableKdTree<f64, 3>,
len: usize,
}
impl KdTree {
pub fn build(cloud: &PointCloud) -> Result<Self, SpatialError> {
Self::build_observed(cloud, |_, _| {})
}
pub fn build_observed<F>(cloud: &PointCloud, mut progress: F) -> Result<Self, SpatialError>
where
F: FnMut(usize, usize),
{
const PHASES: usize = 2;
let entries: Vec<[f64; 3]> = (0..cloud.len())
.map(|i| {
let p = cloud.point(i);
[p.x, p.y, p.z]
})
.collect();
progress(1, PHASES);
let tree = ImmutableKdTree::new_from_slice(&entries)
.map_err(|e| SpatialError::Construction(e.to_string()))?;
progress(2, PHASES);
Ok(Self {
tree,
len: cloud.len(),
})
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl NeighborSearch for KdTree {
fn knn_into(&self, query: &Vector3<f64>, k: usize, out: &mut Vec<Neighbor>) {
out.clear();
let Some(count) = NonZeroUsize::new(k.min(self.len)) else {
return;
};
let point = [query.x, query.y, query.z];
if count.get() == 1 {
let found = self
.tree
.query(&point)
.nearest_one::<SquaredEuclidean<f64>>()
.execute();
out.push(Neighbor {
index: found.item,
distance_squared: found.distance,
});
return;
}
let found = self
.tree
.query(&point)
.nearest_n::<SquaredEuclidean<f64>>(count)
.execute();
for item in found {
out.push(Neighbor {
index: item.item,
distance_squared: item.distance,
});
}
out.sort_unstable_by(compare_neighbors);
}
fn radius_into(&self, query: &Vector3<f64>, radius: f64, out: &mut Vec<Neighbor>) {
out.clear();
let point = [query.x, query.y, query.z];
let found = self
.tree
.query(&point)
.within::<SquaredEuclidean<f64>>(radius * radius)
.execute();
for item in found {
out.push(Neighbor {
index: item.item,
distance_squared: item.distance,
});
}
out.sort_unstable_by(compare_neighbors);
}
}