use std::cmp::Reverse;
use std::collections::BinaryHeap;
use crate::mesh::MeshBuffers;
use crate::normals::compute_normals;
pub struct DecimateResult {
pub original_faces: usize,
pub result_faces: usize,
pub original_verts: usize,
pub result_verts: usize,
pub ratio_achieved: f32,
}
fn edge_len(positions: &[[f32; 3]], v0: usize, v1: usize) -> f32 {
let p0 = positions[v0];
let p1 = positions[v1];
let dx = p1[0] - p0[0];
let dy = p1[1] - p0[1];
let dz = p1[2] - p0[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
fn find(rep: &mut [u32], mut v: u32) -> u32 {
while rep[v as usize] != v {
let gp = rep[rep[v as usize] as usize];
rep[v as usize] = gp;
v = gp;
}
v
}
pub fn decimate(mesh: &MeshBuffers, target_faces: usize) -> MeshBuffers {
let orig_face_count = mesh.indices.len() / 3;
if target_faces >= orig_face_count {
return mesh.clone();
}
let nv = mesh.positions.len();
let mut indices: Vec<u32> = mesh.indices.clone();
let mut representative: Vec<u32> = (0..nv as u32).collect();
let mut heap: BinaryHeap<(Reverse<u32>, u32, u32)> = {
use std::collections::HashSet;
let mut seen: HashSet<(u32, u32)> = HashSet::new();
let mut h = BinaryHeap::new();
for tri in indices.chunks_exact(3) {
let (a, b, c) = (tri[0], tri[1], tri[2]);
for &(ea, eb) in &[(a, b), (b, c), (a, c)] {
let key = (ea.min(eb), ea.max(eb));
if seen.insert(key) {
let len = edge_len(&mesh.positions, key.0 as usize, key.1 as usize);
h.push((Reverse(len.to_bits()), key.0, key.1));
}
}
}
h
};
let mut face_removed: Vec<bool> = vec![false; orig_face_count];
let mut active_faces = orig_face_count;
while active_faces > target_faces {
let (_, raw_v0, raw_v1) = match heap.pop() {
Some(e) => e,
None => break,
};
let v0 = find(&mut representative, raw_v0);
let v1 = find(&mut representative, raw_v1);
if v0 == v1 {
continue; }
representative[v1 as usize] = v0;
for (fi, removed) in face_removed.iter_mut().enumerate() {
if *removed {
continue;
}
let base = fi * 3;
let mut changed = false;
for k in 0..3 {
let rv = find(&mut representative, indices[base + k]);
if rv != indices[base + k] {
indices[base + k] = rv;
changed = true;
}
}
if changed {
let (a, b, c) = (indices[base], indices[base + 1], indices[base + 2]);
if a == b || b == c || a == c {
*removed = true;
active_faces -= 1;
}
}
}
}
let mut used = vec![false; nv];
for (fi, &removed) in face_removed.iter().enumerate() {
if removed {
continue;
}
let base = fi * 3;
for k in 0..3 {
used[indices[base + k] as usize] = true;
}
}
let mut remap: Vec<u32> = vec![0; nv];
let mut new_positions: Vec<[f32; 3]> = Vec::new();
let mut new_uvs: Vec<[f32; 2]> = Vec::new();
let new_colors: Option<Vec<[f32; 4]>>;
{
let mut color_buf: Option<Vec<[f32; 4]>> = mesh.colors.as_ref().map(|_| Vec::new());
let mut next = 0u32;
for (old, &is_used) in used.iter().enumerate() {
if is_used {
remap[old] = next;
next += 1;
new_positions.push(mesh.positions[old]);
new_uvs.push(if old < mesh.uvs.len() {
mesh.uvs[old]
} else {
[0.0, 0.0]
});
if let (Some(ref mc), Some(ref mut nc)) = (&mesh.colors, &mut color_buf) {
nc.push(if old < mc.len() {
mc[old]
} else {
[1.0, 1.0, 1.0, 1.0]
});
}
}
}
new_colors = color_buf;
}
let mut new_indices: Vec<u32> = Vec::new();
for (fi, &removed) in face_removed.iter().enumerate() {
if removed {
continue;
}
let base = fi * 3;
new_indices.push(remap[indices[base] as usize]);
new_indices.push(remap[indices[base + 1] as usize]);
new_indices.push(remap[indices[base + 2] as usize]);
}
let new_nv = new_positions.len();
let mut result = MeshBuffers {
positions: new_positions,
normals: vec![[0.0, 1.0, 0.0]; new_nv],
tangents: vec![[1.0, 0.0, 0.0, 1.0]; new_nv],
uvs: new_uvs,
indices: new_indices,
colors: new_colors,
has_suit: mesh.has_suit,
};
compute_normals(&mut result);
result
}
pub fn decimate_ratio(mesh: &MeshBuffers, ratio: f32) -> MeshBuffers {
let target = ((mesh.indices.len() / 3) as f32 * ratio.clamp(0.01, 1.0)) as usize;
decimate(mesh, target.max(1))
}
pub fn decimate_with_info(
mesh: &MeshBuffers,
target_faces: usize,
) -> (MeshBuffers, DecimateResult) {
let original_faces = mesh.indices.len() / 3;
let original_verts = mesh.positions.len();
let result = decimate(mesh, target_faces);
let result_faces = result.indices.len() / 3;
let result_verts = result.positions.len();
let ratio_achieved = if original_faces == 0 {
1.0
} else {
result_faces as f32 / original_faces as f32
};
(
result,
DecimateResult {
original_faces,
result_faces,
original_verts,
result_verts,
ratio_achieved,
},
)
}
#[cfg(test)]
mod tests {
use super::*;
fn grid_mesh(n: usize) -> MeshBuffers {
let mut positions = Vec::new();
for i in 0..n {
for j in 0..n {
positions.push([i as f32, 0.0, j as f32]);
}
}
let mut indices = Vec::new();
for i in 0..n - 1 {
for j in 0..n - 1 {
let b = (i * n + j) as u32;
indices.extend_from_slice(&[b, b + 1, b + n as u32]);
indices.extend_from_slice(&[b + 1, b + n as u32 + 1, b + n as u32]);
}
}
MeshBuffers {
positions,
normals: vec![[0.0, 1.0, 0.0]; n * n],
uvs: vec![[0.0, 0.0]; n * n],
tangents: vec![],
colors: None,
indices,
has_suit: true,
}
}
#[test]
fn decimate_reduces_faces() {
let mesh = grid_mesh(5); assert_eq!(mesh.face_count(), 32);
let result = decimate(&mesh, 10);
assert!(
result.face_count() <= 20,
"expected <= 20 faces, got {}",
result.face_count()
);
}
#[test]
fn decimate_above_target_no_change() {
let mesh = grid_mesh(5);
let original_faces = mesh.face_count();
let result = decimate(&mesh, original_faces + 100);
assert_eq!(result.face_count(), original_faces);
}
#[test]
fn decimate_index_bounds_valid() {
let mesh = grid_mesh(5);
let result = decimate(&mesh, 10);
let nv = result.positions.len();
for &idx in &result.indices {
assert!(
(idx as usize) < nv,
"index {} out of bounds (nv={})",
idx,
nv
);
}
}
#[test]
fn decimate_ratio_half() {
let mesh = grid_mesh(5);
let original = mesh.face_count();
let result = decimate_ratio(&mesh, 0.5);
assert!(
result.face_count() <= original,
"ratio=0.5 should not increase face count"
);
}
#[test]
fn decimate_with_info_metadata() {
let mesh = grid_mesh(5);
let original_faces = mesh.indices.len() / 3;
let (_, info) = decimate_with_info(&mesh, 10);
assert_eq!(info.original_faces, original_faces);
}
#[test]
fn decimate_result_has_vertices() {
let mesh = grid_mesh(5);
let result = decimate(&mesh, 1);
assert!(
result.positions.len() >= 3,
"result must have at least 3 vertices"
);
}
}