use std::collections::{BTreeSet, HashMap};
use crate::mesh::{Indices, Primitive, Topology};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SmoothOptions {
pub iterations: u32,
pub preserve_boundary: bool,
}
impl Default for SmoothOptions {
fn default() -> Self {
Self {
iterations: 1,
preserve_boundary: true,
}
}
}
struct Adjacency {
neighbours: Vec<BTreeSet<u32>>,
boundary: Vec<bool>,
}
impl Adjacency {
fn build(tris: &[[u32; 3]], n: usize) -> Self {
let mut edge_count: HashMap<(u32, u32), u32> = HashMap::new();
let key = |a: u32, b: u32| if a < b { (a, b) } else { (b, a) };
for &[a, b, c] in tris {
for (u, v) in [(a, b), (b, c), (c, a)] {
*edge_count.entry(key(u, v)).or_insert(0) += 1;
}
}
let mut neighbours: Vec<BTreeSet<u32>> = vec![BTreeSet::new(); n];
let mut boundary = vec![false; n];
for (&(a, b), &count) in &edge_count {
neighbours[a as usize].insert(b);
neighbours[b as usize].insert(a);
if count != 2 {
boundary[a as usize] = true;
boundary[b as usize] = true;
}
}
Self {
neighbours,
boundary,
}
}
}
fn umbrella(pos: &[[f32; 3]], adj: &Adjacency) -> Vec<[f32; 3]> {
let n = pos.len();
let mut out = vec![[0.0f32; 3]; n];
for i in 0..n {
let ring = &adj.neighbours[i];
if ring.is_empty() {
continue;
}
let mut sum = [0.0f64; 3];
for &j in ring {
let p = pos[j as usize];
sum[0] += p[0] as f64;
sum[1] += p[1] as f64;
sum[2] += p[2] as f64;
}
let inv = 1.0 / ring.len() as f64;
let centroid = [sum[0] * inv, sum[1] * inv, sum[2] * inv];
out[i] = [
(centroid[0] - pos[i][0] as f64) as f32,
(centroid[1] - pos[i][1] as f64) as f32,
(centroid[2] - pos[i][2] as f64) as f32,
];
}
out
}
fn relax(pos: &mut [[f32; 3]], adj: &Adjacency, factor: f32, pin_boundary: bool) {
let lap = umbrella(pos, adj);
for i in 0..pos.len() {
if pin_boundary && adj.boundary[i] {
continue;
}
let candidate = [
pos[i][0] + factor * lap[i][0],
pos[i][1] + factor * lap[i][1],
pos[i][2] + factor * lap[i][2],
];
if candidate.iter().all(|c| c.is_finite()) {
pos[i] = candidate;
}
}
}
fn welded_triangles(prim: &Primitive) -> Option<(Primitive, Vec<[u32; 3]>)> {
let sn = prim.positions.len();
if sn == 0 {
return None;
}
let clean_tris: Vec<[u32; 3]> = prim
.triangle_indices()
.into_iter()
.filter(|&[a, b, c]| {
(a as usize) < sn
&& (b as usize) < sn
&& (c as usize) < sn
&& a != b
&& b != c
&& a != c
})
.collect();
if clean_tris.is_empty() {
return None;
}
let mut clean = prim.to_triangle_list();
let mut flat: Vec<u32> = Vec::with_capacity(clean_tris.len() * 3);
for t in &clean_tris {
flat.extend_from_slice(t);
}
clean.indices = Some(Indices::U32(flat));
let welded = clean.weld_vertices();
let tris = welded.triangle_indices();
if tris.is_empty() || welded.positions.is_empty() {
return None;
}
Some((welded, tris))
}
fn empty_like(prim: &Primitive) -> Primitive {
let mut o = prim.to_triangle_list();
o.topology = Topology::Triangles;
o.indices = Some(Indices::U32(Vec::new()));
o
}
impl Primitive {
pub fn smooth_laplacian(&self, lambda: f32, options: SmoothOptions) -> Primitive {
let Some((mut welded, tris)) = welded_triangles(self) else {
return empty_like(self);
};
let lambda = lambda.clamp(0.0, 1.0);
let n = welded.positions.len();
let adj = Adjacency::build(&tris, n);
for _ in 0..options.iterations {
relax(
&mut welded.positions,
&adj,
lambda,
options.preserve_boundary,
);
}
welded
}
pub fn smooth_taubin(&self, lambda: f32, mu: f32, options: SmoothOptions) -> Primitive {
let Some((mut welded, tris)) = welded_triangles(self) else {
return empty_like(self);
};
let lambda = lambda.clamp(0.0, 1.0);
let n = welded.positions.len();
let adj = Adjacency::build(&tris, n);
for _ in 0..options.iterations {
relax(
&mut welded.positions,
&adj,
lambda,
options.preserve_boundary,
);
relax(&mut welded.positions, &adj, mu, options.preserve_boundary);
}
welded
}
}