use std::collections::HashMap;
use geo::Coord;
pub(crate) fn snap_vertices(rings: &mut [Vec<Vec<Coord<f64>>>], tolerance: f64) {
if rings.is_empty() { return; }
let mut coords: Vec<Coord<f64>> = Vec::new();
let mut flat_idxs: Vec<Vec<Vec<usize>>> = Vec::with_capacity(rings.len());
for unit_rings in rings.iter() {
let mut unit_flat = Vec::with_capacity(unit_rings.len());
for ring in unit_rings {
let mut ring_flat = Vec::with_capacity(ring.len());
for &c in ring {
ring_flat.push(coords.len());
coords.push(c);
}
unit_flat.push(ring_flat);
}
flat_idxs.push(unit_flat);
}
let n = coords.len();
if n == 0 { return; }
let mut parent: Vec<usize> = (0..n).collect();
let mut rank: Vec<u8> = vec![0; n];
let find = |parent: &mut Vec<usize>, mut x: usize| -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]]; x = parent[x];
}
x
};
let union = |parent: &mut Vec<usize>, rank: &mut Vec<u8>, x: usize, y: usize| {
let rx = {
let mut v = x;
while parent[v] != v { parent[v] = parent[parent[v]]; v = parent[v]; }
v
};
let ry = {
let mut v = y;
while parent[v] != v { parent[v] = parent[parent[v]]; v = parent[v]; }
v
};
if rx == ry { return; }
match rank[rx].cmp(&rank[ry]) {
std::cmp::Ordering::Less => parent[rx] = ry,
std::cmp::Ordering::Greater => parent[ry] = rx,
std::cmp::Ordering::Equal => { parent[ry] = rx; rank[rx] += 1; }
}
};
struct Edge { unit: usize, i: usize, j: usize }
let mut edges: Vec<Edge> = Vec::new();
for (u, unit_rings) in rings.iter().enumerate() {
for (r, ring) in unit_rings.iter().enumerate() {
let len = ring.len();
for pos in 0..len {
let next_pos = (pos + 1) % len;
edges.push(Edge {
unit: u,
i: flat_idxs[u][r][pos],
j: flat_idxs[u][r][next_pos],
});
}
}
}
let tol_sq = tolerance * tolerance;
let near = |a: Coord<f64>, b: Coord<f64>| -> bool {
let dx = a.x - b.x;
let dy = a.y - b.y;
dx * dx + dy * dy <= tol_sq
};
let mut vertex_unit: Vec<usize> = vec![0usize; n];
for (u, unit_rings) in rings.iter().enumerate() {
for (r, ring) in unit_rings.iter().enumerate() {
for pos in 0..ring.len() {
vertex_unit[flat_idxs[u][r][pos]] = u;
}
}
}
let inv_tol = 1.0 / tolerance;
let cell_of = |c: Coord<f64>| -> (i64, i64) {
((c.x * inv_tol).floor() as i64, (c.y * inv_tol).floor() as i64)
};
let mut vertex_grid: HashMap<(i64, i64), Vec<usize>> = HashMap::new();
for vi in 0..n {
vertex_grid.entry(cell_of(coords[vi])).or_default().push(vi);
}
let mut edges_from: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut edges_to: Vec<Vec<usize>> = vec![Vec::new(); n];
for (ei, e) in edges.iter().enumerate() {
edges_from[e.i].push(ei);
edges_to[e.j].push(ei);
}
for ei in 0..edges.len() {
let (e1_unit, e1_i, e1_j) = (edges[ei].unit, edges[ei].i, edges[ei].j);
let p_i = coords[e1_i];
let p_j = coords[e1_j];
let (cx, cy) = cell_of(p_i);
for dx in -1i64..=1 {
for dy in -1i64..=1 {
let Some(candidates) = vertex_grid.get(&(cx + dx, cy + dy)) else { continue };
for &k in candidates {
if vertex_unit[k] == e1_unit { continue; }
if !near(p_i, coords[k]) { continue; }
let from_k: Vec<usize> = edges_from[k].clone();
for ej in from_k {
let l = edges[ej].j;
if near(p_j, coords[l]) {
union(&mut parent, &mut rank, e1_i, k);
union(&mut parent, &mut rank, e1_j, l);
}
}
let to_k: Vec<usize> = edges_to[k].clone();
for ej in to_k {
let m = edges[ej].i;
if near(p_j, coords[m]) {
union(&mut parent, &mut rank, e1_i, k);
union(&mut parent, &mut rank, e1_j, m);
}
}
}
}
}
}
let canonical: Vec<Coord<f64>> = (0..n)
.map(|i| coords[find(&mut parent, i)])
.collect();
for (u, unit_rings) in rings.iter_mut().enumerate() {
for (r, ring) in unit_rings.iter_mut().enumerate() {
for (pos, coord) in ring.iter_mut().enumerate() {
*coord = canonical[flat_idxs[u][r][pos]];
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c(x: f64, y: f64) -> Coord<f64> { Coord { x, y } }
fn ring(pts: &[(f64, f64)]) -> Vec<Coord<f64>> {
pts.iter().map(|&(x, y)| c(x, y)).collect()
}
#[test]
fn empty_input_does_not_panic() {
snap_vertices(&mut [], 1e-7);
}
#[test]
fn single_unit_no_neighbours_unchanged() {
let original = ring(&[(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)]);
let mut rings = vec![vec![original.clone()]];
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[0][0], original);
}
#[test]
fn exact_coincident_vertices_are_stable() {
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)])],
vec![ring(&[(1.0, 0.0), (0.0, 0.0), (0.5, -1.0)])],
];
let before_a = rings[0][0][0];
let before_b = rings[1][0][1]; snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[0][0][0], before_a);
assert_eq!(rings[1][0][1], before_b);
assert_eq!(rings[0][0][0], rings[1][0][1]);
}
#[test]
fn near_coincident_edge_is_snapped() {
let eps = 5e-8_f64; let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)])],
vec![ring(&[(1.0 + eps, eps), (eps, eps), (0.5, -1.0)])],
];
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[0][0][0], rings[1][0][1]);
assert_eq!(rings[0][0][1], rings[1][0][0]);
}
#[test]
fn snapped_coordinates_are_the_root_vertex() {
let eps = 5e-8_f64;
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)])],
vec![ring(&[(1.0 + eps, eps), (eps, eps), (0.5, -1.0)])],
];
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[0][0][0], c(0.0, 0.0));
assert_eq!(rings[0][0][1], c(1.0, 0.0));
assert_eq!(rings[1][0][1], c(0.0, 0.0));
assert_eq!(rings[1][0][0], c(1.0, 0.0));
}
#[test]
fn beyond_tolerance_not_snapped() {
let eps = 2e-7_f64;
let orig_b0 = c(1.0 + eps, 0.0);
let orig_b1 = c(eps, 0.0);
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)])],
vec![ring(&[(orig_b0.x, orig_b0.y), (orig_b1.x, orig_b1.y), (0.5, -1.0)])],
];
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[1][0][0], orig_b0);
assert_eq!(rings[1][0][1], orig_b1);
}
#[test]
fn same_unit_close_vertices_not_snapped() {
let eps = 5e-8_f64;
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (eps, eps), (1.0, 0.0)])],
];
let before = rings[0][0].clone();
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[0][0][0], before[0]);
assert_eq!(rings[0][0][1], before[1]);
}
#[test]
fn isolated_vertex_close_but_no_matching_edge() {
let eps = 5e-8_f64;
let orig_b0 = c(eps, eps);
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)])],
vec![ring(&[(orig_b0.x, orig_b0.y), (5.0, 0.0), (5.0, 5.0)])],
];
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[1][0][0], orig_b0);
}
#[test]
fn two_shared_vertices_both_snapped() {
let eps = 5e-8_f64;
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)])],
vec![ring(&[(2.0, 0.0), (2.0, 1.0), (1.0 + eps, 1.0 + eps), (1.0 + eps, eps)])],
];
snap_vertices(&mut rings, 1e-7);
assert_eq!(rings[0][0][1], rings[1][0][3]);
assert_eq!(rings[0][0][2], rings[1][0][2]);
}
#[test]
fn third_unit_also_snapped_transitively() {
let eps = 5e-8_f64;
let mut rings = vec![
vec![ring(&[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)])],
vec![ring(&[(1.0 + eps, eps), (eps, eps), (0.0, -1.0)])],
vec![ring(&[(eps, eps), (1.0 + eps, eps), (1.0, -1.0)])],
];
snap_vertices(&mut rings, 1e-7);
let a = rings[0][0][0];
let b = rings[1][0][1]; let c_v = rings[2][0][0]; assert_eq!(a, b);
assert_eq!(a, c_v);
}
}