use crate::mesh::Mesh;
use rustc_hash::FxHashMap;
const BASE_CELL_FRACTION: f32 = 0.0025;
const CELL_GROWTH: f32 = 1.6;
const MAX_ITERATIONS: u32 = 10;
pub(crate) fn cluster_decimate(mesh: &Mesh, cell_size: f32) -> Mesh {
let n_verts = mesh.positions.len() / 3;
if n_verts == 0 || mesh.indices.is_empty() || !(cell_size > 0.0) || !cell_size.is_finite() {
return mesh.clone();
}
let inv = 1.0f64 / cell_size as f64;
let mut rep_of_cell: FxHashMap<[i64; 3], u32> = FxHashMap::default();
let mut rep: Vec<u32> = Vec::with_capacity(n_verts);
for i in 0..n_verts {
let key = [
(mesh.positions[i * 3] as f64 * inv).floor() as i64,
(mesh.positions[i * 3 + 1] as f64 * inv).floor() as i64,
(mesh.positions[i * 3 + 2] as f64 * inv).floor() as i64,
];
rep.push(*rep_of_cell.entry(key).or_insert(i as u32));
}
let mut kept: Vec<u32> = Vec::with_capacity(mesh.indices.len());
for tri in mesh.indices.chunks_exact(3) {
if (tri[0] as usize) >= n_verts
|| (tri[1] as usize) >= n_verts
|| (tri[2] as usize) >= n_verts
{
continue;
}
let (a, b, c) = (
rep[tri[0] as usize],
rep[tri[1] as usize],
rep[tri[2] as usize],
);
if a == b || b == c || a == c {
continue;
}
kept.extend_from_slice(&[a, b, c]);
}
let mut remap: Vec<i32> = vec![-1; n_verts];
let mut new_pos: Vec<f32> = Vec::with_capacity(kept.len() * 3);
let mut new_idx: Vec<u32> = Vec::with_capacity(kept.len());
for &i in &kept {
let old = i as usize;
let slot = if remap[old] < 0 {
let n = (new_pos.len() / 3) as u32;
remap[old] = n as i32;
new_pos.extend_from_slice(&mesh.positions[old * 3..old * 3 + 3]);
n
} else {
remap[old] as u32
};
new_idx.push(slot);
}
mesh.rebuilt_like(new_pos, Vec::new(), new_idx)
}
pub(crate) fn cluster_to_ratio(mesh: &Mesh, target_ratio: f32, min_floor: u32) -> (Mesh, u32) {
let tris_before = mesh.indices.len() / 3;
if tris_before == 0 {
return (mesh.clone(), 0);
}
let target =
((tris_before as f64 * target_ratio as f64).ceil() as usize).max(min_floor.max(1) as usize);
if tris_before <= target {
return (mesh.clone(), 0);
}
let (min, max) = mesh.bounds();
let diag = ((max.x - min.x).powi(2) + (max.y - min.y).powi(2) + (max.z - min.z).powi(2)).sqrt();
if !(diag > 0.0) || !diag.is_finite() {
return (mesh.clone(), 0);
}
let mut cell = diag * BASE_CELL_FRACTION;
let mut best: Option<Mesh> = None;
for iteration in 1..=MAX_ITERATIONS {
let candidate = cluster_decimate(mesh, cell);
let count = candidate.indices.len() / 3;
if count == 0 {
return (best.unwrap_or_else(|| mesh.clone()), iteration.saturating_sub(1));
}
let improves = best
.as_ref()
.map(|b| count < b.indices.len() / 3)
.unwrap_or(true);
if improves {
best = Some(candidate);
}
if count <= target {
return (best.unwrap(), iteration);
}
cell *= CELL_GROWTH;
}
(best.unwrap_or_else(|| mesh.clone()), MAX_ITERATIONS)
}