use std::collections::{HashMap, VecDeque};
use super::quads::Quad;
use super::TopologicalComponent;
fn build_edge_index(quads: &[Quad]) -> HashMap<(usize, usize), Vec<(usize, usize)>> {
let mut idx: HashMap<(usize, usize), Vec<(usize, usize)>> = HashMap::new();
for (qi, q) in quads.iter().enumerate() {
for (k, (u, v)) in q.perimeter_edges().iter().enumerate() {
let key = if u < v { (*u, *v) } else { (*v, *u) };
idx.entry(key).or_default().push((qi, k));
}
}
idx
}
fn connected_components(
quads: &[Quad],
edge_index: &HashMap<(usize, usize), Vec<(usize, usize)>>,
) -> (Vec<u32>, u32) {
let mut comp_of = vec![u32::MAX; quads.len()];
let mut next_comp: u32 = 0;
for start in 0..quads.len() {
if comp_of[start] != u32::MAX {
continue;
}
let cid = next_comp;
next_comp += 1;
comp_of[start] = cid;
let mut q = VecDeque::new();
q.push_back(start);
while let Some(qi) = q.pop_front() {
for (u, v) in quads[qi].perimeter_edges() {
let key = if u < v { (u, v) } else { (v, u) };
if let Some(buddies) = edge_index.get(&key) {
for &(qj, _) in buddies {
if qj != qi && comp_of[qj] == u32::MAX {
comp_of[qj] = cid;
q.push_back(qj);
}
}
}
}
}
}
(comp_of, next_comp)
}
fn position_of(quad: &Quad, target: usize) -> Option<usize> {
quad.vertices.iter().position(|&v| v == target)
}
fn derive_labels(
seed_a_idx: usize,
seed_a_lbl: (i32, i32),
seed_b_lbl: (i32, i32),
outward: (i32, i32),
) -> [(i32, i32); 4] {
let mut out = [(0i32, 0i32); 4];
let a = seed_a_idx;
let b = (seed_a_idx + 1) % 4;
let c = (seed_a_idx + 2) % 4;
let d = (seed_a_idx + 3) % 4;
out[a] = seed_a_lbl;
out[b] = seed_b_lbl;
out[c] = (seed_b_lbl.0 + outward.0, seed_b_lbl.1 + outward.1);
out[d] = (seed_a_lbl.0 + outward.0, seed_a_lbl.1 + outward.1);
out
}
fn seed_labels() -> [(i32, i32); 4] {
[(0, 0), (1, 0), (1, 1), (0, 1)]
}
fn propagate(
cur_quad: &Quad,
cur_labels: &[(i32, i32); 4],
cur_edge_k: usize, nbr_quad: &Quad,
) -> Option<[(i32, i32); 4]> {
let cur_a = cur_edge_k;
let cur_b = (cur_edge_k + 1) % 4;
let cur_c = (cur_edge_k + 2) % 4;
let cur_d = (cur_edge_k + 3) % 4;
let nbr_a_pos = position_of(nbr_quad, cur_quad.vertices[cur_b])?;
let nbr_b_pos = position_of(nbr_quad, cur_quad.vertices[cur_a])?;
if (nbr_a_pos + 1) % 4 != nbr_b_pos {
return None; }
let outward = (
cur_labels[cur_b].0 - cur_labels[cur_c].0,
cur_labels[cur_b].1 - cur_labels[cur_c].1,
);
let _ = cur_d;
let nbr_seed_a_lbl = cur_labels[cur_b];
let nbr_seed_b_lbl = cur_labels[cur_a];
Some(derive_labels(
nbr_a_pos,
nbr_seed_a_lbl,
nbr_seed_b_lbl,
outward,
))
}
fn rebase(labelled: &mut HashMap<(i32, i32), usize>) {
if labelled.is_empty() {
return;
}
let min_i = labelled.keys().map(|(i, _)| *i).min().unwrap();
let min_j = labelled.keys().map(|(_, j)| *j).min().unwrap();
if min_i == 0 && min_j == 0 {
return;
}
let rebased: HashMap<(i32, i32), usize> = labelled
.drain()
.map(|((i, j), v)| ((i - min_i, j - min_j), v))
.collect();
*labelled = rebased;
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(
level = "debug",
skip_all,
fields(num_quads = quads.len()),
)
)]
pub(crate) fn label_components(quads: &[Quad], min_quads: usize) -> Vec<TopologicalComponent> {
if quads.is_empty() {
return Vec::new();
}
let edge_index = build_edge_index(quads);
let (comp_of, n_comp) = connected_components(quads, &edge_index);
let mut quads_by_comp: Vec<Vec<usize>> = vec![Vec::new(); n_comp as usize];
for (qi, &cid) in comp_of.iter().enumerate() {
quads_by_comp[cid as usize].push(qi);
}
let mut out = Vec::new();
for comp_quads in quads_by_comp {
if comp_quads.len() < min_quads {
continue;
}
let mut quad_labels: HashMap<usize, [(i32, i32); 4]> = HashMap::new();
let seed = comp_quads[0];
quad_labels.insert(seed, seed_labels());
let mut queue = VecDeque::new();
queue.push_back(seed);
while let Some(qi) = queue.pop_front() {
let cur_labels = *quad_labels.get(&qi).expect("seed assigned");
let q = &quads[qi];
for (k, (u, v)) in q.perimeter_edges().iter().enumerate() {
let key = if u < v { (*u, *v) } else { (*v, *u) };
let buddies = edge_index.get(&key).expect("edge in index");
for &(qj, _) in buddies {
if qj == qi {
continue;
}
let nbr = &quads[qj];
let Some(nbr_labels) = propagate(q, &cur_labels, k, nbr) else {
continue;
};
if let Some(existing) = quad_labels.get(&qj) {
if existing != &nbr_labels {
continue;
}
} else {
quad_labels.insert(qj, nbr_labels);
queue.push_back(qj);
}
}
}
}
let mut labelled: HashMap<(i32, i32), usize> = HashMap::new();
let mut by_corner: HashMap<usize, (i32, i32)> = HashMap::new();
let mut conflicts = false;
for (&qi, lbls) in &quad_labels {
let q = &quads[qi];
for (k, lbl) in lbls.iter().enumerate() {
let v = q.vertices[k];
if let Some(existing) = by_corner.get(&v) {
if existing != lbl {
conflicts = true;
}
} else {
by_corner.insert(v, *lbl);
}
}
}
if conflicts {
continue;
}
for (v, lbl) in by_corner {
labelled.insert(lbl, v);
}
rebase(&mut labelled);
out.push(TopologicalComponent { labelled });
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn quad(a: usize, b: usize, c: usize, d: usize) -> Quad {
Quad {
vertices: [a, b, c, d],
}
}
#[test]
fn single_quad_labels() {
let comps = label_components(&[quad(10, 11, 12, 13)], 1);
assert_eq!(comps.len(), 1);
let c = &comps[0];
assert_eq!(c.labelled.get(&(0, 0)), Some(&10));
assert_eq!(c.labelled.get(&(1, 0)), Some(&11));
assert_eq!(c.labelled.get(&(1, 1)), Some(&12));
assert_eq!(c.labelled.get(&(0, 1)), Some(&13));
}
#[test]
fn two_quads_share_right_edge() {
let qs = vec![quad(0, 1, 2, 3), quad(1, 4, 5, 2)];
let comps = label_components(&qs, 1);
assert_eq!(comps.len(), 1);
let c = &comps[0];
assert_eq!(c.labelled.get(&(0, 0)), Some(&0));
assert_eq!(c.labelled.get(&(1, 0)), Some(&1));
assert_eq!(c.labelled.get(&(2, 0)), Some(&4));
assert_eq!(c.labelled.get(&(2, 1)), Some(&5));
assert_eq!(c.labelled.get(&(1, 1)), Some(&2));
assert_eq!(c.labelled.get(&(0, 1)), Some(&3));
}
#[test]
fn rebase_makes_labels_non_negative() {
let mut m: HashMap<(i32, i32), usize> = HashMap::new();
m.insert((-2, 3), 7);
m.insert((0, 5), 8);
rebase(&mut m);
assert_eq!(m.get(&(0, 0)), Some(&7));
assert_eq!(m.get(&(2, 2)), Some(&8));
}
}