#![allow(dead_code)]
#[allow(dead_code)]
pub struct MlsReconstructConfig {
pub search_radius: f32,
pub polynomial_degree: u32,
pub grid_resolution: u32,
}
#[allow(dead_code)]
impl Default for MlsReconstructConfig {
fn default() -> Self {
Self { search_radius: 0.1, polynomial_degree: 2, grid_resolution: 32 }
}
}
#[allow(dead_code)]
pub struct MlsReconstructResult {
pub positions: Vec<[f32; 3]>,
pub normals: Vec<[f32; 3]>,
pub point_count: usize,
}
#[inline]
fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0]-b[0], a[1]-b[1], a[2]-b[2]]
}
#[inline]
fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0]*b[0]+a[1]*b[1]+a[2]*b[2]
}
#[inline]
fn len3(v: [f32; 3]) -> f32 {
dot3(v,v).sqrt()
}
#[allow(dead_code)]
pub fn sq_dist(a: [f32; 3], b: [f32; 3]) -> f32 {
let d = sub3(a, b);
dot3(d, d)
}
#[allow(dead_code)]
pub fn gaussian_weight(dist_sq: f32, radius: f32) -> f32 {
let h2 = radius * radius;
(-dist_sq / h2).exp()
}
#[allow(dead_code)]
pub fn find_neighbours(points: &[[f32; 3]], query: [f32; 3], radius: f32) -> Vec<usize> {
let r2 = radius * radius;
points.iter().enumerate()
.filter(|(_, p)| sq_dist(query, **p) <= r2)
.map(|(i, _)| i)
.collect()
}
#[allow(dead_code)]
pub fn weighted_centroid(points: &[[f32; 3]], weights: &[f32]) -> [f32; 3] {
let total: f32 = weights.iter().sum();
if total < 1e-10 {
return [0.0; 3];
}
let mut sum = [0.0f32; 3];
for (p, &w) in points.iter().zip(weights.iter()) {
sum[0] += p[0] * w;
sum[1] += p[1] * w;
sum[2] += p[2] * w;
}
[sum[0]/total, sum[1]/total, sum[2]/total]
}
#[allow(dead_code)]
pub fn estimate_normal_stub(_points: &[[f32; 3]]) -> [f32; 3] {
[0.0, 0.0, 1.0]
}
#[allow(dead_code)]
pub fn project_to_plane(p: [f32; 3], centroid: [f32; 3], normal: [f32; 3]) -> [f32; 3] {
let d = sub3(p, centroid);
let dist = dot3(d, normal);
[p[0]-dist*normal[0], p[1]-dist*normal[1], p[2]-dist*normal[2]]
}
#[allow(dead_code)]
pub fn mls_reconstruct(
points: &[[f32; 3]],
config: &MlsReconstructConfig,
) -> MlsReconstructResult {
let mut smoothed = Vec::with_capacity(points.len());
for &q in points {
let nb = find_neighbours(points, q, config.search_radius);
if nb.is_empty() {
smoothed.push(q);
} else {
let nb_pts: Vec<[f32; 3]> = nb.iter().map(|&i| points[i]).collect();
let weights: Vec<f32> = nb_pts.iter()
.map(|&p| gaussian_weight(sq_dist(q, p), config.search_radius))
.collect();
smoothed.push(weighted_centroid(&nb_pts, &weights));
}
}
let normals = vec![[0.0f32, 0.0, 1.0]; smoothed.len()];
let n = smoothed.len();
MlsReconstructResult { positions: smoothed, normals, point_count: n }
}
#[allow(dead_code)]
pub fn mls_coverage(points: &[[f32; 3]], radius: f32) -> f32 {
if points.is_empty() { return 0.0; }
let covered = points.iter().filter(|&&q| {
points.iter().any(|&p| sq_dist(q, p) < radius * radius * 1.001 && p != q)
}).count();
covered as f32 / points.len() as f32
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_points() -> Vec<[f32; 3]> {
vec![
[0.0,0.0,0.0],[1.0,0.0,0.0],[0.5,0.5,0.0],
[0.0,1.0,0.0],[1.0,1.0,0.0],
]
}
#[test]
fn sq_dist_correct() {
let d = sq_dist([0.0,0.0,0.0],[1.0,0.0,0.0]);
assert!((d - 1.0).abs() < 1e-6);
}
#[test]
fn gaussian_weight_at_zero() {
let w = gaussian_weight(0.0, 1.0);
assert!((w - 1.0).abs() < 1e-6);
}
#[test]
fn find_neighbours_count() {
let pts = sample_points();
let nb = find_neighbours(&pts, [0.5, 0.5, 0.0], 0.8);
assert!(!nb.is_empty());
}
#[test]
fn weighted_centroid_uniform() {
let pts = vec![[0.0,0.0,0.0],[2.0,0.0,0.0]];
let weights = vec![1.0, 1.0];
let c = weighted_centroid(&pts, &weights);
assert!((c[0] - 1.0).abs() < 1e-6);
}
#[test]
fn project_to_plane_z_up() {
let p = [1.0, 1.0, 5.0];
let c = [0.0, 0.0, 0.0];
let n = [0.0, 0.0, 1.0];
let proj = project_to_plane(p, c, n);
assert!(proj[2].abs() < 1e-5);
}
#[test]
fn mls_reconstruct_preserves_count() {
let pts = sample_points();
let cfg = MlsReconstructConfig { search_radius: 2.0, ..Default::default() };
let r = mls_reconstruct(&pts, &cfg);
assert_eq!(r.point_count, pts.len());
}
#[test]
fn estimate_normal_stub_unit() {
let n = estimate_normal_stub(&[[0.0,0.0,0.0]]);
let l = n[0]*n[0]+n[1]*n[1]+n[2]*n[2];
assert!((l - 1.0).abs() < 1e-6);
}
#[test]
fn coverage_all_close() {
let pts = vec![[0.0,0.0,0.0],[0.1,0.0,0.0]];
let cov = mls_coverage(&pts, 1.0);
assert!((cov - 1.0).abs() < 1e-5);
}
#[test]
fn default_config() {
let c = MlsReconstructConfig::default();
assert_eq!(c.grid_resolution, 32);
}
}