use super::interner::{Interner, Vid};
use super::rational::{point_of, tri_area2};
use super::retriangulate::{edge_exists, orient2d_v, tri_edges, Mesh2d, SubTri};
use super::retriangulate_recover::{recover_subsegment, recover_via_traversal};
use super::{DropAxis, ImplicitPoint, Sign};
use num_rational::BigRational;
use num_traits::Zero;
use std::collections::BTreeMap;
fn e2(x: f64, y: f64) -> ImplicitPoint {
ImplicitPoint::Explicit([x, y, 0.0])
}
fn pinch_mesh(it: &mut Interner) -> (Mesh2d, Vid, Vid) {
let a = it.intern(e2(0.0, 0.0));
let b = it.intern(e2(10.0, 0.0));
let p = it.intern(e2(5.0, 2.0));
let l1 = it.intern(e2(2.0, 1.0));
let l2 = it.intern(e2(2.5, -1.0));
let y1 = it.intern(e2(4.0, 0.5));
let y2 = it.intern(e2(6.0, 0.5));
let l4 = it.intern(e2(7.5, -1.0));
let l5 = it.intern(e2(8.0, 1.0));
let l6 = it.intern(e2(5.0, 4.0));
let b1 = it.intern(e2(3.0, -3.0));
let b2 = it.intern(e2(7.0, -3.0));
let tris: Vec<SubTri> = vec![
[p, l6, l1],
[p, l1, l2],
[p, l2, y1],
[p, y1, y2], [p, y2, l4],
[p, l4, l5],
[p, l5, l6],
[a, l2, l1],
[a, b1, l2],
[b1, y1, l2],
[b1, y2, y1],
[b1, b2, y2],
[b2, l4, y2],
[b2, b, l4],
[b, l5, l4],
];
let mesh = Mesh2d {
tris,
axis: DropAxis::Z,
w0: Sign::Positive,
unrecovered: 0,
audit_needed: false,
coords: BTreeMap::new(),
};
(mesh, a, b)
}
fn area_sum(it: &Interner, tris: &[SubTri], axis: DropAxis) -> BigRational {
let pt = |v: Vid| point_of(it.get(v));
tris.iter().fold(BigRational::zero(), |acc, &t| {
acc + tri_area2(&pt(t[0]), &pt(t[1]), &pt(t[2]), axis)
})
}
#[test]
fn traversal_recovers_figure8_pinch_with_exact_coverage() {
let mut it = Interner::new();
let (mut mesh, a, b) = pinch_mesh(&mut it);
let axis = mesh.axis;
let w0 = mesh.w0;
let before = area_sum(&it, &mesh.tris, axis);
assert!(
!edge_exists(&mesh, a, b),
"precondition: a-b not yet an edge"
);
let mut probe = Mesh2d {
tris: mesh.tris.clone(),
axis,
w0,
unrecovered: 0,
audit_needed: false,
coords: BTreeMap::new(),
};
recover_subsegment(&mut probe, &it, a, b);
assert!(
!edge_exists(&probe, a, b),
"expected the pocket-split boundary walk to bail on the pinch"
);
assert!(
probe.audit_needed,
"bailing recover_subsegment must request the audit"
);
recover_via_traversal(&mut mesh, &it, a, b);
assert!(edge_exists(&mesh, a, b), "traversal failed to recover a-b");
for t in &mesh.tris {
assert!(
t[0] != t[1] && t[1] != t[2] && t[0] != t[2],
"Vid-degenerate triangle emitted: {t:?}"
);
}
for &t in &mesh.tris {
assert_eq!(
orient2d_v(&it, t[0], t[1], t[2], axis),
w0,
"triangle not oriented w0: {t:?}"
);
}
let after = area_sum(&it, &mesh.tris, axis);
assert_eq!(
after, before,
"coverage changed: overlap or hole in the rebuilt pocket"
);
let mut directed: Vec<(Vid, Vid)> = Vec::new();
for &t in &mesh.tris {
for e in tri_edges(t) {
directed.push(e);
}
}
let n = directed.len();
directed.sort_unstable();
directed.dedup();
assert_eq!(
directed.len(),
n,
"a directed edge appears twice: overlapping triangles"
);
}