use std::cmp::Ordering;
use num_rational::BigRational;
use num_traits::{Signed, Zero};
use super::exact::rational::R3;
use super::exact::Sign;
use super::intersection_graph::{edge_key, EdgeKey, IntersectionGraph};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Tag {
Union,
Inter,
}
pub struct Classification {
pub tags: Vec<Option<Tag>>,
pub discarded: Vec<bool>,
}
struct Incident {
piece: usize,
forward: bool,
apex: R3,
du: BigRational,
dv: BigRational,
}
fn angle_cmp(a: (&BigRational, &BigRational), b: (&BigRational, &BigRational)) -> Ordering {
fn quadrant(du: &BigRational, dv: &BigRational) -> u8 {
let (su, sv) = (Sign::of_rat(du), Sign::of_rat(dv));
debug_assert!(!(su == Sign::Zero && sv == Sign::Zero), "zero direction");
match (su, sv) {
(Sign::Pos, Sign::Pos) | (Sign::Pos, Sign::Zero) => 0,
(Sign::Zero, Sign::Pos) | (Sign::Neg, Sign::Pos) => 1,
(Sign::Neg, Sign::Zero) | (Sign::Neg, Sign::Neg) => 2,
_ => 3,
}
}
let (qa, qb) = (quadrant(a.0, a.1), quadrant(b.0, b.1));
if qa != qb {
return qa.cmp(&qb);
}
let lhs = a.0.numer() * b.1.numer() * a.1.denom() * b.0.denom();
let rhs = a.1.numer() * b.0.numer() * a.0.denom() * b.1.denom();
rhs.cmp(&lhs)
}
fn bind_coincident(
graph: &IntersectionGraph,
tags: &mut [Option<Tag>],
discarded: &mut [bool],
) {
let mut by_key: std::collections::HashMap<[u32; 3], Vec<(usize, bool)>> =
std::collections::HashMap::new();
for (pi, piece) in graph.pieces.iter().enumerate() {
let mut sorted = piece.vi;
sorted.sort_unstable();
let start = piece.vi.iter().position(|v| *v == sorted[0]).unwrap();
let same = piece.vi[(start + 1) % 3] == sorted[1];
by_key.entry(sorted).or_default().push((pi, same));
}
let reduce = |side: &mut Vec<(usize, bool)>, discarded: &mut [bool]| {
let mut fwd: Vec<usize> = side.iter().filter(|e| e.1).map(|e| e.0).collect();
let mut bwd: Vec<usize> = side.iter().filter(|e| !e.1).map(|e| e.0).collect();
while !fwd.is_empty() && !bwd.is_empty() {
discarded[fwd.pop().unwrap()] = true;
discarded[bwd.pop().unwrap()] = true;
}
for &pi in fwd.iter().chain(&bwd).skip(1) {
discarded[pi] = true;
}
side.retain(|&(pi, _)| !discarded[pi]);
debug_assert!(side.len() <= 1);
};
for owners in by_key.values() {
if owners.len() < 2 {
continue;
}
let mut p_side: Vec<(usize, bool)> = Vec::new();
let mut q_side: Vec<(usize, bool)> = Vec::new();
for &(pi, parity) in owners {
if graph.pieces[pi].mesh == 0 {
p_side.push((pi, parity));
} else {
q_side.push((pi, parity));
}
}
reduce(&mut p_side, discarded);
reduce(&mut q_side, discarded);
if let (Some(&(pp, p_parity)), Some(&(qp, q_parity))) =
(p_side.first(), q_side.first())
{
if p_parity != q_parity {
discarded[pp] = true;
discarded[qp] = true;
} else {
tags[pp] = Some(Tag::Union);
tags[qp] = Some(Tag::Inter);
}
}
}
}
pub fn classify_rings(graph: &IntersectionGraph) -> Classification {
let n = graph.pieces.len();
let mut tags: Vec<Option<Tag>> = vec![None; n];
let mut discarded = vec![false; n];
bind_coincident(graph, &mut tags, &mut discarded);
let mut rings: std::collections::HashMap<EdgeKey, Vec<Incident>> =
std::collections::HashMap::new();
for (pi, piece) in graph.pieces.iter().enumerate() {
if discarded[pi] {
continue;
}
for e in 0..3 {
let a = piece.vi[e];
let b = piece.vi[(e + 1) % 3];
let key = edge_key(a, b);
if !graph.isect_edges.contains(&key) {
continue;
}
let forward = a == key.0; let apex = &graph.verts[piece.vi[(e + 2) % 3] as usize];
rings.entry(key).or_default().push(Incident {
piece: pi,
forward,
apex: apex.clone(),
du: BigRational::zero(),
dv: BigRational::zero(),
});
}
}
for (key, incidents) in rings.iter_mut() {
regularize_one_ring(
&graph.verts[key.0 as usize],
&graph.verts[key.1 as usize],
incidents,
&mut discarded,
);
}
Classification { tags, discarded }
}
fn regularize_one_ring(k0: &R3, k1: &R3, incidents: &mut [Incident], discarded: &mut [bool]) {
let w = k1.sub(k0);
let ax = w.x.abs();
let ay = w.y.abs();
let az = w.z.abs();
let unit = |i: usize| {
let z = BigRational::zero;
let o = || BigRational::from_integer(1.into());
match i {
0 => R3::new(o(), z(), z()),
1 => R3::new(z(), o(), z()),
_ => R3::new(z(), z(), o()),
}
};
let k = if ax <= ay && ax <= az {
0
} else if ay <= az {
1
} else {
2
};
let u = w.cross(&unit(k));
debug_assert!(!u.is_zero());
let v = w.cross(&u);
for inc in incidents.iter_mut() {
let (du_n, du_d) = super::exact::predicates::dot_diff_raw(&inc.apex, k0, &u);
let (dv_n, dv_d) = super::exact::predicates::dot_diff_raw(&inc.apex, k0, &v);
inc.du = num_rational::BigRational::new_raw(du_n, du_d);
inc.dv = num_rational::BigRational::new_raw(dv_n, dv_d);
debug_assert!(
!(inc.du.is_zero() && inc.dv.is_zero()),
"apex on the ring axis"
);
}
incidents.sort_by(|a, b| {
angle_cmp((&a.du, &a.dv), (&b.du, &b.dv)).then_with(|| a.piece.cmp(&b.piece))
});
let m = incidents.len();
let mut cancelled = vec![false; m];
let mut i = 0;
while i < m {
let mut j = i;
while j + 1 < m
&& angle_cmp(
(&incidents[i].du, &incidents[i].dv),
(&incidents[j + 1].du, &incidents[j + 1].dv),
) == Ordering::Equal
{
j += 1;
}
let mut fwd: Vec<usize> = (i..=j).filter(|&x| incidents[x].forward).collect();
let mut bwd: Vec<usize> = (i..=j).filter(|&x| !incidents[x].forward).collect();
while let (Some(f), Some(bk)) = (fwd.pop(), bwd.pop()) {
cancelled[f] = true;
cancelled[bk] = true;
discarded[incidents[f].piece] = true;
discarded[incidents[bk].piece] = true;
}
i = j + 1;
}
}
#[cfg(test)]
#[path = "classify_tests.rs"]
mod tests;