use super::functions::*;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct EdgeCollapseRecord {
pub removed: usize,
pub target: usize,
pub deleted_triangles: Vec<[usize; 3]>,
}
pub struct ProgressiveMeshSimple {
pub current: SimpleMesh,
pub history: Vec<EdgeCollapseRecord>,
}
impl ProgressiveMeshSimple {
pub fn new(mesh: SimpleMesh) -> Self {
Self {
current: mesh,
history: Vec::new(),
}
}
pub fn collapse_edge(&mut self, src: usize, dst: usize) {
let mut deleted = Vec::new();
let mut remaining = Vec::new();
for tri in &self.current.triangles {
let has_src = tri.contains(&src);
let has_dst = tri.contains(&dst);
if has_src && has_dst {
deleted.push(*tri);
} else if has_src {
let new_tri = tri.map(|v| if v == src { dst } else { v });
remaining.push(new_tri);
} else {
remaining.push(*tri);
}
}
self.history.push(EdgeCollapseRecord {
removed: src,
target: dst,
deleted_triangles: deleted,
});
self.current.triangles = remaining;
}
pub fn n_collapses(&self) -> usize {
self.history.len()
}
pub fn n_triangles(&self) -> usize {
self.current.triangle_count()
}
}
pub struct ProgressiveMesh {
pub mesh: SimpleMesh,
pub history: Vec<CollapseRecord>,
}
impl ProgressiveMesh {
pub fn new(mesh: SimpleMesh) -> Self {
Self {
mesh,
history: Vec::new(),
}
}
pub fn collapse_one(&mut self) -> bool {
if self.mesh.triangle_count() <= 3 {
return false;
}
let quadrics = compute_vertex_quadrics(&self.mesh);
let mut edge_set: std::collections::HashSet<(usize, usize)> =
std::collections::HashSet::new();
for &[a, b, c] in &self.mesh.triangles {
edge_set.insert((a.min(b), a.max(b)));
edge_set.insert((b.min(c), b.max(c)));
edge_set.insert((a.min(c), a.max(c)));
}
let mut best_cost = f64::MAX;
let mut best_v1 = 0usize;
let mut best_v2 = 0usize;
let mut best_pos = [0.0f64; 3];
for (v1, v2) in &edge_set {
let (cost, pos) = edge_collapse_cost(
self.mesh.vertices[*v1],
self.mesh.vertices[*v2],
&quadrics[*v1],
&quadrics[*v2],
);
if cost < best_cost {
best_cost = cost;
best_v1 = *v1;
best_v2 = *v2;
best_pos = pos;
}
}
let old_pos = self.mesh.vertices[best_v1];
let record = CollapseRecord {
v_kept: best_v1,
v_removed: best_v2,
old_pos,
new_pos: best_pos,
};
collapse_edge(&mut self.mesh, best_v1, best_v2, best_pos);
self.history.push(record);
true
}
pub fn decimate_to(&mut self, target_triangles: usize) {
while self.mesh.triangle_count() > target_triangles {
if !self.collapse_one() {
break;
}
}
}
}
#[derive(Debug, Clone)]
pub struct CollapseRecord {
pub v_kept: usize,
pub v_removed: usize,
pub old_pos: [f64; 3],
pub new_pos: [f64; 3],
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MeshEdge {
pub v0: usize,
pub v1: usize,
pub cost: f64,
}
pub struct EdgePriorityQueue {
pub(super) heap: std::collections::BinaryHeap<MeshEdge>,
}
impl EdgePriorityQueue {
pub fn new() -> Self {
Self {
heap: std::collections::BinaryHeap::new(),
}
}
pub fn push(&mut self, edge: MeshEdge) {
self.heap.push(edge);
}
pub fn pop(&mut self) -> Option<MeshEdge> {
self.heap.pop()
}
pub fn len(&self) -> usize {
self.heap.len()
}
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
pub fn from_mesh(mesh: &SimpleMesh) -> Self {
let mut q = Self::new();
let mut seen: HashSet<(usize, usize)> = HashSet::new();
for tri in &mesh.triangles {
for &(a, b) in &[(tri[0], tri[1]), (tri[1], tri[2]), (tri[0], tri[2])] {
let key = if a < b { (a, b) } else { (b, a) };
if seen.insert(key) {
let va = mesh.vertices[a];
let vb = mesh.vertices[b];
let cost =
(va[0] - vb[0]).powi(2) + (va[1] - vb[1]).powi(2) + (va[2] - vb[2]).powi(2);
q.push(MeshEdge {
v0: key.0,
v1: key.1,
cost,
});
}
}
}
q
}
}
#[derive(Clone)]
pub struct SimpleMesh {
pub vertices: Vec<[f64; 3]>,
pub triangles: Vec<[usize; 3]>,
}
impl SimpleMesh {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
triangles: Vec::new(),
}
}
pub fn add_vertex(&mut self, v: [f64; 3]) -> usize {
let idx = self.vertices.len();
self.vertices.push(v);
idx
}
pub fn add_triangle(&mut self, a: usize, b: usize, c: usize) {
self.triangles.push([a, b, c]);
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn triangle_count(&self) -> usize {
self.triangles.len()
}
pub fn triangle_normal(&self, t_idx: usize) -> [f64; 3] {
let [a, b, c] = self.triangles[t_idx];
let va = self.vertices[a];
let vb = self.vertices[b];
let vc = self.vertices[c];
let ab = [vb[0] - va[0], vb[1] - va[1], vb[2] - va[2]];
let ac = [vc[0] - va[0], vc[1] - va[1], vc[2] - va[2]];
let n = cross(ab, ac);
normalize(n)
}
pub fn triangle_area(&self, t_idx: usize) -> f64 {
let [a, b, c] = self.triangles[t_idx];
let va = self.vertices[a];
let vb = self.vertices[b];
let vc = self.vertices[c];
let ab = [vb[0] - va[0], vb[1] - va[1], vb[2] - va[2]];
let ac = [vc[0] - va[0], vc[1] - va[1], vc[2] - va[2]];
let cp = cross(ab, ac);
0.5 * length(cp)
}
pub fn surface_area(&self) -> f64 {
(0..self.triangle_count())
.map(|i| self.triangle_area(i))
.sum()
}
}
pub struct QuadricMatrix {
pub q: [[f64; 4]; 4],
}
impl QuadricMatrix {
pub fn zero() -> Self {
Self { q: [[0.0; 4]; 4] }
}
pub fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Self {
let p = [a, b, c, d];
let mut q = [[0.0f64; 4]; 4];
for i in 0..4 {
for j in 0..4 {
q[i][j] = p[i] * p[j];
}
}
Self { q }
}
pub fn add(&self, other: &Self) -> Self {
let mut result = Self::zero();
for i in 0..4 {
for j in 0..4 {
result.q[i][j] = self.q[i][j] + other.q[i][j];
}
}
result
}
pub fn vertex_error(&self, v: [f64; 3]) -> f64 {
let h = [v[0], v[1], v[2], 1.0];
let mut result = 0.0;
for i in 0..4 {
for j in 0..4 {
result += h[i] * self.q[i][j] * h[j];
}
}
result
}
}
#[derive(Debug, Clone, Copy)]
pub struct NormalCone {
pub axis: [f64; 3],
pub half_angle: f64,
}
impl NormalCone {
pub fn from_normal(n: [f64; 3]) -> Self {
Self {
axis: normalize3(n),
half_angle: 0.0,
}
}
pub fn merge(&self, other: &NormalCone) -> NormalCone {
let axis_new = normalize3(add3(self.axis, other.axis));
let angle1 = dot3(self.axis, axis_new).clamp(-1.0, 1.0).acos() + self.half_angle;
let angle2 = dot3(other.axis, axis_new).clamp(-1.0, 1.0).acos() + other.half_angle;
NormalCone {
axis: axis_new,
half_angle: angle1.max(angle2),
}
}
pub fn contains(&self, n: [f64; 3], tol: f64) -> bool {
let n_hat = normalize3(n);
let cos_angle = dot3(self.axis, n_hat).clamp(-1.0, 1.0);
let angle = cos_angle.acos();
angle <= self.half_angle + tol
}
pub fn half_angle_deg(&self) -> f64 {
self.half_angle.to_degrees()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EdgeFeature {
Smooth,
Crease,
Boundary,
}
#[derive(Debug, Clone, Default)]
pub struct DecimationMetrics {
pub original_vertices: usize,
pub original_triangles: usize,
pub reduced_vertices: usize,
pub reduced_triangles: usize,
pub n_collapses: usize,
pub total_qem_cost: f64,
pub max_qem_cost: f64,
}
impl DecimationMetrics {
pub fn vertex_reduction_ratio(&self) -> f64 {
if self.original_vertices == 0 {
return 0.0;
}
1.0 - self.reduced_vertices as f64 / self.original_vertices as f64
}
pub fn triangle_reduction_ratio(&self) -> f64 {
if self.original_triangles == 0 {
return 0.0;
}
1.0 - self.reduced_triangles as f64 / self.original_triangles as f64
}
pub fn avg_qem_cost(&self) -> f64 {
if self.n_collapses == 0 {
return 0.0;
}
self.total_qem_cost / self.n_collapses as f64
}
}
pub struct QemDecimation {
pub mesh: SimpleMesh,
pub(super) quadrics: Vec<QuadricMatrix>,
}
impl QemDecimation {
pub fn new(mesh: SimpleMesh) -> Self {
let quadrics = compute_vertex_quadrics(&mesh);
Self { mesh, quadrics }
}
pub fn recompute_quadrics(&mut self) {
self.quadrics = compute_vertex_quadrics(&self.mesh);
}
pub fn compute_error_threshold(&self, scale_factor: f64) -> f64 {
let avg_edge = average_edge_length(&self.mesh);
if avg_edge < 1e-12 {
return 0.0;
}
let scale_sq = avg_edge * avg_edge;
let n_verts = self.mesh.vertex_count();
if n_verts < 2 {
return scale_factor * scale_sq;
}
let mut sampled_cost = 0.0f64;
let mut sampled_count = 0usize;
let step = (n_verts / 32).max(1);
for tri in self.mesh.triangles.iter().step_by(step) {
let v0 = tri[0];
let v1 = tri[1];
if v0 >= self.quadrics.len() || v1 >= self.quadrics.len() {
continue;
}
let (cost, _) = edge_collapse_cost(
self.mesh.vertices[v0],
self.mesh.vertices[v1],
&self.quadrics[v0],
&self.quadrics[v1],
);
sampled_cost += cost;
sampled_count += 1;
}
let mean_cost = if sampled_count > 0 {
sampled_cost / sampled_count as f64
} else {
1.0
};
(scale_factor * scale_sq * mean_cost).max(1e-12)
}
pub fn preserve_boundary(&mut self, threshold: f64) -> usize {
let boundary_edges = find_boundary_edges(&self.mesh);
let boundary_set: HashSet<(usize, usize)> = boundary_edges
.iter()
.flat_map(|&(a, b)| [(a.min(b), a.max(b)), (a.min(b), a.max(b))])
.collect();
let mut n_collapses = 0usize;
let mut keep_going = true;
while keep_going {
keep_going = false;
self.recompute_quadrics();
let mut best: Option<(f64, [f64; 3], usize, usize)> = None;
for tri in &self.mesh.triangles {
for k in 0..3 {
let v0 = tri[k];
let v1 = tri[(k + 1) % 3];
let key = (v0.min(v1), v0.max(v1));
if boundary_set.contains(&key) {
continue;
}
if v0 >= self.quadrics.len() || v1 >= self.quadrics.len() {
continue;
}
let (cost, opt) = edge_collapse_cost(
self.mesh.vertices[v0],
self.mesh.vertices[v1],
&self.quadrics[v0],
&self.quadrics[v1],
);
if cost <= threshold && best.as_ref().is_none_or(|b| cost < b.0) {
best = Some((cost, opt, v0, v1));
}
}
}
if let Some((_cost, opt, v0, v1)) = best {
self.mesh.vertices[v0] = opt;
for tri in &mut self.mesh.triangles {
for idx in tri.iter_mut() {
if *idx == v1 {
*idx = v0;
}
}
}
self.mesh
.triangles
.retain(|tri| tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2]);
n_collapses += 1;
keep_going = true;
}
}
n_collapses
}
pub fn compute_feature_score(&self) -> HashMap<(usize, usize), f64> {
let mut edge_faces: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
for (fi, tri) in self.mesh.triangles.iter().enumerate() {
for k in 0..3 {
let a = tri[k];
let b = tri[(k + 1) % 3];
let key = (a.min(b), a.max(b));
edge_faces.entry(key).or_default().push(fi);
}
}
let mut scores: HashMap<(usize, usize), f64> = HashMap::new();
for (&edge, faces) in &edge_faces {
if faces.len() != 2 {
scores.insert(edge, f64::INFINITY);
continue;
}
let n0 = self.mesh.triangle_normal(faces[0]);
let n1 = self.mesh.triangle_normal(faces[1]);
let cos_a = (dot3(n0, n1)).clamp(-1.0, 1.0);
let score = 1.0 - cos_a;
scores.insert(edge, score);
}
scores
}
}
#[derive(Debug, Clone)]
pub struct DecimationStats {
pub original_vertices: usize,
pub original_triangles: usize,
pub result_vertices: usize,
pub result_triangles: usize,
pub reduction_ratio: f64,
pub total_qem_error: f64,
}
impl DecimationStats {
pub fn compute(
original_v: usize,
original_t: usize,
result: &SimpleMesh,
total_qem_error: f64,
) -> Self {
let reduction_ratio = if original_t > 0 {
1.0 - result.triangle_count() as f64 / original_t as f64
} else {
0.0
};
Self {
original_vertices: original_v,
original_triangles: original_t,
result_vertices: result.vertex_count(),
result_triangles: result.triangle_count(),
reduction_ratio,
total_qem_error,
}
}
}