use crate::data::{CellArray, PolyData, Points};
pub fn thin_mesh(input: &PolyData, target_vertices: usize) -> PolyData {
let n_pts = input.points.len();
if n_pts <= target_vertices || n_pts < 4 {
return input.clone();
}
let mut points: Vec<[f64; 3]> = (0..n_pts).map(|i| input.points.get(i)).collect();
let mut triangles: Vec<Option<[usize; 3]>> = Vec::new();
for cell in input.polys.iter() {
if cell.len() == 3 {
triangles.push(Some([cell[0] as usize, cell[1] as usize, cell[2] as usize]));
}
}
let mut alive = vec![true; n_pts];
let mut current_count: usize = n_pts;
while current_count > target_vertices {
let mut valence = vec![0usize; points.len()];
let mut edge_count = std::collections::HashMap::<(usize, usize), usize>::new();
for tri in triangles.iter().flatten() {
for &v in tri {
if alive[v] {
valence[v] += 1;
}
}
let edges = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])];
for &(a, b) in &edges {
let key = if a < b { (a, b) } else { (b, a) };
*edge_count.entry(key).or_insert(0) += 1;
}
}
let mut is_boundary = vec![false; points.len()];
for (&(a, b), &count) in &edge_count {
if count == 1 {
is_boundary[a] = true;
is_boundary[b] = true;
}
}
let mut best_v: Option<usize> = None;
let mut best_val: usize = usize::MAX;
for i in 0..points.len() {
if alive[i] && !is_boundary[i] && valence[i] > 0 && valence[i] < best_val {
best_val = valence[i];
best_v = Some(i);
}
}
let victim = match best_v {
Some(v) => v,
None => break, };
let mut collapse_target: Option<usize> = None;
for tri in triangles.iter().flatten() {
if tri[0] == victim || tri[1] == victim || tri[2] == victim {
for &v in tri {
if v != victim && alive[v] {
collapse_target = Some(v);
break;
}
}
if collapse_target.is_some() {
break;
}
}
}
let target = match collapse_target {
Some(t) => t,
None => break,
};
alive[victim] = false;
current_count -= 1;
for tri in &mut triangles {
if let Some(ref mut t) = tri {
for v in t.iter_mut() {
if *v == victim {
*v = target;
}
}
if t[0] == t[1] || t[1] == t[2] || t[0] == t[2] {
*tri = None;
}
}
}
}
let mut new_points = Points::<f64>::new();
let mut remap = vec![0usize; points.len()];
let mut new_idx: usize = 0;
for i in 0..points.len() {
if alive[i] {
new_points.push(points[i]);
remap[i] = new_idx;
new_idx += 1;
}
}
let mut new_polys = CellArray::new();
for tri in triangles.iter().flatten() {
if alive[tri[0]] && alive[tri[1]] && alive[tri[2]] {
new_polys.push_cell(&[remap[tri[0]] as i64, remap[tri[1]] as i64, remap[tri[2]] as i64]);
}
}
let mut output = PolyData::new();
output.points = new_points;
output.polys = new_polys;
output
}
#[cfg(test)]
mod tests {
use super::*;
fn make_grid_mesh() -> PolyData {
let mut pts = Vec::new();
for j in 0..4 {
for i in 0..4 {
pts.push([i as f64, j as f64, 0.0]);
}
}
let mut tris = Vec::new();
for j in 0..3 {
for i in 0..3 {
let v0 = j * 4 + i;
let v1 = v0 + 1;
let v2 = v0 + 4;
let v3 = v2 + 1;
tris.push([v0, v1, v3]);
tris.push([v0, v3, v2]);
}
}
PolyData::from_triangles(pts, tris)
}
#[test]
fn reduces_vertex_count() {
let pd = make_grid_mesh();
assert_eq!(pd.points.len(), 16);
let result = thin_mesh(&pd, 14);
assert!(result.points.len() <= 14, "expected <= 14 vertices, got {}", result.points.len());
}
#[test]
fn target_above_current_unchanged() {
let pd = make_grid_mesh();
let result = thin_mesh(&pd, 100);
assert_eq!(result.points.len(), pd.points.len());
}
#[test]
fn no_degenerate_triangles() {
let pd = make_grid_mesh();
let result = thin_mesh(&pd, 5);
for cell in result.polys.iter() {
assert_eq!(cell.len(), 3);
assert_ne!(cell[0], cell[1]);
assert_ne!(cell[1], cell[2]);
assert_ne!(cell[0], cell[2]);
}
}
}