use std::ops::{Add, Div, Mul, Sub};
use ahash::AHashSet;
use smallvec::SmallVec;
use crate::{
geometry::{point::*, vector::*},
impl_mesh,
mesh::basic_types::*,
numeric::scalar::Scalar,
};
#[derive(Debug)]
pub struct CollapsePlan<T: Scalar, const N: usize> {
pub v_keep: usize,
pub v_gone: usize,
pub p_star: Point<T, N>,
}
#[derive(Debug)]
pub enum CollapseReject {
NotAdjacent,
BorderForbidden,
LinkCondition, DuplicateEdges, TwoGon, DegenerateFace, NormalFlip, InternalError, }
pub struct CollapseOpts<T> {
pub area_eps2: T,
pub forbid_border: bool,
pub forbid_normal_flip: bool,
}
impl<T: Scalar> Default for CollapseOpts<T>
where
T: Scalar,
for<'a> &'a T: Sub<&'a T, Output = T>
+ Mul<&'a T, Output = T>
+ Add<&'a T, Output = T>
+ Div<&'a T, Output = T>,
{
fn default() -> Self {
let tol = T::tolerance();
Self {
area_eps2: &tol * &tol,
forbid_border: false,
forbid_normal_flip: true,
}
}
}
pub trait Placement<T: Scalar, const N: usize> {
fn place(&self, mesh: &Mesh<T, N>, v0: usize, v1: usize) -> Point<T, N>;
}
pub struct Midpoint;
impl<T: Scalar, const N: usize> Placement<T, N> for Midpoint
where
Vector<T, N>: VectorOps<T, N>,
Point<T, N>: PointOps<T, N, Vector = Vector<T, N>>,
for<'a> &'a T: core::ops::Add<&'a T, Output = T>
+ core::ops::Sub<&'a T, Output = T>
+ core::ops::Mul<&'a T, Output = T>
+ core::ops::Div<&'a T, Output = T>,
{
fn place(&self, mesh: &Mesh<T, N>, v0: usize, v1: usize) -> Point<T, N> {
let p0 = mesh.vertices[v0].position.clone();
let p1 = mesh.vertices[v1].position.clone();
(&p0 + &p1).as_vector().scale(&T::from_num_den(1, 2)).0
}
}
impl_mesh! {
#[inline]
fn rings_adjacency_ok(&self, pr: &PairRing, v0: usize, v1: usize) -> bool {
let he01 = match self.half_edge_between(v0, v1) { Some(h) => h, None => return false };
let he10 = match self.half_edge_between(v1, v0) { Some(h) => h, None => return false };
let i0 = match pr.idx_v1_in_ring0 { Some(i) => i, None => return false };
let i1 = match pr.idx_v0_in_ring1 { Some(i) => i, None => return false };
if i0 >= pr.ring0.halfedges_ccw.len() || i1 >= pr.ring1.halfedges_ccw.len() { return false; }
if pr.ring0.halfedges_ccw[i0] != he01 { return false; }
if pr.ring1.halfedges_ccw[i1] != he10 { return false; }
if pr.ring0.neighbors_ccw[i0] != v1 { return false; }
if pr.ring1.neighbors_ccw[i1] != v0 { return false; }
true
}
#[inline]
fn neighbor_sets_excluding_endpoints(
&self,
pr: &PairRing,
v0: usize,
v1: usize,
) -> (AHashSet<usize>, AHashSet<usize>) {
let set0: AHashSet<_> = pr.ring0.neighbors_ccw
.iter().copied()
.filter(|&x| x != v1 && x != v0) .collect();
let set1: AHashSet<_> = pr.ring1.neighbors_ccw
.iter().copied()
.filter(|&x| x != v0 && x != v1)
.collect();
(set0, set1)
}
#[inline]
fn opposites_count(&self, pr: &PairRing) -> usize {
(pr.opposite_a.is_some() as usize) + (pr.opposite_b.is_some() as usize)
}
pub fn check_link_condition_triangle(&self, v0: usize, v1: usize) -> bool {
let Some(pr) = self.ring_pair(v0, v1) else { return false; };
if !self.rings_adjacency_ok(&pr, v0, v1) { return false; }
let (set0, set1) = self.neighbor_sets_excluding_endpoints(&pr, v0, v1);
let common: AHashSet<_> = set0.intersection(&set1).copied().collect();
let mut expected = AHashSet::new();
if let Some(a) = pr.opposite_a { expected.insert(a); }
if let Some(b) = pr.opposite_b { expected.insert(b); }
match self.opposites_count(&pr) {
2 => common == expected, 1 => common == expected, _ => false, }
}
pub fn would_create_duplicate_edges(&self, v0: usize, v1: usize) -> bool {
let Some(pr) = self.ring_pair(v0, v1) else { return true; };
if !self.rings_adjacency_ok(&pr, v0, v1) { return true; }
let (set0, set1) = self.neighbor_sets_excluding_endpoints(&pr, v0, v1);
let mut inter: AHashSet<_> = set0.intersection(&set1).copied().collect();
if let Some(a) = pr.opposite_a { inter.remove(&a); }
if let Some(b) = pr.opposite_b { inter.remove(&b); }
!inter.is_empty()
}
pub fn would_create_2gons(&self, v0: usize, v1: usize) -> bool {
let Some(pr) = self.ring_pair(v0, v1) else { return true; };
if !self.rings_adjacency_ok(&pr, v0, v1) { return true; }
match (pr.opposite_a, pr.opposite_b) {
(Some(a), Some(b)) => a == b, (Some(_), None) | (None, Some(_)) => false, _ => true, }
}
pub fn verify_collapse_prereqs(
&self,
v0: usize,
v1: usize,
placement: &impl Placement<T, N>,
opts: &CollapseOpts<T>,
) -> Result<Point<T, N>, CollapseReject>
where
Vector<T, N>: VectorOps<T, N, Cross = Vector<T, N>>,
{
let Some(pr) = self.ring_pair(v0, v1) else {
return Err(CollapseReject::NotAdjacent);
};
if !self.rings_adjacency_ok(&pr, v0, v1) {
return Err(CollapseReject::NotAdjacent);
}
let opp_count = self.opposites_count(&pr);
if opts.forbid_border && opp_count == 1 {
return Err(CollapseReject::BorderForbidden);
}
if !self.check_link_condition_triangle(v0, v1) {
return Err(CollapseReject::LinkCondition);
}
if self.would_create_duplicate_edges(v0, v1) {
return Err(CollapseReject::DuplicateEdges);
}
if self.would_create_2gons(v0, v1) {
return Err(CollapseReject::TwoGon);
}
let p_star = placement.place(self, v0, v1);
let survivors = self.surviving_faces_after_collapse(v0, v1);
for &f in &survivors {
let a2x4 = self.tri_area2_after_move_face(f, v0, v1, &p_star);
let eps = T::tolerance();
if a2x4 <= &(&eps * &eps) * &(&eps * &eps) {
return Err(CollapseReject::DegenerateFace);
}
}
Ok(p_star)
}
fn surviving_faces_after_collapse(&self, v_keep: usize, v_gone: usize) -> Vec<usize> {
let s0 = self.incident_faces(v_keep);
let s1 = self.incident_faces(v_gone);
let mut out = Vec::new();
for &f in s0.union(&s1) {
let [a, b, c] = self.face_vertices(f);
let has_keep = a == v_keep || b == v_keep || c == v_keep;
let has_gone = a == v_gone || b == v_gone || c == v_gone;
if has_keep && has_gone { continue; } out.push(f);
}
out
}
fn tri_area2_after_move_face(
&self,
f: usize,
v_keep: usize,
v_gone: usize,
p_star: &Point<T, N>,
) -> T
where
Vector<T, N>: VectorOps<T, N, Cross = Vector<T, N>>,
{
let [i, j, k] = self.face_vertices(f);
let pos = |idx: usize| -> &Point<T, N> {
if idx == v_keep || idx == v_gone { p_star } else { &self.vertices[idx].position }
};
let pa = pos(i);
let pb = pos(j);
let pc = pos(k);
let ab = (pb - pa).as_vector();
let ac = (pc - pa).as_vector();
let n = ab.cross(&ac);
n.dot(&n) }
pub fn collapse_edge_begin_he(
&self,
he_ab: usize,
placement: &impl Placement<T, N>,
opts: &CollapseOpts<T>,
) -> Result<CollapsePlan<T, N>, CollapseReject> where Vector<T, N>: VectorOps<T, N, Cross = Vector<T, N>>,
{
if he_ab >= self.half_edges.len() || self.half_edges[he_ab].removed {
return Err(CollapseReject::NotAdjacent);
}
let a = self.source(he_ab);
let b = self.target(he_ab);
let p_star = self.verify_collapse_prereqs(a, b, placement, opts)?;
Ok(CollapsePlan {
v_keep: a,
v_gone: b,
p_star,
})
}
pub fn collapse_edge_begin_vertices(
&self,
v_keep: usize,
v_gone: usize,
placement: &impl Placement<T, N>,
opts: &CollapseOpts<T>,
) -> Result<CollapsePlan<T, N>, CollapseReject> where Vector<T, N>: VectorOps<T, N, Cross = Vector<T, N>>,
{
if self.ring_pair(v_keep, v_gone).is_none() {
return Err(CollapseReject::NotAdjacent);
}
let p_star = self.verify_collapse_prereqs(v_keep, v_gone, placement, opts)?;
Ok(CollapsePlan {
v_keep,
v_gone,
p_star,
})
}
#[inline]
fn he_from(&self, he: usize) -> usize {
self.half_edges[self.half_edges[he].prev].vertex
}
#[inline]
fn edge_map_insert_bidir(&mut self, src: usize, dst: usize, h: usize) {
let t = self.half_edges[h].twin;
self.edge_map.insert((src, dst), h);
self.edge_map.insert((dst, src), t);
}
#[inline]
fn collect_all_neighbors_fast(&self, u: usize, v: usize) -> AHashSet<usize> {
let mut neighbors = AHashSet::with_capacity(16);
if let Some(start) = self.vertices[u].half_edge {
let mut current = start;
loop {
let target = self.half_edges[current].vertex;
if target != u && target != v {
neighbors.insert(target);
}
let twin = self.half_edges[current].twin;
if twin == usize::MAX { break; }
current = self.half_edges[twin].next;
if current == start { break; }
}
}
if let Some(start) = self.vertices[v].half_edge {
let mut current = start;
loop {
let target = self.half_edges[current].vertex;
if target != u && target != v {
neighbors.insert(target);
}
let twin = self.half_edges[current].twin;
if twin == usize::MAX { break; }
current = self.half_edges[twin].next;
if current == start { break; }
}
}
neighbors
}
pub fn collapse_edge_commit(&mut self, plan: CollapsePlan<T, N>) -> Result<(), CollapseReject> {
let u = plan.v_keep;
let v = plan.v_gone;
if u == v { return Err(CollapseReject::InternalError); }
let he_uv = match self.edge_map.get(&(u, v)) {
Some(&h) => h,
None => return Err(CollapseReject::InternalError),
};
let he_vu = self.half_edges[he_uv].twin;
if he_vu == usize::MAX || self.half_edges[he_vu].removed {
return Err(CollapseReject::InternalError);
}
let he_va = self.half_edges[he_uv].next; let he_au = self.half_edges[he_uv].prev; let he_ub = self.half_edges[he_vu].next; let he_bv = self.half_edges[he_vu].prev;
let f_left = self.half_edges[he_uv].face;
let f_right = self.half_edges[he_vu].face;
let left_exists = f_left.is_some() && !self.faces[f_left.unwrap()].removed;
let right_exists = f_right.is_some() && !self.faces[f_right.unwrap()].removed;
let splice_l_from = if left_exists { self.half_edges[he_au].prev } else { usize::MAX };
let splice_l_to = if left_exists { self.half_edges[he_va].next } else { usize::MAX };
let splice_r_from = if right_exists { self.half_edges[he_bv].prev } else { usize::MAX };
let splice_r_to = if right_exists { self.half_edges[he_ub].next } else { usize::MAX };
let remove_set = [he_uv, he_vu, he_va, he_au, he_ub, he_bv];
let affected_u = self.collect_incident_half_edges(u);
let affected_v = self.collect_incident_half_edges(v);
let mut all_affected = affected_u;
all_affected.extend_from_slice(&affected_v);
let mut neighbor_flag = self.collect_all_neighbors_fast(u, v);
neighbor_flag.remove(&u);
neighbor_flag.remove(&v);
self.clear_edge_map_incident(u, v);
for hid in affected_v {
let he = &self.half_edges[hid];
if he.removed { continue; }
if self.in_remove_set(hid, &remove_set) { continue; }
if he.vertex == v {
self.half_edges[hid].vertex = u;
}
}
if left_exists {
self.half_edges[splice_l_from].next = splice_l_to;
self.half_edges[splice_l_to].prev = splice_l_from;
}
if right_exists {
self.half_edges[splice_r_from].next = splice_r_to;
self.half_edges[splice_r_to].prev = splice_r_from;
}
if left_exists { self.faces[f_left.unwrap()].removed = true; }
if right_exists { self.faces[f_right.unwrap()].removed = true; }
for &h in &remove_set {
let twin = self.half_edges[h].twin;
if twin != usize::MAX && !self.half_edges[twin].removed {
self.half_edges[twin].twin = usize::MAX;
}
self.half_edges[h].removed = true;
self.half_edges[h].twin = usize::MAX;
self.half_edges[h].next = h;
self.half_edges[h].prev = h;
self.half_edges[h].face = None;
}
self.vertices[u].position = plan.p_star;
self.vertices[v].removed = true;
self.vertices[v].half_edge = None;
use ahash::AHashMap;
struct PairDir { d0: Vec<usize>, d1: Vec<usize> }
let mut buckets: AHashMap<(usize,usize), PairDir> = AHashMap::new();
for &hid in &all_affected {
let he = &self.half_edges[hid];
if he.removed { continue; }
if self.in_remove_set(hid, &remove_set) { continue; }
let src = self.he_from(hid);
let dst = he.vertex;
let (a, b) = (src.min(dst), src.max(dst));
let entry = buckets.entry((a, b)).or_insert(PairDir { d0: Vec::new(), d1: Vec::new() });
if src == a {
entry.d0.push(hid);
} else {
entry.d1.push(hid);
}
}
for ((a, b), pd) in &buckets {
let h_ab = pd.d0.first().copied();
let h_ba = pd.d1.first().copied();
match (h_ab, h_ba) {
(Some(h_ab), Some(h_ba)) => {
self.half_edges[h_ab].twin = h_ba;
self.half_edges[h_ba].twin = h_ab;
self.edge_map_insert_bidir(*a, *b, h_ab);
}
(Some(h_ab), None) => {
let src = self.he_from(h_ab);
let dst = self.half_edges[h_ab].vertex;
self.edge_map.insert((src, dst), h_ab);
}
(None, Some(h_ba)) => {
let src = self.he_from(h_ba);
let dst = self.half_edges[h_ba].vertex;
self.edge_map.insert((src, dst), h_ba);
}
_ => {}
}
for &h in pd.d0.iter().skip(1) {
self.half_edges[h].removed = true;
}
for &h in pd.d1.iter().skip(1) {
self.half_edges[h].removed = true;
}
}
for ((a,b), pd) in buckets {
let h_ab = pd.d0.first().copied();
let h_ba = pd.d1.first().copied();
match (h_ab, h_ba) {
(Some(h_ab), Some(h_ba)) => {
self.half_edges[h_ab].twin = h_ba;
self.half_edges[h_ba].twin = h_ab;
self.edge_map_insert_bidir(a, b, h_ab);
}
(Some(h_ab), None) => {
let src = self.he_from(h_ab); let dst = self.half_edges[h_ab].vertex;
self.edge_map.insert((src,dst), h_ab);
}
(None, Some(h_ba)) => {
let src = self.he_from(h_ba); let dst = self.half_edges[h_ba].vertex;
self.edge_map.insert((src,dst), h_ba);
}
_ => {}
}
for &h in pd.d0.iter().skip(1) {
self.half_edges[h].removed = true;
}
for &h in pd.d1.iter().skip(1) {
self.half_edges[h].removed = true;
}
}
let mut fixed_vertices = AHashSet::new();
for &hid in &all_affected {
let he = &self.half_edges[hid];
if he.removed { continue; }
let vid = self.he_from(hid);
if self.vertices[vid].removed || fixed_vertices.contains(&vid) {
continue;
}
let needs_update = self.vertices[vid].half_edge
.map(|h| h >= self.half_edges.len() ||
self.half_edges[h].removed ||
self.he_from(h) != vid)
.unwrap_or(true);
if needs_update {
self.vertices[vid].half_edge = Some(hid);
}
fixed_vertices.insert(vid);
}
for he in &all_affected {
let vid = self.half_edges[*he].vertex;
if self.vertices[vid].removed { continue; }
let mut needs_update = false;
if let Some(h) = self.vertices[vid].half_edge {
if h >= self.half_edges.len() ||
self.half_edges[h].removed ||
self.he_from(h) != vid {
needs_update = true;
}
} else {
needs_update = true;
}
if needs_update {
self.vertices[vid].half_edge = None;
for (hid, he) in self.half_edges.iter().enumerate() {
if !he.removed && self.he_from(hid) == vid {
self.vertices[vid].half_edge = Some(hid);
break;
}
}
}
}
Ok(())
}
#[inline(always)]
fn in_remove_set(&self, h: usize, set: &[usize;6]) -> bool {
for &x in set { if x == h { return true; } }
false
}
#[inline]
fn clear_edge_map_incident(&mut self, u: usize, v: usize) {
self.edge_map.retain(|&(a, b), _| a != u && a != v && b != u && b != v);
}
pub fn collapse_edge(
&mut self,
vertex_to_keep: usize,
vertex_to_remove: usize,
) -> Result<(), CollapseReject>
where
Vector<T, N>: VectorOps<T, N, Cross = Vector<T, N>>,
{
let opts = CollapseOpts::default();
let placement = Midpoint;
let plan = self.collapse_edge_begin_vertices(vertex_to_keep, vertex_to_remove, &placement, &opts);
if let Ok(plan) = plan {
return self.collapse_edge_commit(plan).map_err(|_| CollapseReject::NotAdjacent);
} else {
return Err(plan.err().unwrap());
}
}
fn collect_incident_half_edges(&self, vertex: usize) -> SmallVec<[usize; 16]> {
let mut incident = SmallVec::new();
let Some(start_he) = self.vertices[vertex].half_edge else {
return incident;
};
let mut current = start_he;
loop {
incident.push(current);
let twin = self.half_edges[current].twin;
if twin == usize::MAX { break; }
incident.push(twin);
current = self.half_edges[twin].next;
if current == start_he { break; }
}
incident
}
}