use super::predicates::cmp_lex;
use super::{fixed, interval, ImplicitPoint, Sign};
use std::cmp::Ordering;
pub type Vid = u32;
#[derive(Default)]
pub struct Interner {
points: Vec<ImplicitPoint>, sorted: Vec<Vid>, lambdas: Vec<Option<fixed::Lam>>,
lambdas_iv: Vec<interval::IvLam>,
}
impl Interner {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, p: ImplicitPoint) -> Vid {
let new_lam = fixed::lambda1024(&p);
let new_lam_iv = interval::ilambda_cached(&p);
let search = self.sorted.binary_search_by(|&vid| {
let s = interval::cmp_lex_from_lam_iv(&self.lambdas_iv[vid as usize], &new_lam_iv)
.or_else(|| match (&self.lambdas[vid as usize], &new_lam) {
(Some(le), Some(ln)) => fixed::cmp_lex_from_lam(le, ln),
_ => None,
})
.unwrap_or_else(|| cmp_lex(&self.points[vid as usize], &p));
match s {
Sign::Negative => Ordering::Less,
Sign::Positive => Ordering::Greater,
Sign::Zero => Ordering::Equal,
}
});
match search {
Ok(idx) => self.sorted[idx],
Err(idx) => {
let vid = self.points.len() as Vid;
self.points.push(p);
self.lambdas.push(new_lam);
self.lambdas_iv.push(new_lam_iv);
self.sorted.insert(idx, vid);
vid
}
}
}
pub fn get(&self, v: Vid) -> &ImplicitPoint {
&self.points[v as usize]
}
#[inline]
pub fn lam(&self, v: Vid) -> &Option<fixed::Lam> {
&self.lambdas[v as usize]
}
#[inline]
pub fn lam_iv(&self, v: Vid) -> &interval::IvLam {
&self.lambdas_iv[v as usize]
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn lex_order(&self) -> &[Vid] {
&self.sorted
}
}
#[cfg(test)]
mod tests {
use super::super::rational::point_of;
use super::super::{Lpi, Tpi};
use super::*;
fn lpi_at(x: f64, y: f64) -> ImplicitPoint {
ImplicitPoint::Lpi(Lpi {
p: [x, y, -1.],
q: [x, y, 1.],
r: [0., 0., 0.],
s: [1., 0., 0.],
t: [0., 1., 0.],
})
}
fn tpi_at(x: f64, y: f64) -> ImplicitPoint {
ImplicitPoint::Tpi(Tpi {
planes: [
[[0., 0., 0.], [1., 0., 0.], [0., 1., 0.]],
[[x, 0., 0.], [x, 1., 0.], [x, 0., 1.]],
[[0., y, 0.], [1., y, 0.], [0., y, 1.]],
],
})
}
#[test]
fn coincident_points_weld_to_one_vid() {
let mut it = Interner::new();
let a = it.intern(lpi_at(0.3, 0.4));
let b = it.intern(tpi_at(0.3, 0.4)); assert_eq!(a, b, "coincident LPI/TPI got different Vids");
let c = it.intern(lpi_at(0.5, 0.4)); assert_ne!(a, c);
assert_eq!(it.len(), 2);
}
#[test]
fn interning_is_order_independent_canonically() {
let inputs = || {
vec![
lpi_at(0.5, 0.1),
tpi_at(0.2, 0.7),
ImplicitPoint::Explicit([0.9, 0.9, 0.0]),
lpi_at(0.2, 0.7), ]
};
let canonical = |order: &[usize]| {
let mut it = Interner::new();
let ps = inputs();
for &i in order {
it.intern(ps[i].clone());
}
it.lex_order().iter().map(|&v| point_of(it.get(v))).collect::<Vec<_>>()
};
let forward = canonical(&[0, 1, 2, 3]);
let backward = canonical(&[3, 2, 1, 0]);
assert_eq!(forward, backward, "canonical lex order is insertion-order dependent");
assert_eq!(forward.len(), 3, "one coincidence should leave 3 distinct points");
}
}