use nalgebra::{Matrix3, Vector3};
use rayon::prelude::*;
use crate::cloud::PointCloud;
use crate::neighbors::NeighborSearch;
pub fn estimate_normals<S>(cloud: &PointCloud, search: &S, k: usize) -> Vec<Vector3<f64>>
where
S: NeighborSearch + Sync,
{
estimate_normals_observed(cloud, search, k, |_, _| {})
}
const PROGRESS_CHUNK: usize = 16_384;
pub fn estimate_normals_observed<S, F>(
cloud: &PointCloud,
search: &S,
k: usize,
mut progress: F,
) -> Vec<Vector3<f64>>
where
S: NeighborSearch + Sync,
F: FnMut(usize, usize),
{
assert!(k >= 3, "normal estimation needs at least three neighbours");
let count = cloud.len();
let mut normals: Vec<Vector3<f64>> = Vec::with_capacity(count);
let mut done = 0;
while done < count {
let end = (done + PROGRESS_CHUNK).min(count);
normals.par_extend((done..end).into_par_iter().map(|index| {
let query = cloud.point(index);
let neighbours = search.knn(&query, k);
if neighbours.len() < 3 {
return Vector3::z();
}
let mut centroid = Vector3::zeros();
for neighbour in &neighbours {
centroid += cloud.point(neighbour.index as usize);
}
centroid /= neighbours.len() as f64;
let mut covariance = Matrix3::zeros();
for neighbour in &neighbours {
let delta = cloud.point(neighbour.index as usize) - centroid;
covariance += delta * delta.transpose();
}
let eigen = nalgebra::SymmetricEigen::new(covariance);
let mut smallest = 0;
for axis in 1..3 {
if eigen.eigenvalues[axis] < eigen.eigenvalues[smallest] {
smallest = axis;
}
}
let normal: Vector3<f64> = eigen.eigenvectors.column(smallest).into();
let mut dominant = 0;
for axis in 1..3 {
if normal[axis].abs() > normal[dominant].abs() {
dominant = axis;
}
}
if normal[dominant] < 0.0 {
-normal
} else {
normal
}
}));
done = end;
progress(done, count);
}
normals
}