use super::interner::{Interner, Vid};
use super::retriangulate::{
cmp_lex_v, coord2d_cached, earcut, edge_exists, insert_point, lex_cmp, orient2d_v, orient_ring,
tri_aabb_disjoint, tri_edges, Mesh2d, SubTri, PREFILTER_MIN,
};
use super::retriangulate_audit::pocket_rebuild_valid;
use super::Sign;
use std::collections::{BTreeMap, BTreeSet};
pub(crate) fn recover_subsegment(mesh: &mut Mesh2d, it: &Interner, a: Vid, b: Vid) {
if edge_exists(mesh, a, b) {
return;
}
let audit_before = mesh.audit_needed;
mesh.audit_needed = true;
let (axis, w0) = (mesh.axis, mesh.w0);
let tri_crosses = |tri: SubTri| {
let s = [
orient2d_v(it, a, b, tri[0], axis),
orient2d_v(it, a, b, tri[1], axis),
orient2d_v(it, a, b, tri[2], axis),
];
for k in 0..3 {
let (su, sv) = (s[k], s[(k + 1) % 3]);
if su == Sign::Zero || sv == Sign::Zero || su == sv {
continue; }
let (u, v) = (tri[k], tri[(k + 1) % 3]);
let s3 = orient2d_v(it, u, v, a, axis);
if s3 == Sign::Zero {
continue;
}
let s4 = orient2d_v(it, u, v, b, axis);
if s4 != Sign::Zero && s3 != s4 {
return true;
}
}
false
};
let ab_box: Option<[f64; 4]> = if mesh.tris.len() > PREFILTER_MIN {
match (
coord2d_cached(it, a, axis, &mut mesh.coords),
coord2d_cached(it, b, axis, &mut mesh.coords),
) {
(Some(a2), Some(b2)) => Some([
a2[0].min(b2[0]),
a2[1].min(b2[1]),
a2[0].max(b2[0]),
a2[1].max(b2[1]),
]),
_ => None,
}
} else {
None
};
let channel: Vec<usize> = (0..mesh.tris.len())
.filter(|&ti| {
let tri = mesh.tris[ti];
if let Some(bx) = ab_box {
if tri_aabb_disjoint(it, tri, bx, axis, &mut mesh.coords) {
return false;
}
}
tri_crosses(tri)
})
.collect();
if channel.is_empty() {
return;
}
let channel_set: BTreeSet<usize> = channel.iter().copied().collect();
let mut edges: BTreeSet<(Vid, Vid)> = BTreeSet::new();
for &ti in &channel {
for e in tri_edges(mesh.tris[ti]) {
edges.insert(e);
}
}
let mut next: BTreeMap<Vid, Vid> = BTreeMap::new();
for &(u, v) in &edges {
if !edges.contains(&(v, u)) {
next.insert(u, v);
}
}
let start = if next.contains_key(&a) {
a
} else {
match next.keys().next() {
Some(&v) => v,
None => return,
}
};
let mut loop_v = vec![start];
let mut cur = match next.get(&start) {
Some(&v) => v,
None => return,
};
while cur != start {
loop_v.push(cur);
cur = match next.get(&cur) {
Some(&v) => v,
None => return,
};
if loop_v.len() > next.len() + 1 {
return; }
}
let loop_set: BTreeSet<Vid> = loop_v.iter().copied().collect();
let mut lost: Vec<Vid> = channel
.iter()
.flat_map(|&ti| mesh.tris[ti])
.filter(|v| !loop_set.contains(v))
.collect::<BTreeSet<Vid>>()
.into_iter()
.collect();
lost.sort_by(|&x, &y| lex_cmp(it, x, y)); let ia = loop_v.iter().position(|&x| x == a);
let ib = loop_v.iter().position(|&x| x == b);
let mut new_tris: Vec<[Vid; 3]> = Vec::new();
let mut fan_hub: Option<Vid> = None;
match (ia, ib) {
(Some(ia), Some(ib)) => {
let n = loop_v.len();
let rot: Vec<Vid> = (0..n).map(|k| loop_v[(ia + k) % n]).collect();
let jb = (ib + n - ia) % n;
let arc1: Vec<Vid> = rot[0..=jb].to_vec(); let mut arc2: Vec<Vid> = rot[jb..].to_vec(); arc2.push(a); for ring in [arc1, arc2] {
if ring.len() >= 3 {
let oriented = orient_ring(it, ring, axis, w0);
new_tris.extend(earcut(it, &oriented, axis, w0));
}
}
}
_ => {
let inner = if ia.is_none() { a } else { b };
let oriented = orient_ring(it, loop_v.clone(), axis, w0);
let n = oriented.len();
let star = !loop_set.contains(&inner)
&& (0..n).all(|k| {
orient2d_v(it, inner, oriented[k], oriented[(k + 1) % n], axis) == w0
});
if star {
for k in 0..n {
new_tris.push([inner, oriented[k], oriented[(k + 1) % n]]);
}
fan_hub = Some(inner);
} else {
new_tris.extend(earcut(it, &oriented, axis, w0));
}
}
}
mesh.tris = mesh
.tris
.iter()
.enumerate()
.filter(|(i, _)| !channel_set.contains(i))
.map(|(_, t)| *t)
.collect();
mesh.tris.extend(new_tris);
for v in lost {
if Some(v) == fan_hub {
continue; }
insert_point(mesh, it, v);
}
if edge_exists(mesh, a, b) {
mesh.audit_needed = audit_before; }
}
pub(crate) fn between(it: &Interner, s: Vid, t: Vid, v: Vid) -> bool {
let sv = cmp_lex_v(it, s, v);
sv != Sign::Zero && sv == cmp_lex_v(it, v, t)
}
pub(crate) fn recover_via_traversal(mesh: &mut Mesh2d, it: &Interner, a: Vid, b: Vid) {
let (axis, w0) = (mesh.axis, mesh.w0);
let mut adj: BTreeMap<(Vid, Vid), Vec<usize>> = BTreeMap::new();
for (ti, t) in mesh.tris.iter().enumerate() {
for (u, v) in tri_edges(*t) {
adj.entry(if u < v { (u, v) } else { (v, u) }).or_default().push(ti);
}
}
let find_entry = |start: Vid, end: Vid| -> Option<(usize, Vid, Vid)> {
for (ti, t) in mesh.tris.iter().enumerate() {
let Some(ai) = (0..3).find(|&k| t[k] == start) else { continue };
let (u, v) = (t[(ai + 1) % 3], t[(ai + 2) % 3]);
let (su, sv) = (orient2d_v(it, start, end, u, axis), orient2d_v(it, start, end, v, axis));
let (sa, sb) = (orient2d_v(it, u, v, start, axis), orient2d_v(it, u, v, end, axis));
if su == Sign::Zero || sv == Sign::Zero || su == sv {
continue;
}
if sa == Sign::Zero || sb == Sign::Zero || sa == sb {
continue;
}
return Some(if su == w0 { (ti, u, v) } else { (ti, v, u) });
}
None
};
let (a, b, entry) = if let Some(en) = find_entry(a, b) {
(a, b, en)
} else if let Some(en) = find_entry(b, a) {
(b, a, en)
} else {
return;
};
let side = |x: Vid| orient2d_v(it, a, b, x, axis);
let (mut cur_tri, mut eu, mut ev) = entry; let mut upper = vec![a, eu];
let mut lower = vec![a, ev];
let mut crossed = vec![cur_tri];
loop {
let key = if eu < ev { (eu, ev) } else { (ev, eu) };
let Some(&nt) = adj.get(&key).and_then(|ts| ts.iter().find(|&&t| t != cur_tri)) else {
return; };
let apex = match mesh.tris[nt].iter().copied().find(|&x| x != eu && x != ev) {
Some(x) => x,
None => return,
};
crossed.push(nt);
if apex == b {
upper.push(b);
lower.push(b);
break;
}
match side(apex) {
s if s == w0 => {
upper.push(apex);
eu = apex;
}
Sign::Zero => return, _ => {
lower.push(apex);
ev = apex;
}
}
cur_tri = nt;
if crossed.len() > mesh.tris.len() {
return; }
}
let mut new_tris: Vec<SubTri> = Vec::new();
for chain in [upper, lower] {
if chain.len() >= 3 {
let ring = orient_ring(it, chain, axis, w0);
new_tris.extend(earcut(it, &ring, axis, w0));
}
}
if !pocket_rebuild_valid(&new_tris) {
return;
}
let crossed_set: BTreeSet<usize> = crossed.into_iter().collect();
mesh.tris = mesh
.tris
.iter()
.enumerate()
.filter(|(i, _)| !crossed_set.contains(i))
.map(|(_, t)| *t)
.collect();
mesh.tris.extend(new_tris);
}
pub(crate) fn enforce_constraint(mesh: &mut Mesh2d, it: &Interner, s: Vid, t: Vid) {
let axis = mesh.axis;
let verts: BTreeSet<Vid> = mesh.tris.iter().flatten().copied().collect();
let seg_box: Option<[f64; 4]> = if verts.len() > PREFILTER_MIN {
match (
coord2d_cached(it, s, axis, &mut mesh.coords),
coord2d_cached(it, t, axis, &mut mesh.coords),
) {
(Some(s2), Some(t2)) => Some([
s2[0].min(t2[0]),
s2[1].min(t2[1]),
s2[0].max(t2[0]),
s2[1].max(t2[1]),
]),
_ => None,
}
} else {
None
};
let mut on_seg: Vec<Vid> = verts
.into_iter()
.filter(|&v| {
if v == s || v == t {
return false;
}
if let Some(bx) = seg_box {
if let Some(vc) = coord2d_cached(it, v, axis, &mut mesh.coords) {
let mx = 1e-6 + vc[0].abs() * 1e-9;
let my = 1e-6 + vc[1].abs() * 1e-9;
if vc[0] < bx[0] - mx
|| vc[0] > bx[2] + mx
|| vc[1] < bx[1] - my
|| vc[1] > bx[3] + my
{
return false; }
}
}
orient2d_v(it, s, t, v, axis) == Sign::Zero && between(it, s, t, v)
})
.collect();
on_seg.sort_by(|&x, &y| lex_cmp(it, x, y));
if cmp_lex_v(it, s, t) == Sign::Positive {
on_seg.reverse(); }
let mut chain = vec![s];
chain.extend(on_seg);
chain.push(t);
for w in chain.windows(2) {
recover_subsegment(mesh, it, w[0], w[1]);
if !edge_exists(mesh, w[0], w[1]) {
recover_via_traversal(mesh, it, w[0], w[1]);
}
}
}