use std::collections::BTreeMap;
use super::exact::backend::{
denom, int_from_uint, mul_int_uint, mul_uint, numer, Int, Rational, Signed,
};
use crate::linalg::Vec3;
use super::cdt;
use super::exact::approx::orient2d_a;
use super::exact::predicates::{homog2_of, line_line_intersect_2d, orient2d_h, point_in_tri_2d, tri_normal_r, Homog2, TriLoc};
use super::exact::rational::{int_ratio_to_f64, R2, R2Key, R3};
use super::exact::Sign;
use super::tri_tri::{dominant_axis, lift_to_plane};
#[derive(Clone, Debug, Default)]
pub struct ArrangementInput {
pub points: Vec<(R3, usize)>,
pub segments: Vec<(R3, R3, usize)>,
}
#[derive(Clone, Debug)]
pub struct Arrangement {
pub axis: usize,
pub points3: Vec<R3>,
pub points2: Vec<R2>,
pub tris: Vec<[usize; 3]>,
pub constraints: BTreeMap<(usize, usize), Vec<usize>>,
pub flipped: bool,
}
pub mod stats {
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
pub static SETUP_NS: AtomicU64 = AtomicU64::new(0);
pub static NORM_NS: AtomicU64 = AtomicU64::new(0);
pub static CROSS_NS: AtomicU64 = AtomicU64::new(0);
pub static ONSEG_NS: AtomicU64 = AtomicU64::new(0);
pub static CDT_NS: AtomicU64 = AtomicU64::new(0);
pub static CALLS: AtomicU64 = AtomicU64::new(0);
pub static SEGS: AtomicU64 = AtomicU64::new(0);
pub static PTS: AtomicU64 = AtomicU64::new(0);
pub fn snapshot_and_reset() -> String {
let take = |a: &AtomicU64| a.swap(0, Relaxed) as f64 * 1e-9;
let taken = |a: &AtomicU64| a.swap(0, Relaxed);
format!(
"setup {:.3}s (norm {:.3}s), crossings {:.3}s, on-seg {:.3}s, cdt {:.3}s ({} calls, {} segs, {} pts)",
take(&SETUP_NS),
take(&NORM_NS),
take(&CROSS_NS),
take(&ONSEG_NS),
take(&CDT_NS),
taken(&CALLS),
taken(&SEGS),
taken(&PTS),
)
}
}
#[inline]
fn approx_box(pts: &[[f64; 2]]) -> [f64; 4] {
let (mut b, rest) = ([pts[0][0], pts[0][1], pts[0][0], pts[0][1]], &pts[1..]);
for p in rest {
b[0] = b[0].min(p[0]);
b[1] = b[1].min(p[1]);
b[2] = b[2].max(p[0]);
b[3] = b[3].max(p[1]);
}
let pad = |x: f64| x.abs() * 1e-15 + f64::MIN_POSITIVE;
[
b[0] - pad(b[0]),
b[1] - pad(b[1]),
b[2] + pad(b[2]),
b[3] + pad(b[3]),
]
}
#[inline]
fn translated_coord(pc: &Rational, oc: &Rational) -> (f64, bool) {
let (pn, pd) = (numer(pc), denom(pc));
let (on, od) = (numer(oc), denom(oc));
let num = mul_int_uint(pn, od) - mul_int_uint(on, pd);
let den = int_from_uint(mul_uint(pd, od));
int_ratio_to_f64(&num, &den)
}
#[inline]
fn translated_approx(o: &R2, p: &R2) -> ([f64; 2], bool) {
let (x, x_exact) = translated_coord(&p.x, &o.x);
let (y, y_exact) = translated_coord(&p.y, &o.y);
([x, y], x_exact && y_exact)
}
#[inline]
fn boxes_overlap(a: &[f64; 4], b: &[f64; 4]) -> bool {
a[0] <= b[2] && b[0] <= a[2] && a[1] <= b[3] && b[1] <= a[3]
}
#[inline]
fn box_contains(b: &[f64; 4], p: [f64; 2]) -> bool {
p[0] >= b[0] && p[0] <= b[2] && p[1] >= b[1] && p[1] <= b[3]
}
struct BoxPairs {
starts: Vec<u32>,
partners: Vec<u32>,
}
impl BoxPairs {
fn iter(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
(0..self.starts.len() - 1).flat_map(move |i| {
self.partners[self.starts[i] as usize..self.starts[i + 1] as usize]
.iter()
.map(move |&j| (i, j as usize))
})
}
}
fn overlapping_box_pairs(
boxes: &[[f64; 4]],
token: Option<&crate::cancel::CancelToken>,
) -> Option<BoxPairs> {
let n = boxes.len();
let mut starts = vec![0u32; n + 1];
if n <= 64 {
let mut partners: Vec<u32> = Vec::new();
for i in 0..n {
starts[i] = partners.len() as u32;
for j in (i + 1)..n {
if boxes_overlap(&boxes[i], &boxes[j]) {
partners.push(j as u32);
}
}
}
starts[n] = partners.len() as u32;
return Some(BoxPairs { starts, partners });
}
let mut lo = [f64::INFINITY; 2];
let mut hi = [f64::NEG_INFINITY; 2];
let mut width = [0.0f64; 2];
for b in boxes {
for k in 0..2 {
lo[k] = lo[k].min(b[k]);
hi[k] = hi[k].max(b[k + 2]);
width[k] += b[k + 2] - b[k];
}
}
let density = |k: usize| -> f64 {
let span = hi[k] - lo[k];
if span > 0.0 && span.is_finite() && width[k].is_finite() {
width[k] / span
} else {
f64::INFINITY
}
};
let axis = if density(1) < density(0) { 1 } else { 0 };
let (slo, shi) = (axis, axis + 2);
let (olo, ohi) = (1 - axis, 3 - axis);
let mut order: Vec<u32> = (0..n as u32).collect();
order.sort_unstable_by(|&a, &b| {
boxes[a as usize][slo]
.total_cmp(&boxes[b as usize][slo])
.then(a.cmp(&b))
});
let mut active: Vec<u32> = Vec::new();
let mut sweep = |emit: &mut dyn FnMut(u32, u32)| -> Option<()> {
active.clear();
for (step, &c) in order.iter().enumerate() {
if step % 1024 == 0 && crate::cancel::is_cancelled(token) {
return None;
}
let cb = &boxes[c as usize];
active.retain(|&a| boxes[a as usize][shi] >= cb[slo]);
for &a in &active {
let ab = &boxes[a as usize];
if ab[olo] <= cb[ohi] && cb[olo] <= ab[ohi] {
emit(a.min(c), a.max(c));
}
}
active.push(c);
}
Some(())
};
sweep(&mut |i, _j| starts[i as usize + 1] += 1)?;
for i in 0..n {
starts[i + 1] += starts[i];
}
let total = starts[n] as usize;
let mut partners = vec![0u32; total];
let mut cursor: Vec<u32> = starts[..n].to_vec();
sweep(&mut |i, j| {
partners[cursor[i as usize] as usize] = j;
cursor[i as usize] += 1;
})?;
for i in 0..n {
partners[starts[i] as usize..starts[i + 1] as usize].sort_unstable();
}
Some(BoxPairs { starts, partners })
}
struct PointIndex {
by_x: Vec<u32>,
}
impl PointIndex {
fn build(apts: &[[f64; 2]]) -> Self {
let mut by_x: Vec<u32> = (0..apts.len() as u32).collect();
by_x.sort_unstable_by(|&a, &b| {
apts[a as usize][0]
.total_cmp(&apts[b as usize][0])
.then(a.cmp(&b))
});
Self { by_x }
}
fn query(&self, apts: &[[f64; 2]], b: &[f64; 4], out: &mut Vec<usize>) {
out.clear();
let mut start = self
.by_x
.partition_point(|&i| apts[i as usize][0].total_cmp(&b[0]).is_lt());
while start > 0 && apts[self.by_x[start - 1] as usize][0] >= b[0] {
start -= 1;
}
for &i in &self.by_x[start..] {
let p = apts[i as usize];
if p[0] > b[2] {
break;
}
if box_contains(b, p) {
out.push(i as usize);
}
}
out.sort_unstable();
}
}
pub fn build(
tri: [Vec3; 3],
input: &ArrangementInput,
token: Option<&crate::cancel::CancelToken>,
) -> Option<Arrangement> {
use std::sync::atomic::Ordering::Relaxed;
let t0 = crate::timing::Stopwatch::start();
stats::CALLS.fetch_add(1, Relaxed);
stats::SEGS.fetch_add(input.segments.len() as u64, Relaxed);
stats::PTS.fetch_add(input.points.len() as u64, Relaxed);
let corners: [R3; 3] = [
R3::from_vec3(tri[0]),
R3::from_vec3(tri[1]),
R3::from_vec3(tri[2]),
];
let normal = tri_normal_r(&corners[0], &corners[1], &corners[2]);
debug_assert!(!normal.is_zero(), "degenerate triangle in arrangement");
let axis = dominant_axis(&normal);
stats::NORM_NS.fetch_add(t0.elapsed_ns(), Relaxed);
let mut points3: Vec<R3> = Vec::new();
let mut points2: Vec<R2> = Vec::new();
let mut index: rustc_hash::FxHashMap<R2Key, usize> = rustc_hash::FxHashMap::default();
let mut add_point = |p3: R3, points3: &mut Vec<R3>, points2: &mut Vec<R2>| -> usize {
let p2 = p3.project_drop(axis);
let next = points3.len();
match index.entry(R2Key(p2.clone())) {
std::collections::hash_map::Entry::Occupied(e) => *e.get(),
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(next);
points3.push(p3);
points2.push(p2);
next
}
}
};
for c in &corners {
add_point(c.clone(), &mut points3, &mut points2);
}
debug_assert_eq!(points3.len(), 3, "corner points must be distinct");
struct Seg {
a: usize,
b: usize,
prov: usize,
}
let mut segs: Vec<Seg> = Vec::with_capacity(input.segments.len());
for (a3, b3, prov) in &input.segments {
debug_assert_ne!(a3, b3, "zero-length segment primitive");
let a = add_point(a3.clone(), &mut points3, &mut points2);
let b = add_point(b3.clone(), &mut points3, &mut points2);
debug_assert_ne!(a, b);
segs.push(Seg {
a,
b,
prov: *prov,
});
}
for (p3, _prov) in &input.points {
add_point(p3.clone(), &mut points3, &mut points2);
}
stats::SETUP_NS.fetch_add(t0.elapsed_ns(), Relaxed);
let t0 = crate::timing::Stopwatch::start();
let mut homogs: Vec<Homog2> = points2.iter().map(homog2_of).collect();
let mut apts: Vec<[f64; 2]> = Vec::with_capacity(points2.len());
let mut apts_exact: Vec<bool> = Vec::with_capacity(points2.len());
for i in 0..points2.len() {
if i % 1024 == 0 && crate::cancel::is_cancelled(token) {
return None;
}
let (a, e) = translated_approx(&points2[0], &points2[i]);
apts.push(a);
apts_exact.push(e);
}
let o2 = |apts: &[[f64; 2]], homogs: &[Homog2], i: usize, j: usize, k: usize| -> Sign {
orient2d_a(apts[i], apts[j], apts[k])
.unwrap_or_else(|| orient2d_h(&homogs[i], &homogs[j], &homogs[k]))
};
let seg_boxes: Vec<[f64; 4]> = segs
.iter()
.map(|s| approx_box(&[apts[s.a], apts[s.b]]))
.collect();
let pairs = overlapping_box_pairs(&seg_boxes, token)?;
for (k, (i, j)) in pairs.iter().enumerate() {
if k % 1024 == 0 && crate::cancel::is_cancelled(token) {
return None;
}
let (ia, ib) = (segs[i].a, segs[i].b);
let (ic, id) = (segs[j].a, segs[j].b);
let sc = o2(&apts, &homogs, ia, ib, ic);
let sd = o2(&apts, &homogs, ia, ib, id);
let sa = o2(&apts, &homogs, ic, id, ia);
let sb = o2(&apts, &homogs, ic, id, ib);
if sc != Sign::Zero && sd != Sign::Zero && sc != sd
&& sa != Sign::Zero && sb != Sign::Zero && sa != sb
{
let x2 = line_line_intersect_2d(
&points2[segs[i].a],
&points2[segs[i].b],
&points2[segs[j].a],
&points2[segs[j].b],
)
.expect("properly crossing segments are not parallel");
let x3 = lift_to_plane(&x2, axis, &corners[0], &normal);
add_point(x3, &mut points3, &mut points2);
}
}
for p in points2.iter().skip(homogs.len()) {
homogs.push(homog2_of(p));
}
for i in apts.len()..points2.len() {
if i % 1024 == 0 && crate::cancel::is_cancelled(token) {
return None;
}
let (a, e) = translated_approx(&points2[0], &points2[i]);
apts.push(a);
apts_exact.push(e);
}
debug_assert!(points2.iter().all(|p| {
point_in_tri_2d(p, &points2[0], &points2[1], &points2[2]) != TriLoc::Outside
}), "arrangement primitive escapes its triangle");
stats::CROSS_NS.fetch_add(t0.elapsed_ns(), Relaxed);
let t0 = crate::timing::Stopwatch::start();
let mut constraints: BTreeMap<(usize, usize), Vec<usize>> = BTreeMap::new();
let pindex = PointIndex::build(&apts);
let mut cand: Vec<usize> = Vec::new();
for (si, seg) in segs.iter().enumerate() {
if crate::cancel::is_cancelled(token) {
return None;
}
let sbox = seg_boxes[si];
let (ha, hb) = (&homogs[seg.a], &homogs[seg.b]);
let vx = &hb.0 * &ha.2 - &ha.0 * &hb.2;
let vy = &hb.1 * &ha.2 - &ha.1 * &hb.2;
let vv = &vx * &vx + &vy * &vy;
let mut on_seg: Vec<(Int, Int, usize)> = Vec::new();
pindex.query(&apts, &sbox, &mut cand);
for &idx in &cand {
let hp = &homogs[idx];
match orient2d_a(apts[seg.a], apts[seg.b], apts[idx]) {
Some(_) => continue, None => {}
}
if orient2d_h(ha, hb, hp) != Sign::Zero {
continue;
}
let ux = &hp.0 * &ha.2 - &ha.0 * &hp.2;
let uy = &hp.1 * &ha.2 - &ha.1 * &hp.2;
let uv = &ux * &vx + &uy * &vy;
if !uv.is_negative() && &uv * &hb.2 <= &vv * &hp.2 {
on_seg.push((uv, hp.2.clone(), idx));
}
}
on_seg.sort_by(|a, b| (&a.0 * &b.1).cmp(&(&b.0 * &a.1)));
debug_assert!(on_seg.len() >= 2, "segment lost its own endpoints");
for w in on_seg.windows(2) {
let (u, v) = (w[0].2, w[1].2);
debug_assert_ne!(u, v, "duplicate points on segment");
let key = (u.min(v), u.max(v));
let provs = constraints.entry(key).or_default();
if !provs.contains(&seg.prov) {
provs.push(seg.prov);
}
}
}
stats::ONSEG_NS.fetch_add(t0.elapsed_ns(), Relaxed);
let t0 = crate::timing::Stopwatch::start();
let constraint_pairs: Vec<(usize, usize)> = constraints.keys().copied().collect();
let tris =
cdt::triangulate_with_apts(&points2, &constraint_pairs, apts, apts_exact, token)?;
stats::CDT_NS.fetch_add(t0.elapsed_ns(), Relaxed);
let axis_comp = match axis {
0 => &normal.x,
1 => &normal.y,
_ => &normal.z,
};
let flipped = Sign::of_rat(axis_comp) == Sign::Neg;
Some(Arrangement {
axis,
points3,
points2,
tris,
constraints,
flipped,
})
}
pub fn candidate_points(
tri: [Vec3; 3],
input: &ArrangementInput,
token: Option<&crate::cancel::CancelToken>,
) -> Option<Vec<R3>> {
let corners: [R3; 3] = [
R3::from_vec3(tri[0]),
R3::from_vec3(tri[1]),
R3::from_vec3(tri[2]),
];
let normal = tri_normal_r(&corners[0], &corners[1], &corners[2]);
let axis = dominant_axis(&normal);
let mut out: Vec<R3> = Vec::new();
let mut seen: rustc_hash::FxHashSet<R2Key> = rustc_hash::FxHashSet::default();
let add = |p3: R3, out: &mut Vec<R3>, seen: &mut rustc_hash::FxHashSet<R2Key>| {
if seen.insert(R2Key(p3.project_drop(axis))) {
out.push(p3);
}
};
for (p, _) in &input.points {
add(p.clone(), &mut out, &mut seen);
}
for (a, b, _) in &input.segments {
add(a.clone(), &mut out, &mut seen);
add(b.clone(), &mut out, &mut seen);
}
let segs2: Vec<(R2, R2)> = input
.segments
.iter()
.map(|(a, b, _)| (a.project_drop(axis), b.project_drop(axis)))
.collect();
let homogs: Vec<(Homog2, Homog2)> = segs2
.iter()
.map(|(a, b)| (homog2_of(a), homog2_of(b)))
.collect();
let origin = corners[0].project_drop(axis);
let apts: Vec<([f64; 2], [f64; 2])> = segs2
.iter()
.map(|(a, b)| {
(
translated_approx(&origin, a).0,
translated_approx(&origin, b).0,
)
})
.collect();
let o2 = |a: ([f64; 2], &Homog2), b: ([f64; 2], &Homog2), c: ([f64; 2], &Homog2)| -> Sign {
orient2d_a(a.0, b.0, c.0).unwrap_or_else(|| orient2d_h(a.1, b.1, c.1))
};
let seg_boxes: Vec<[f64; 4]> = apts
.iter()
.map(|(a, b)| approx_box(&[*a, *b]))
.collect();
let pairs = overlapping_box_pairs(&seg_boxes, token)?;
for (k, (i, j)) in pairs.iter().enumerate() {
if k % 1024 == 0 && crate::cancel::is_cancelled(token) {
return None;
}
let (a, b) = (
(apts[i].0, &homogs[i].0),
(apts[i].1, &homogs[i].1),
);
let (c, d) = (
(apts[j].0, &homogs[j].0),
(apts[j].1, &homogs[j].1),
);
let sc = o2(a, b, c);
let sd = o2(a, b, d);
let sa = o2(c, d, a);
let sb = o2(c, d, b);
if sc != Sign::Zero && sd != Sign::Zero && sc != sd
&& sa != Sign::Zero && sb != Sign::Zero && sa != sb
{
let x2 = line_line_intersect_2d(&segs2[i].0, &segs2[i].1, &segs2[j].0, &segs2[j].1)
.expect("properly crossing segments are not parallel");
add(
lift_to_plane(&x2, axis, &corners[0], &normal),
&mut out,
&mut seen,
);
}
}
Some(out)
}
#[cfg(test)]
#[path = "arrangement_tests.rs"]
mod tests;