use std::cmp::{Ordering, Reverse};
use std::collections::{BinaryHeap, HashMap, HashSet};
use crate::geometry::face_normal;
use crate::ids::{FaceId, HalfEdgeId, VertexId};
use crate::predicates::is_triangle_degenerate_3d;
use crate::storage::MeshStorage;
type EdgeCostHeap = BinaryHeap<(Reverse<CostKey>, HalfEdgeId)>;
type EdgeCostMap = HashMap<HalfEdgeId, (f64, [f64; 3])>;
struct CollapseInfo {
k: VertexId,
v_a: VertexId,
v_b: VertexId,
q_k: Quadric,
deleted_hes: Vec<HalfEdgeId>,
}
use crate::topology_ops::{TopologyError, collapse_edge_at};
use crate::traversal::{FaceHalfEdges, VertexAdjacentFaces, VertexRing, is_boundary_edge};
#[derive(Clone, Debug)]
struct Quadric {
data: [f64; 10],
}
impl Quadric {
fn zero() -> Self {
Self { data: [0.0; 10] }
}
fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Self {
Self {
data: [
a * a,
a * b,
a * c,
a * d, b * b,
b * c,
b * d, c * c,
c * d, d * d, ],
}
}
fn add(&self, other: &Self) -> Self {
let mut r = Self::zero();
for i in 0..10 {
r.data[i] = self.data[i] + other.data[i];
}
r
}
fn evaluate(&self, pos: [f64; 3]) -> f64 {
let [x, y, z] = pos;
let q = &self.data;
q[0] * x * x
+ 2.0 * q[1] * x * y
+ 2.0 * q[2] * x * z
+ 2.0 * q[3] * x
+ q[4] * y * y
+ 2.0 * q[5] * y * z
+ 2.0 * q[6] * y
+ q[7] * z * z
+ 2.0 * q[8] * z
+ q[9]
}
fn find_optimal_position(&self) -> Option<[f64; 3]> {
let q = &self.data;
let (q00, q01, q02, q03) = (q[0], q[1], q[2], q[3]);
let (q11, q12, q13) = (q[4], q[5], q[6]);
let (q22, q23) = (q[7], q[8]);
let det = q00 * (q11 * q22 - q12 * q12) - q01 * (q01 * q22 - q12 * q02)
+ q02 * (q01 * q12 - q11 * q02);
let norm_sq = q00 * q00 + 2.0 * (q01 * q01 + q02 * q02 + q12 * q12) + q11 * q11 + q22 * q22;
if norm_sq == 0.0 {
return None; }
let scale = norm_sq * norm_sq.sqrt(); if det.abs() < 1e-12 * scale {
return None;
}
let (c0, c1, c2) = (-q03, -q13, -q23);
let det_x = c0 * (q11 * q22 - q12 * q12) - q01 * (c1 * q22 - q12 * c2)
+ q02 * (c1 * q12 - q11 * c2);
let det_y = q00 * (c1 * q22 - q12 * c2) - c0 * (q01 * q22 - q12 * q02)
+ q02 * (q01 * c2 - c1 * q02);
let det_z = q00 * (q11 * c2 - c1 * q12) - q01 * (q01 * c2 - c1 * q02)
+ c0 * (q01 * q12 - q11 * q02);
Some([det_x / det, det_y / det, det_z / det])
}
}
fn face_plane(mesh: &MeshStorage, f: FaceId) -> Option<(f64, f64, f64, f64)> {
let he = mesh.get_face(f)?.halfedge?;
let h = mesh.get_halfedge(he)?;
let v0 = h.vertex;
let p0 = mesh.get_vertex(v0)?.position;
let he2 = h.next?;
let h2 = mesh.get_halfedge(he2)?;
let v1 = h2.vertex;
let p1 = mesh.get_vertex(v1)?.position;
let he3 = h2.next?;
let h3 = mesh.get_halfedge(he3)?;
let v2 = h3.vertex;
let p2 = mesh.get_vertex(v2)?.position;
if is_triangle_degenerate_3d(p0, p1, p2) {
return None;
}
let n = face_normal(mesh, f)?;
let d = -(n[0] * p0[0] + n[1] * p0[1] + n[2] * p0[2]);
Some((n[0], n[1], n[2], d))
}
fn edge_cost_and_position(
mesh: &MeshStorage,
he: HalfEdgeId,
quadrics: &HashMap<VertexId, Quadric>,
) -> Option<(f64, [f64; 3])> {
let h = mesh.get_halfedge(he)?;
let twin_id = h.twin?;
let twin = mesh.get_halfedge(twin_id)?;
let v0 = twin.vertex; let v1 = h.vertex;
let q0 = quadrics.get(&v0)?;
let q1 = quadrics.get(&v1)?;
let q = q0.add(q1);
let p0 = mesh.get_vertex(v0)?.position;
let p1 = mesh.get_vertex(v1)?.position;
let mid = [
(p0[0] + p1[0]) * 0.5,
(p0[1] + p1[1]) * 0.5,
(p0[2] + p1[2]) * 0.5,
];
let mut candidates: Vec<[f64; 3]> = vec![p0, p1, mid];
if let Some(p_opt) = q.find_optimal_position() {
candidates.push(p_opt);
}
let mut best_cost = f64::INFINITY;
let mut best_pos = mid;
for pos in &candidates {
let c = q.evaluate(*pos);
if c < best_cost {
best_cost = c;
best_pos = *pos;
}
}
if best_cost.is_nan() {
best_cost = f64::INFINITY;
}
Some((best_cost, best_pos))
}
#[derive(Clone, Copy)]
struct CostKey(f64);
impl PartialEq for CostKey {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for CostKey {}
impl PartialOrd for CostKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CostKey {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0).unwrap_or(Ordering::Equal)
}
}
fn would_collapse_create_degenerate(
mesh: &MeshStorage,
v_a: VertexId,
v_b: VertexId,
f1: Option<FaceId>,
f2: Option<FaceId>,
new_pos: [f64; 3],
) -> bool {
let faces: HashSet<FaceId> = VertexAdjacentFaces::new(mesh, v_a)
.chain(VertexAdjacentFaces::new(mesh, v_b))
.filter(|&f| Some(f) != f1 && Some(f) != f2)
.collect();
for f in faces {
let halfedges: Vec<HalfEdgeId> = FaceHalfEdges::new(mesh, f).collect();
if halfedges.len() != 3 {
continue;
}
let mut tri = [[0.0; 3]; 3];
for (i, &he) in halfedges.iter().enumerate() {
let v = match mesh.get_halfedge(he) {
Some(h) => h.vertex,
None => return false,
};
tri[i] = if v == v_a || v == v_b {
new_pos
} else {
match mesh.get_vertex(v) {
Some(vt) => vt.position,
None => return false,
}
};
}
if is_triangle_degenerate_3d(tri[0], tri[1], tri[2]) {
return true;
}
}
false
}
pub fn decimate_qem(mesh: &mut MeshStorage, target_faces: usize) -> Result<usize, TopologyError> {
let initial_faces = mesh.face_count();
if target_faces >= initial_faces {
return Ok(0);
}
let mut quadrics = init_vertex_quadrics(mesh);
let (mut heap, mut cost_map) = build_edge_cost_heap(mesh, &quadrics);
let mut faces_removed = 0;
while mesh.face_count() > target_faces {
let (Reverse(CostKey(heap_cost)), he_id) = match heap.pop() {
Some(entry) => entry,
None => break,
};
if is_heap_entry_stale(mesh, heap_cost, he_id, &cost_map) {
continue;
}
let h = match mesh.get_halfedge(he_id) {
Some(h) => h.clone(),
None => continue,
};
let twin_id = match h.twin {
Some(t) => t,
None => continue,
};
let twin = match mesh.get_halfedge(twin_id) {
Some(t) => t.clone(),
None => continue,
};
let v_a = twin.vertex;
let v_b = h.vertex;
let deleted_hes = collect_deleted_halfedges(&h, &twin, he_id, twin_id);
let q_a = quadrics.get(&v_a).cloned().unwrap_or_else(Quadric::zero);
let q_b = quadrics.get(&v_b).cloned().unwrap_or_else(Quadric::zero);
let q_k = q_a.add(&q_b);
let stored_pos = cost_map[&he_id].1;
if would_collapse_create_degenerate(mesh, v_a, v_b, h.face, twin.face, stored_pos) {
continue;
}
match collapse_edge_at(mesh, he_id, stored_pos) {
Ok(k) => {
faces_removed += 2;
let info = CollapseInfo {
k,
v_a,
v_b,
q_k,
deleted_hes,
};
update_quadrics_and_recompute_costs(
mesh,
&mut quadrics,
&mut heap,
&mut cost_map,
&info,
);
}
Err(_) => continue,
}
}
Ok(faces_removed)
}
fn init_vertex_quadrics(mesh: &MeshStorage) -> HashMap<VertexId, Quadric> {
use rayon::prelude::*;
let mut quadrics: HashMap<VertexId, Quadric> = HashMap::new();
for v_id in mesh.vertex_ids() {
quadrics.insert(v_id, Quadric::zero());
}
let face_ids: Vec<FaceId> = mesh.face_ids().collect();
let face_quadrics: Vec<(Vec<VertexId>, Quadric)> = face_ids
.par_iter()
.filter_map(|&f_id| {
let (a, b, c, d) = face_plane(mesh, f_id)?;
let kp = Quadric::from_plane(a, b, c, d);
let verts: Vec<VertexId> = FaceHalfEdges::new(mesh, f_id)
.filter_map(|he| mesh.get_halfedge(he).map(|h| h.vertex))
.collect();
Some((verts, kp))
})
.collect();
for (verts, kp) in face_quadrics {
for v in verts {
if let Some(q) = quadrics.get_mut(&v) {
*q = q.add(&kp);
}
}
}
quadrics
}
fn build_edge_cost_heap(
mesh: &MeshStorage,
quadrics: &HashMap<VertexId, Quadric>,
) -> (EdgeCostHeap, EdgeCostMap) {
use rayon::prelude::*;
let he_ids: Vec<HalfEdgeId> = mesh
.halfedge_ids()
.filter(|&he| !is_boundary_edge(mesh, he))
.collect();
let edge_costs: Vec<(HalfEdgeId, f64, [f64; 3])> = he_ids
.par_iter()
.filter_map(|&he_id| {
let (cost, pos) = edge_cost_and_position(mesh, he_id, quadrics)?;
Some((he_id, cost, pos))
})
.collect();
let mut heap: EdgeCostHeap = BinaryHeap::new();
let mut cost_map: EdgeCostMap = HashMap::new();
for (he_id, cost, pos) in edge_costs {
heap.push((Reverse(CostKey(cost)), he_id));
cost_map.insert(he_id, (cost, pos));
}
(heap, cost_map)
}
fn is_heap_entry_stale(
mesh: &MeshStorage,
heap_cost: f64,
he_id: HalfEdgeId,
cost_map: &EdgeCostMap,
) -> bool {
if !mesh.contains_halfedge(he_id) {
return true;
}
let (stored_cost, _stored_pos) = match cost_map.get(&he_id) {
Some(&c) => c,
None => return true,
};
let tol = 1e-9 * heap_cost.abs().max(stored_cost.abs()).max(1.0);
if (heap_cost - stored_cost).abs() > tol {
return true;
}
if is_boundary_edge(mesh, he_id) {
return true;
}
false
}
fn collect_deleted_halfedges(
h: &crate::storage::HalfEdge,
twin: &crate::storage::HalfEdge,
he_id: HalfEdgeId,
twin_id: HalfEdgeId,
) -> Vec<HalfEdgeId> {
[he_id, twin_id]
.iter()
.copied()
.chain(h.next)
.chain(h.prev)
.chain(twin.next)
.chain(twin.prev)
.collect()
}
fn update_quadrics_and_recompute_costs(
mesh: &mut MeshStorage,
quadrics: &mut HashMap<VertexId, Quadric>,
heap: &mut EdgeCostHeap,
cost_map: &mut EdgeCostMap,
info: &CollapseInfo,
) {
quadrics.remove(&info.v_a);
quadrics.remove(&info.v_b);
quadrics.insert(info.k, info.q_k.clone());
for &dh in &info.deleted_hes {
cost_map.remove(&dh);
}
for out_he in VertexRing::new(mesh, info.k).collect::<Vec<_>>() {
if is_boundary_edge(mesh, out_he) {
continue;
}
if let Some((cost, pos)) = edge_cost_and_position(mesh, out_he, quadrics) {
let twin_he = mesh.get_halfedge(out_he).and_then(|h| h.twin);
heap.push((Reverse(CostKey(cost)), out_he));
cost_map.insert(out_he, (cost, pos));
if let Some(t) = twin_he {
heap.push((Reverse(CostKey(cost)), t));
cost_map.insert(t, (cost, pos));
}
}
}
}
pub fn decimate_to_vertices(
mesh: &mut MeshStorage,
target_verts: usize,
) -> Result<usize, TopologyError> {
let target_faces = 2usize.saturating_mul(target_verts).saturating_sub(4);
decimate_qem(mesh, target_faces)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::build_mesh_from_vertices_and_faces;
use crate::test_util::build_icosphere;
use crate::topology_ops::validate_mesh;
use crate::validate::check_topology;
#[test]
fn quadric_zero_evaluates_to_zero() {
let q = Quadric::zero();
assert_eq!(q.evaluate([1.0, 2.0, 3.0]), 0.0);
assert_eq!(q.evaluate([0.0, 0.0, 0.0]), 0.0);
}
#[test]
fn quadric_from_plane_zero_at_plane_point() {
let s = 1.0 / 3f64.sqrt();
let q = Quadric::from_plane(s, s, s, 0.0);
assert!(q.evaluate([0.0, 0.0, 0.0]).abs() < 1e-12);
assert!(q.evaluate([1.0, -1.0, 0.0]).abs() < 1e-12);
let err = q.evaluate([1.0, 0.0, 0.0]);
assert!((err - 1.0 / 3.0).abs() < 1e-10, "err = {}", err);
}
#[test]
fn quadric_add_is_commutative() {
let q1 = Quadric::from_plane(1.0, 0.0, 0.0, 0.0);
let q2 = Quadric::from_plane(0.0, 1.0, 0.0, 0.0);
let s1 = q1.add(&q2);
let s2 = q2.add(&q1);
assert_eq!(s1.data, s2.data);
}
#[test]
fn quadric_find_optimal_position_singular_returns_none() {
let q = Quadric::from_plane(1.0, 0.0, 0.0, 0.0);
assert!(q.find_optimal_position().is_none());
}
#[test]
fn quadric_find_optimal_position_two_planes() {
let q1 = Quadric::from_plane(1.0, 0.0, 0.0, 0.0);
let q2 = Quadric::from_plane(0.0, 1.0, 0.0, 0.0);
let q = q1.add(&q2);
assert!(q.find_optimal_position().is_none());
}
#[test]
fn quadric_find_optimal_position_three_planes() {
let q1 = Quadric::from_plane(1.0, 0.0, 0.0, 0.0);
let q2 = Quadric::from_plane(0.0, 1.0, 0.0, 0.0);
let q3 = Quadric::from_plane(0.0, 0.0, 1.0, 0.0);
let q = q1.add(&q2).add(&q3);
let p = q.find_optimal_position().expect("三正交平面应有唯一最优解");
assert!(p[0].abs() < 1e-10);
assert!(p[1].abs() < 1e-10);
assert!(p[2].abs() < 1e-10);
}
#[test]
fn decimate_icosphere2_to_80_faces() {
let mut mesh = build_icosphere(2); assert_eq!(mesh.face_count(), 320);
let removed = decimate_qem(&mut mesh, 80).expect("简化应成功");
assert!(removed > 0);
let f = mesh.face_count();
assert!(f <= 80, "面数 {} 应 ≤ 80", f);
let v = mesh.vertex_count();
assert!(v <= 42 + 2, "顶点数 {} 应 ≤ 44(F≈80 → V≈42)", v);
assert!(validate_mesh(&mesh).is_ok(), "简化后网格应通过拓扑校验");
}
#[test]
fn decimate_icosphere2_to_tetrahedron() {
let mut mesh = build_icosphere(2); let removed = decimate_qem(&mut mesh, 4).expect("简化应成功");
assert!(removed > 0);
let f = mesh.face_count();
assert!(f <= 8, "面数 {} 应 ≤ 8(接近四面体)", f);
assert!(validate_mesh(&mesh).is_ok(), "极端简化后网格应通过校验");
assert!(mesh.vertex_count() >= 4, "至少 4 个顶点");
}
#[test]
fn decimate_flat_plane_all_zero_cost() {
let vertices = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
];
let faces = [[0, 1, 2], [1, 3, 2]];
let mut mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
assert_eq!(mesh.face_count(), 2);
let removed = decimate_qem(&mut mesh, 0).expect("简化应成功");
assert_eq!(removed, 2, "应移除 2 面");
assert_eq!(mesh.face_count(), 0, "面数应为 0");
assert_eq!(mesh.vertex_count(), 3, "顶点数应为 3");
}
#[test]
fn decimate_boundary_edges_not_collapsed() {
let vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = [[0, 1, 2]];
let mut mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
assert_eq!(mesh.face_count(), 1);
let removed = decimate_qem(&mut mesh, 0).expect("简化应成功");
assert_eq!(removed, 0, "边界边不应折叠,移除 0 面");
assert_eq!(mesh.face_count(), 1, "面数不变");
assert_eq!(mesh.vertex_count(), 3, "顶点数不变");
}
#[test]
fn decimate_target_geq_current_is_noop() {
let mut mesh = build_icosphere(0); let removed = decimate_qem(&mut mesh, 20).expect("简化应成功");
assert_eq!(removed, 0);
assert_eq!(mesh.face_count(), 20);
}
#[test]
fn decimate_icosphere1_half_simplification() {
let mut mesh = build_icosphere(1); let target = 40;
let removed = decimate_qem(&mut mesh, target).expect("简化应成功");
assert!(removed > 0);
let f = mesh.face_count();
assert!(f <= target, "面数 {} 应 ≤ {}", f, target);
assert!(validate_mesh(&mesh).is_ok(), "简化后网格应通过校验");
}
#[test]
fn decimate_preserves_closed_topology() {
let mut mesh = build_icosphere(1); decimate_qem(&mut mesh, 20).expect("简化应成功");
let chi = mesh.euler_characteristic();
assert_eq!(chi, 2, "闭合网格 Euler 示性数应保持为 2,实际 {}", chi);
}
#[test]
fn decimate_to_vertices_icosphere2() {
let mut mesh = build_icosphere(2); let removed = decimate_to_vertices(&mut mesh, 42).expect("简化应成功");
assert!(removed > 0);
let f = mesh.face_count();
assert!(f <= 80, "面数 {} 应 ≤ 80", f);
assert!(validate_mesh(&mesh).is_ok());
}
#[test]
fn decimate_multiple_iterations_stay_valid() {
let mut mesh = build_icosphere(1); for target in [60, 30, 10] {
decimate_qem(&mut mesh, target).expect("简化应成功");
assert!(validate_mesh(&mesh).is_ok(), "target={} 时校验失败", target);
assert!(
check_topology(&mesh).is_ok(),
"target={} 时完整校验失败",
target
);
}
}
}