use super::CONFORM_TOL;
use nalgebra::{Point2, Point3, Vector3};
pub(super) fn snap_near_duplicates(
ring: &mut [Point2<f64>],
cands: &[Point2<f64>],
origin: Point3<f64>,
u_axis: Vector3<f64>,
v_axis: Vector3<f64>,
) -> bool {
let n = ring.len();
if n < 3 || cands.is_empty() {
return false;
}
let lift = |p: Point2<f64>| -> Point3<f64> { origin + u_axis * p.x + v_axis * p.y };
let orig: Vec<Point2<f64>> = ring.to_vec();
let mut chosen: Vec<Option<Point2<f64>>> = vec![None; n];
for (i, &v) in orig.iter().enumerate() {
let mut best: Option<(f64, Point2<f64>)> = None;
for &q in cands {
let dx = q.x - v.x;
let dy = q.y - v.y;
if dx == 0.0 && dy == 0.0 {
continue; }
if dx.abs() > CONFORM_TOL || dy.abs() > CONFORM_TOL {
continue;
}
let d2 = dx * dx + dy * dy;
let closer = match best {
Some((bd, _)) => d2 < bd,
None => true,
};
if closer {
best = Some((d2, q));
}
}
let Some((_, q)) = best else { continue };
let v_abs = lift(v);
let q_abs = lift(q);
if v_abs.x as f32 == q_abs.x as f32
&& v_abs.y as f32 == q_abs.y as f32
&& v_abs.z as f32 == q_abs.z as f32
{
continue;
}
chosen[i] = Some(q);
}
let near = |p: Point2<f64>, q: Point2<f64>| {
(p.x - q.x).abs() <= CONFORM_TOL && (p.y - q.y).abs() <= CONFORM_TOL
};
let mut snapped = false;
for i in 0..n {
let Some(q) = chosen[i] else { continue };
if (0..n).any(|j| j != i && near(orig[j], q)) {
continue; }
ring[i] = q;
snapped = true;
}
snapped
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(non_snake_case)]
fn ORIGIN() -> Point3<f64> {
Point3::new(0.0, 0.0, 0.0)
}
#[allow(non_snake_case)]
fn U_AXIS() -> Vector3<f64> {
Vector3::new(1.0, 0.0, 0.0)
}
#[allow(non_snake_case)]
fn V_AXIS() -> Vector3<f64> {
Vector3::new(0.0, 1.0, 0.0)
}
fn snap(ring: &mut [Point2<f64>], cands: &[Point2<f64>]) -> bool {
snap_near_duplicates(ring, cands, ORIGIN(), U_AXIS(), V_AXIS())
}
#[test]
fn snaps_a_near_duplicate_onto_the_candidate() {
let mut ring = vec![
Point2::new(0.0, 0.0),
Point2::new(2.0, 0.000_02), Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
];
assert!(snap(&mut ring, &[Point2::new(2.0, 0.0)]));
assert_eq!(ring[1], Point2::new(2.0, 0.0));
assert_eq!(ring[0], Point2::new(0.0, 0.0), "untouched");
assert_eq!(ring[2], Point2::new(2.0, 2.0), "untouched");
}
#[test]
fn bit_identical_and_out_of_tolerance_candidates_are_no_ops() {
let mut ring = vec![
Point2::new(0.0, 0.0),
Point2::new(2.0, 0.0),
Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
];
let before = ring.clone();
assert!(!snap(&mut ring, &[Point2::new(2.0, 0.0)]), "already exact");
assert!(
!snap(&mut ring, &[Point2::new(1.0, 1.0)]),
"not near any vertex"
);
assert!(
!snap(&mut ring, &[Point2::new(2.001, 0.0)]),
"beyond CONFORM_TOL"
);
assert_eq!(ring, before);
}
#[test]
fn refuses_a_snap_that_would_collapse_onto_a_neighbour() {
let mut ring = vec![
Point2::new(0.0, 0.0),
Point2::new(0.000_02, 0.0), Point2::new(2.0, 2.0),
];
assert!(!snap(&mut ring, &[Point2::new(0.0, 0.0)]));
assert_eq!(
ring[1],
Point2::new(0.000_02, 0.0),
"must not collapse onto neighbour"
);
}
#[test]
fn refuses_a_snap_invisible_at_f32_mesh_precision() {
let v_x = 0.329_999_958f64;
let q_x = v_x + 1.0e-12; assert_eq!(v_x as f32, q_x as f32, "must be f32-indistinguishable");
let mut ring = vec![
Point2::new(v_x, 0.0),
Point2::new(2.0, 0.0),
Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
];
assert!(!snap(&mut ring, &[Point2::new(q_x, 0.0)]));
assert_eq!(
ring[0],
Point2::new(v_x, 0.0),
"f32-invisible move must be refused"
);
}
#[test]
fn applies_a_snap_visible_at_f32_mesh_precision() {
let v_x = 0.329_999_958f64;
let q_x = v_x + 1.0e-6;
assert_ne!(
v_x as f32, q_x as f32,
"fixture must be f32-distinguishable"
);
let mut ring = vec![
Point2::new(v_x, 0.0),
Point2::new(2.0, 0.0),
Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
];
assert!(snap(&mut ring, &[Point2::new(q_x, 0.0)]));
assert_eq!(ring[0], Point2::new(q_x, 0.0));
}
#[test]
fn refuses_a_snap_invisible_at_f32_precision_only_once_lifted_to_the_georeferenced_frame() {
let origin = Point3::new(2_600_000.0, 0.0, 0.0);
let v_local = Point2::new(0.0, 0.0);
let q_local = Point2::new(9.9e-5, 0.0);
assert_ne!(q_local.x as f32, v_local.x as f32);
assert_eq!((origin.x + v_local.x) as f32, (origin.x + q_local.x) as f32);
let mut ring = vec![
v_local,
Point2::new(2.0, 0.0),
Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
];
assert!(!snap_near_duplicates(
&mut ring,
&[q_local],
origin,
U_AXIS(),
V_AXIS()
));
assert_eq!(ring[0], v_local, "f32-invisible move must be refused");
}
#[test]
fn refuses_a_snap_that_would_collapse_onto_a_non_adjacent_vertex() {
let ring0 = Point2::new(0.0, 0.0);
let ring1 = Point2::new(10.0, 0.0);
let ring2 = Point2::new(10.0, 10.0);
let ring3 = Point2::new(0.000_05, 0.000_03);
let ring4 = Point2::new(0.0, 10.0);
let pentagon = || vec![ring0, ring1, ring2, ring3, ring4];
for cands in [[ring0], [Point2::new(9.0e-5, 0.0)]] {
let mut ring = pentagon();
let moved = snap_near_duplicates(&mut ring, &cands, ORIGIN(), U_AXIS(), V_AXIS());
assert!(!moved, "must not snap ring[3] near ring[0]: {cands:?}");
assert_eq!(ring[3], ring3, "non-adjacent vertex must not move");
assert_ne!(ring[3], ring[0], "ring must not end up bit-identical");
}
}
#[test]
fn refuses_both_snaps_when_two_vertices_want_the_same_candidate() {
let mut ring = vec![
Point2::new(9.0e-5, 0.0), Point2::new(5.0, 5.0),
Point2::new(-9.0e-5, 0.0), Point2::new(5.0, -5.0),
];
let before = ring.clone();
assert!(
!snap(&mut ring, &[Point2::new(0.0, 0.0)]),
"neither vertex may take a candidate the other still sits near"
);
assert_eq!(ring, before);
}
#[test]
fn result_is_independent_of_ring_traversal_order() {
let v0 = Point2::new(0.0, 0.0);
let v1 = Point2::new(5.0, 5.0); let v2 = Point2::new(10.0, 10.0); let v3 = Point2::new(0.000_095, 0.000_06);
let c0 = Point2::new(-0.000_02, -0.000_02); let c3 = Point2::new(0.000_09, 0.0); let cands = [c0, c3];
let mut forward = vec![v0, v1, v2, v3];
snap(&mut forward, &cands);
let mut reversed = vec![v3, v2, v1, v0];
snap(&mut reversed, &cands);
let n = forward.len();
let remapped: Vec<Point2<f64>> = (0..n).map(|k| reversed[n - 1 - k]).collect();
assert_eq!(
forward, remapped,
"snap_near_duplicates must not depend on which index the ring \
traversal starts from — both describe the same ring"
);
assert_eq!(
forward,
vec![c0, v1, v2, v3],
"ring[0] snaps to its own candidate; ring[3]'s candidate is \
refused in both orders, not won"
);
}
#[test]
fn refuses_both_when_adjacent_vertices_pick_the_same_candidate() {
let a = Point2::new(0.0, 0.0);
let b = Point2::new(3.0e-5, 0.0);
let c = Point2::new(2.0, 2.0);
let d = Point2::new(0.0, 2.0);
let m = Point2::new(1.5e-5, 0.0);
let mut ring = vec![a, b, c, d];
assert!(
!snap(&mut ring, &[m]),
"both candidates tie on distance and must be refused"
);
assert_eq!(ring, vec![a, b, c, d], "neither A nor B may move");
}
#[test]
fn refuses_reuse_of_the_same_candidate_by_non_adjacent_vertices() {
let v0 = Point2::new(0.0, 0.0);
let v1 = Point2::new(10.0, 0.0);
let v2 = Point2::new(0.000_03, 0.000_02);
let v3 = Point2::new(10.0, 10.0);
let q = Point2::new(0.000_01, 0.000_01);
let mut ring = vec![v0, v1, v2, v3];
assert!(
!snap(&mut ring, &[q]),
"both non-adjacent vertices want the same candidate and neither may take it"
);
assert_eq!(
ring,
vec![v0, v1, v2, v3],
"neither ring[0] nor ring[2] may move"
);
}
}