use rustc_hash::FxHashMap as HashMap;
use crate::disjoint_sets::DisjointSets;
use super::cells::{radial_fan, VertTables};
use super::intersection_graph::{edge_key, EdgeKey};
type Half = (EdgeKey, usize, bool, u32);
pub type SplitPlan = Vec<u32>;
struct Fan {
key: EdgeKey,
halfs: Vec<Half>,
flip: bool,
}
pub fn plan_vertex_splits(tris: &[[u32; 3]], vt: VertTables) -> Option<SplitPlan> {
let mut incident: Vec<Half> = Vec::with_capacity(3 * tris.len());
for (t, vi) in tris.iter().enumerate() {
for c in 0..3 {
let (a, b) = (vi[c], vi[(c + 1) % 3]);
if a == b {
return None; }
incident.push((edge_key(a, b), 3 * t + c, a < b, vi[(c + 2) % 3]));
}
}
incident.sort_unstable();
let mut base = vec![usize::MAX; 3 * tris.len()];
let mut fans: Vec<Fan> = Vec::new();
let mut at = 0;
while at < incident.len() {
let key = incident[at].0;
let mut end = at + 1;
while end < incident.len() && incident[end].0 == key {
end += 1;
}
let raw = &incident[at..end];
at = end;
if raw.len() == 2 {
if raw[0].2 == raw[1].2 {
return None;
}
base[raw[0].1] = raw[1].1;
base[raw[1].1] = raw[0].1;
continue;
}
if raw.len() % 2 != 0 {
return None; }
fans.push(Fan {
key,
halfs: raw.to_vec(),
flip: false,
});
}
if fans.is_empty() {
return None;
}
const MAX_ATTEMPTS: usize = 4;
let mut budget = MAX_ATTEMPTS;
loop {
let Some(a) = attempt(tris, &base, &fans, vt, &mut budget) else {
return None;
};
if a.separated.iter().all(|&s| s) {
return settle(tris, &base, &mut fans, vt, &mut budget, a);
}
let mut progress = false;
for (i, fan) in fans.iter_mut().enumerate() {
if !a.separated[i] && !fan.flip {
fan.flip = true;
progress = true;
}
}
if !progress {
return None;
}
}
}
struct Attempt {
plan: SplitPlan,
separated: Vec<bool>,
edges_ok: bool,
}
fn attempt(
tris: &[[u32; 3]],
base: &[usize],
fans: &[Fan],
vt: VertTables,
budget: &mut usize,
) -> Option<Attempt> {
if *budget == 0 {
return None;
}
*budget -= 1;
let mut partner = base.to_vec();
for fan in fans {
pair_fan(fan, vt, &mut partner)?;
}
let plan = split_from_partners(tris, &partner);
let counts = split_edge_counts(tris, &plan)?;
Some(Attempt {
separated: fans
.iter()
.map(|fan| fan_separated(fan, tris, &plan, &counts))
.collect(),
edges_ok: counts.values().all(|&(f, b)| f == 1 && b == 1),
plan,
})
}
fn settle(
tris: &[[u32; 3]],
base: &[usize],
fans: &mut [Fan],
vt: VertTables,
budget: &mut usize,
accepted: Attempt,
) -> Option<SplitPlan> {
let mut accepted = accepted;
for i in 0..fans.len() {
if !fans[i].flip || *budget == 0 {
continue;
}
fans[i].flip = false;
match attempt(tris, base, fans, vt, budget) {
Some(a) if a.separated.iter().all(|&s| s) => accepted = a,
_ => fans[i].flip = true,
}
}
accepted.edges_ok.then_some(accepted.plan)
}
fn pair_fan(fan: &Fan, vt: VertTables, partner: &mut [usize]) -> Option<()> {
let (incs, groups) = radial_fan(fan.key.0, fan.key.1, &fan.halfs, vt)?;
if incs.len() != fan.halfs.len() || groups.len() != incs.len() {
return None;
}
let n = incs.len();
for i in 0..n {
if incs[i].forward != fan.flip {
continue;
}
let j = (i + 1) % n;
if incs[j].forward == fan.flip {
return None; }
partner[incs[i].id] = incs[j].id;
partner[incs[j].id] = incs[i].id;
}
if fan.halfs.iter().any(|h| partner[h.1] == usize::MAX) {
return None;
}
Some(())
}
fn split_from_partners(tris: &[[u32; 3]], partner: &[usize]) -> SplitPlan {
let n = 3 * tris.len();
let ds = DisjointSets::new(n.max(1) as u32);
for h in 0..n {
let p = partner[h];
if p == usize::MAX || p < h {
continue;
}
let (ht, hc) = (h / 3, h % 3);
let (pt, pc) = (p / 3, p % 3);
ds.unite(h as u32, (3 * pt + (pc + 1) % 3) as u32);
ds.unite((3 * ht + (hc + 1) % 3) as u32, p as u32);
}
let mut ordinal: HashMap<u32, u32> = HashMap::default();
let mut next: HashMap<u32, u32> = HashMap::default();
let mut plan = vec![0u32; n];
for h in 0..n {
let root = ds.find(h as u32);
let vid = tris[h / 3][h % 3];
plan[h] = *ordinal.entry(root).or_insert_with(|| {
let slot = next.entry(vid).or_insert(0);
let id = *slot;
*slot += 1;
id
});
}
plan
}
#[inline]
fn split_vert(tris: &[[u32; 3]], plan: &SplitPlan, h: usize) -> (u32, u32) {
(tris[h / 3][h % 3], plan[h])
}
fn split_edge_counts(
tris: &[[u32; 3]],
plan: &SplitPlan,
) -> Option<HashMap<((u32, u32), (u32, u32)), (u32, u32)>> {
let mut counts = HashMap::default();
for t in 0..tris.len() {
for c in 0..3 {
let a = split_vert(tris, plan, 3 * t + c);
let b = split_vert(tris, plan, 3 * t + (c + 1) % 3);
if a == b {
return None;
}
let e = counts.entry(if a < b { (a, b) } else { (b, a) }).or_insert((0, 0));
if a < b {
e.0 += 1;
} else {
e.1 += 1;
}
}
}
Some(counts)
}
fn fan_separated(
fan: &Fan,
tris: &[[u32; 3]],
plan: &SplitPlan,
counts: &HashMap<((u32, u32), (u32, u32)), (u32, u32)>,
) -> bool {
fan.halfs.iter().all(|&(_, h, _, _)| {
let a = split_vert(tris, plan, h);
let b = split_vert(tris, plan, 3 * (h / 3) + (h % 3 + 1) % 3);
counts.get(&if a < b { (a, b) } else { (b, a) }) == Some(&(1, 1))
})
}
#[cfg(test)]
#[path = "pairing_tests.rs"]
mod tests;