mod predicates;
use crate::Point2;
use predicates::{dist2, rings_to_pslg, segments_properly_cross, strictly_between};
use std::collections::{BTreeMap, BTreeSet, VecDeque};
const COS_MIN_ANGLE: f64 = 0.927_183_854_566_787_4;
const MAX_ASPECT: f64 = 7.0;
const MAX_REFINE_ITERS: usize = 20_000;
type P2 = [f64; 2];
const NONE: usize = usize::MAX;
#[inline]
fn p2(p: &Point2<f64>) -> P2 {
[p.x, p.y]
}
#[inline]
fn orient(a: P2, b: P2, c: P2) -> i32 {
let d = geometry_predicates::orient2d(a, b, c);
if d > 0.0 {
1
} else if d < 0.0 {
-1
} else {
0
}
}
#[inline]
fn in_circle_sign(a: P2, b: P2, c: P2, d: P2) -> i32 {
let v = geometry_predicates::incircle(a, b, c, d);
if v > 0.0 {
1
} else if v < 0.0 {
-1
} else {
0
}
}
#[inline]
fn ekey(a: usize, b: usize) -> (usize, usize) {
if a < b {
(a, b)
} else {
(b, a)
}
}
#[derive(Clone, Copy)]
struct Tri {
v: [usize; 3],
n: [usize; 3],
alive: bool,
}
impl Tri {
#[inline]
fn edge_of(&self, a: usize, b: usize) -> Option<usize> {
for e in 0..3 {
let x = self.v[e];
let y = self.v[(e + 1) % 3];
if (x == a && y == b) || (x == b && y == a) {
return Some(e);
}
}
None
}
}
struct Cdt {
points: Vec<P2>,
tris: Vec<Tri>,
constraints: BTreeSet<(usize, usize)>,
cset: rustc_hash::FxHashSet<(usize, usize)>,
super_base: usize,
n_real: usize,
last_loc: usize,
enc: EncGrid,
inside: Vec<bool>,
skinny: BTreeSet<usize>,
track: bool,
cos_min_angle: f64,
failed: bool,
}
#[derive(Default)]
struct EncGrid {
mid: Vec<P2>,
r2: Vec<f64>,
minx: f64,
miny: f64,
inv: f64,
nx: usize,
ny: usize,
starts: Vec<u32>,
items: Vec<u32>,
big: Vec<u32>,
}
impl Cdt {
fn build_from(
mut points: Vec<P2>,
segments: &[(usize, usize)],
steiner_cap: usize,
) -> Option<Cdt> {
let n_input = points.len();
if n_input < 3 {
return None;
}
let mut constraints = BTreeSet::new();
for &(a, b) in segments {
if a != b && a < n_input && b < n_input {
constraints.insert(ekey(a, b));
}
}
let (mut minx, mut miny, mut maxx, mut maxy) =
(f64::MAX, f64::MAX, f64::MIN, f64::MIN);
for p in &points {
if !p[0].is_finite() || !p[1].is_finite() {
return None;
}
minx = minx.min(p[0]);
miny = miny.min(p[1]);
maxx = maxx.max(p[0]);
maxy = maxy.max(p[1]);
}
let span = (maxx - minx).max(maxy - miny);
if !(span > 0.0) || !span.is_finite() {
return None;
}
let cx = (minx + maxx) * 0.5;
let cy = (miny + maxy) * 0.5;
let big = span * 32.0;
points.resize(n_input + steiner_cap, [0.0, 0.0]);
let super_base = points.len();
points.push([cx - big, cy - big]);
points.push([cx + big, cy - big]);
points.push([cx, cy + big]);
let mut cdt = Cdt {
points,
tris: Vec::new(),
cset: constraints.iter().copied().collect(),
constraints,
super_base,
n_real: n_input,
last_loc: 0,
enc: EncGrid::default(),
inside: Vec::new(),
skinny: BTreeSet::new(),
track: false,
cos_min_angle: COS_MIN_ANGLE,
failed: false,
};
cdt.tris.push(Tri {
v: [super_base, super_base + 1, super_base + 2],
n: [NONE; 3],
alive: true,
});
cdt.inside.push(false);
for vi in 0..n_input {
cdt.insert_point(vi);
}
if cdt.failed {
return None; }
if !cdt.enforce_constraints() {
return None;
}
cdt.restore_constrained_delaunay();
Some(cdt)
}
fn insert_point(&mut self, vi: usize) {
let p = self.points[vi];
let start = match self.walk_strict(self.last_loc, p) {
Some(t) => t,
None => match self.locate(p) {
Some(t) => t,
None => return,
},
};
self.insert_point_at(vi, start);
}
fn insert_point_at(&mut self, vi: usize, start: usize) {
if self.failed {
return; }
let p = self.points[vi];
let region = self.inside.get(start).copied().unwrap_or(false);
let mut bad: Vec<usize> = Vec::new();
let mut in_bad: BTreeSet<usize> = BTreeSet::new();
let mut queue: VecDeque<usize> = VecDeque::new();
queue.push_back(start);
let mut visited: BTreeSet<usize> = BTreeSet::new();
visited.insert(start);
while let Some(ti) = queue.pop_front() {
if !self.tris[ti].alive {
continue;
}
let v = self.tris[ti].v;
if in_circle_sign(self.points[v[0]], self.points[v[1]], self.points[v[2]], p) > 0 {
bad.push(ti);
in_bad.insert(ti);
for e in 0..3 {
let a = v[e];
let b = v[(e + 1) % 3];
if self.cset.contains(&ekey(a, b)) {
continue; }
let nb = self.tris[ti].n[e];
if nb != NONE && visited.insert(nb) {
queue.push_back(nb);
}
}
}
}
if bad.is_empty() {
self.split_at(start, vi);
self.last_loc = self.tris.len() - 1;
return;
}
let mut boundary: Vec<(usize, usize, usize)> = Vec::new();
for &ti in &bad {
let v = self.tris[ti].v;
for e in 0..3 {
let nb = self.tris[ti].n[e];
if nb == NONE || !in_bad.contains(&nb) {
let a = v[e];
let b = v[(e + 1) % 3];
boundary.push((a, b, nb));
}
}
}
boundary.sort_unstable();
for &ti in &bad {
self.tris[ti].alive = false;
}
let mut owner: BTreeMap<(usize, usize), (usize, usize)> = BTreeMap::new();
let mut new_tris: Vec<usize> = Vec::with_capacity(boundary.len());
for &(a, b, outside) in &boundary {
let ti = self.tris.len();
self.tris.push(Tri {
v: [a, b, vi],
n: [NONE; 3],
alive: true,
});
self.inside.push(region);
new_tris.push(ti);
self.tris[ti].n[0] = outside;
if outside != NONE {
if let Some(e) = self.tris[outside].edge_of(a, b) {
self.tris[outside].n[e] = ti;
}
}
self.link_internal(&mut owner, ekey(b, vi), ti, 1);
self.link_internal(&mut owner, ekey(vi, a), ti, 2);
}
let mut stack: Vec<(usize, usize)> = new_tris.iter().map(|&t| (t, 0usize)).collect();
self.legalize(&mut stack);
for t in new_tris {
self.track_tri(t);
}
self.last_loc = self.tris.len() - 1;
}
fn link_internal(
&mut self,
owner: &mut BTreeMap<(usize, usize), (usize, usize)>,
key: (usize, usize),
ti: usize,
e: usize,
) {
if let Some(&(ot, oe)) = owner.get(&key) {
self.tris[ti].n[e] = ot;
self.tris[ot].n[oe] = ti;
} else {
owner.insert(key, (ti, e));
}
}
fn split_in_triangle(&mut self, t: usize, vi: usize) {
if !self.tris[t].alive {
return;
}
let region = self.inside.get(t).copied().unwrap_or(false);
let v = self.tris[t].v;
let n = self.tris[t].n;
self.tris[t].alive = false;
let mut owner: BTreeMap<(usize, usize), (usize, usize)> = BTreeMap::new();
let mut children: Vec<usize> = Vec::new();
for e in 0..3 {
let a = v[e];
let b = v[(e + 1) % 3];
if orient(self.points[a], self.points[b], self.points[vi]) == 0 {
continue; }
let ti = self.tris.len();
self.tris.push(Tri {
v: [a, b, vi],
n: [NONE; 3],
alive: true,
});
self.inside.push(region);
children.push(ti);
self.tris[ti].n[0] = n[e];
if n[e] != NONE {
if let Some(oe) = self.tris[n[e]].edge_of(a, b) {
self.tris[n[e]].n[oe] = ti;
}
}
self.link_internal(&mut owner, ekey(b, vi), ti, 1);
self.link_internal(&mut owner, ekey(vi, a), ti, 2);
}
let mut stack: Vec<(usize, usize)> = children.iter().map(|&c| (c, 0usize)).collect();
self.legalize(&mut stack);
for c in children {
self.track_tri(c);
}
}
fn split_at(&mut self, start: usize, vi: usize) {
if !self.tris[start].alive {
return;
}
let v = self.tris[start].v;
let p = self.points[vi];
for e in 0..3 {
let a = self.points[v[e]];
let b = self.points[v[(e + 1) % 3]];
if orient(a, b, p) == 0 && strictly_between(a, b, p) {
self.split_on_edge(start, e, vi);
return;
}
}
self.split_in_triangle(start, vi);
}
fn split_on_edge(&mut self, t: usize, e: usize, vi: usize) {
let v = self.tris[t].v;
let n = self.tris[t].n;
let a = v[e];
let b = v[(e + 1) % 3];
let c = v[(e + 2) % 3];
let nb = n[e];
let n_bc = n[(e + 1) % 3];
let n_ca = n[(e + 2) % 3];
let region_t = self.inside.get(t).copied().unwrap_or(false);
if self.constraints.remove(&ekey(a, b)) {
self.constraints.insert(ekey(a, vi));
self.constraints.insert(ekey(vi, b));
self.cset.remove(&ekey(a, b));
self.cset.insert(ekey(a, vi));
self.cset.insert(ekey(vi, b));
self.enc = EncGrid::default(); }
self.tris[t].alive = false;
if nb == NONE {
let t1 = self.tris.len(); let t2 = t1 + 1; self.tris.push(Tri { v: [vi, b, c], n: [NONE, n_bc, t2], alive: true });
self.tris.push(Tri { v: [a, vi, c], n: [NONE, t1, n_ca], alive: true });
self.inside.push(region_t);
self.inside.push(region_t);
for (ext, x, y, child) in [(n_bc, b, c, t1), (n_ca, c, a, t2)] {
if ext != NONE {
if let Some(oe) = self.tris[ext].edge_of(x, y) {
self.tris[ext].n[oe] = child;
}
}
}
let mut stack: Vec<(usize, usize)> = vec![(t1, 1), (t2, 2)];
self.legalize(&mut stack);
self.track_tri(t1);
self.track_tri(t2);
return;
}
let region_nb = self.inside.get(nb).copied().unwrap_or(false);
let Some(d) = self.tris[nb].v.iter().copied().find(|&x| x != a && x != b) else {
self.failed = true;
return;
};
let outer_of = |s: &Self, t: usize, x: usize, y: usize| -> usize {
s.tris[t].edge_of(x, y).map(|oe| s.tris[t].n[oe]).unwrap_or(NONE)
};
let n_ad = outer_of(self, nb, a, d);
let n_db = outer_of(self, nb, d, b);
self.tris[nb].alive = false;
let t1 = self.tris.len(); let t2 = t1 + 1; let t3 = t1 + 2; let t4 = t1 + 3; self.tris.push(Tri { v: [vi, b, c], n: [t3, n_bc, t2], alive: true });
self.tris.push(Tri { v: [a, vi, c], n: [t4, t1, n_ca], alive: true });
self.tris.push(Tri { v: [b, vi, d], n: [t1, t4, n_db], alive: true });
self.tris.push(Tri { v: [vi, a, d], n: [t2, n_ad, t3], alive: true });
self.inside.push(region_t);
self.inside.push(region_t);
self.inside.push(region_nb);
self.inside.push(region_nb);
for (ext, x, y, child) in [
(n_bc, b, c, t1),
(n_ca, c, a, t2),
(n_db, d, b, t3),
(n_ad, a, d, t4),
] {
if ext != NONE {
if let Some(oe) = self.tris[ext].edge_of(x, y) {
self.tris[ext].n[oe] = child;
}
}
}
let mut stack: Vec<(usize, usize)> = vec![(t1, 1), (t2, 2), (t3, 2), (t4, 1)];
self.legalize(&mut stack);
for ti in [t1, t2, t3, t4] {
self.track_tri(ti);
}
}
fn legalize(&mut self, stack: &mut Vec<(usize, usize)>) {
let mut guard = 0usize;
while let Some((ti, e)) = stack.pop() {
guard += 1;
if guard > 4_000_000 {
break;
}
if !self.tris[ti].alive {
continue;
}
let a = self.tris[ti].v[e];
let b = self.tris[ti].v[(e + 1) % 3];
let apex = self.tris[ti].v[(e + 2) % 3];
if self.cset.contains(&ekey(a, b)) {
continue;
}
let opp = self.tris[ti].n[e];
if opp == NONE || !self.tris[opp].alive {
continue;
}
let ov = self.tris[opp].v;
let q = ov.iter().copied().find(|&x| x != a && x != b);
let Some(q) = q else { continue };
let tv = self.tris[ti].v;
if in_circle_sign(
self.points[tv[0]],
self.points[tv[1]],
self.points[tv[2]],
self.points[q],
) <= 0
{
continue;
}
let sa = orient(self.points[apex], self.points[q], self.points[a]);
let sb = orient(self.points[apex], self.points[q], self.points[b]);
if sa == 0 || sb == 0 || sa == sb {
continue;
}
self.flip(ti, opp, a, b, apex, q, stack);
}
}
#[allow(clippy::too_many_arguments)]
fn flip(
&mut self,
ti: usize,
opp: usize,
a: usize,
b: usize,
apex: usize,
q: usize,
stack: &mut Vec<(usize, usize)>,
) {
let outer = |s: &Self, t: usize, x: usize, y: usize| -> usize {
s.tris[t].edge_of(x, y).map(|e| s.tris[t].n[e]).unwrap_or(NONE)
};
let n_apex_a = outer(self, ti, apex, a); let n_apex_b = outer(self, ti, apex, b); let n_q_a = outer(self, opp, q, a); let n_q_b = outer(self, opp, q, b);
let mk = |s: &Self, x: usize, y: usize, z: usize| -> [usize; 3] {
if orient(s.points[x], s.points[y], s.points[z]) >= 0 {
[x, y, z]
} else {
[x, z, y]
}
};
let t0 = mk(self, apex, q, a);
let t1 = mk(self, apex, q, b);
self.tris[ti] = Tri {
v: t0,
n: [NONE; 3],
alive: true,
};
self.tris[opp] = Tri {
v: t1,
n: [NONE; 3],
alive: true,
};
let set_nb = |s: &mut Self, t: usize, x: usize, y: usize, val: usize| {
if let Some(e) = s.tris[t].edge_of(x, y) {
s.tris[t].n[e] = val;
}
};
set_nb(self, ti, apex, q, opp);
set_nb(self, opp, apex, q, ti);
set_nb(self, ti, apex, a, n_apex_a);
set_nb(self, ti, q, a, n_q_a);
set_nb(self, opp, apex, b, n_apex_b);
set_nb(self, opp, q, b, n_q_b);
if n_apex_a != NONE {
if let Some(e) = self.tris[n_apex_a].edge_of(apex, a) {
self.tris[n_apex_a].n[e] = ti;
}
}
if n_q_a != NONE {
if let Some(e) = self.tris[n_q_a].edge_of(q, a) {
self.tris[n_q_a].n[e] = ti;
}
}
if n_apex_b != NONE {
if let Some(e) = self.tris[n_apex_b].edge_of(apex, b) {
self.tris[n_apex_b].n[e] = opp;
}
}
if n_q_b != NONE {
if let Some(e) = self.tris[n_q_b].edge_of(q, b) {
self.tris[n_q_b].n[e] = opp;
}
}
for (t, x, y) in [
(ti, apex, a),
(ti, q, a),
(opp, apex, b),
(opp, q, b),
] {
if let Some(e) = self.tris[t].edge_of(x, y) {
stack.push((t, e));
}
}
self.track_tri(ti);
self.track_tri(opp);
}
#[inline]
fn track_tri(&mut self, ti: usize) {
if !self.track {
return;
}
let cand = self.tris[ti].alive
&& self.inside.get(ti).copied().unwrap_or(false)
&& !self.tris[ti].v.iter().any(|&x| x >= self.super_base)
&& self.tri_is_skinny(ti, self.cos_min_angle);
if cand {
self.skinny.insert(ti);
} else {
self.skinny.remove(&ti);
}
}
fn start_refinement(&mut self, cos_min_angle: f64) {
self.inside = self.inside_flags();
self.build_enc_grid();
self.cos_min_angle = cos_min_angle;
self.track = true;
self.skinny.clear();
for ti in 0..self.tris.len() {
self.track_tri(ti);
}
}
fn locate(&self, p: P2) -> Option<usize> {
let mut on_edge = None;
for ti in 0..self.tris.len() {
if !self.tris[ti].alive {
continue;
}
let v = self.tris[ti].v;
let o0 = orient(self.points[v[0]], self.points[v[1]], p);
let o1 = orient(self.points[v[1]], self.points[v[2]], p);
let o2 = orient(self.points[v[2]], self.points[v[0]], p);
if o0 >= 0 && o1 >= 0 && o2 >= 0 {
if o0 > 0 && o1 > 0 && o2 > 0 {
return Some(ti);
}
on_edge.get_or_insert(ti);
}
}
on_edge
}
fn locate_from(&self, start: usize, p: P2) -> Option<usize> {
if !self.tris.get(start).is_some_and(|t| t.alive) {
return self.locate(p);
}
let mut cur = start;
let cap = self.tris.len() * 2 + 16;
for _ in 0..cap {
let v = self.tris[cur].v;
let mut moved = false;
for e in 0..3 {
let a = self.points[v[e]];
let b = self.points[v[(e + 1) % 3]];
if orient(a, b, p) < 0 {
let nb = self.tris[cur].n[e];
if nb == NONE || !self.tris[nb].alive {
return self.locate(p); }
cur = nb;
moved = true;
break;
}
}
if !moved {
return Some(cur);
}
}
self.locate(p)
}
fn walk_strict(&self, start: usize, p: P2) -> Option<usize> {
if !self.tris.get(start).is_some_and(|t| t.alive) {
return None;
}
let mut cur = start;
let cap = self.tris.len().min(4096) + 16;
for _ in 0..cap {
let v = self.tris[cur].v;
let o0 = orient(self.points[v[0]], self.points[v[1]], p);
let o1 = orient(self.points[v[1]], self.points[v[2]], p);
let o2 = orient(self.points[v[2]], self.points[v[0]], p);
if o0 > 0 && o1 > 0 && o2 > 0 {
return Some(cur);
}
let e = if o0 < 0 {
0
} else if o1 < 0 {
1
} else if o2 < 0 {
2
} else {
return None; };
let nb = self.tris[cur].n[e];
if nb == NONE || !self.tris[nb].alive {
return None;
}
cur = nb;
}
None
}
fn enforce_constraints(&mut self) -> bool {
let mut edges: rustc_hash::FxHashSet<(usize, usize)> = rustc_hash::FxHashSet::default();
for t in self.tris.iter().filter(|t| t.alive) {
for e in 0..3 {
edges.insert(ekey(t.v[e], t.v[(e + 1) % 3]));
}
}
let segs: Vec<(usize, usize)> = self.constraints.iter().copied().collect();
for (a, b) in segs {
if !self.recover_segment(a, b, &mut edges) {
return false;
}
}
true
}
fn recover_segment(
&mut self,
a: usize,
b: usize,
edges: &mut rustc_hash::FxHashSet<(usize, usize)>,
) -> bool {
if edges.contains(&ekey(a, b)) {
return true;
}
let pa = self.points[a];
let pb = self.points[b];
let mut guard = 0usize;
loop {
guard += 1;
if guard > 100_000 {
return false;
}
if edges.contains(&ekey(a, b)) {
return true;
}
let mut flipped = false;
'scan: for ti in 0..self.tris.len() {
if !self.tris[ti].alive {
continue;
}
for e in 0..3 {
let u = self.tris[ti].v[e];
let w = self.tris[ti].v[(e + 1) % 3];
if u == a || u == b || w == a || w == b {
continue;
}
if self.cset.contains(&ekey(u, w)) {
continue;
}
if !segments_properly_cross(pa, pb, self.points[u], self.points[w]) {
continue;
}
let opp = self.tris[ti].n[e];
if opp == NONE || !self.tris[opp].alive {
continue;
}
let apex = self.tris[ti].v[(e + 2) % 3];
let q = self.tris[opp]
.v
.iter()
.copied()
.find(|&x| x != u && x != w);
let Some(q) = q else { continue };
if orient(self.points[u], self.points[apex], self.points[q]) == 0
|| orient(self.points[w], self.points[apex], self.points[q]) == 0
{
continue;
}
let s1 = orient(self.points[apex], self.points[q], self.points[u]);
let s2 = orient(self.points[apex], self.points[q], self.points[w]);
if s1 == 0 || s2 == 0 || s1 == s2 {
continue; }
let mut tmp = Vec::new();
self.flip(ti, opp, u, w, apex, q, &mut tmp);
edges.remove(&ekey(u, w));
edges.insert(ekey(apex, q));
flipped = true;
break 'scan;
}
}
if !flipped {
return edges.contains(&ekey(a, b));
}
}
}
#[cfg(test)]
fn edge_exists(&self, a: usize, b: usize) -> bool {
self.tris
.iter()
.any(|t| t.alive && t.edge_of(a, b).is_some())
}
fn restore_constrained_delaunay(&mut self) {
let mut stack: Vec<(usize, usize)> = Vec::new();
for ti in 0..self.tris.len() {
if self.tris[ti].alive {
for e in 0..3 {
stack.push((ti, e));
}
}
}
self.legalize(&mut stack);
}
fn inside_flags(&self) -> Vec<bool> {
let n = self.tris.len();
let mut depth: Vec<i32> = vec![-1; n]; let mut queue: VecDeque<usize> = VecDeque::new();
for ti in 0..n {
if !self.tris[ti].alive {
continue;
}
if self.tris[ti].v.iter().any(|&x| x >= self.super_base)
&& depth[ti] == -1 {
depth[ti] = 0;
queue.push_back(ti);
}
}
let bfs = |start_queue: &mut VecDeque<usize>, depth: &mut [i32]| {
while let Some(ti) = start_queue.pop_front() {
let d = depth[ti];
for e in 0..3 {
let nb = self.tris[ti].n[e];
if nb == NONE || !self.tris[nb].alive || depth[nb] != -1 {
continue;
}
let a = self.tris[ti].v[e];
let b = self.tris[ti].v[(e + 1) % 3];
let nd = if self.cset.contains(&ekey(a, b)) {
d + 1
} else {
d
};
depth[nb] = nd;
start_queue.push_back(nb);
}
}
};
bfs(&mut queue, &mut depth);
for seed in 0..n {
if self.tris[seed].alive && depth[seed] == -1 {
depth[seed] = 0;
let mut q2 = VecDeque::new();
q2.push_back(seed);
bfs(&mut q2, &mut depth);
}
}
depth.iter().map(|&d| d > 0 && d % 2 == 1).collect()
}
fn next_steiner(&mut self) -> Option<(P2, usize)> {
loop {
let &ti = self.skinny.iter().next()?;
if !self.tris[ti].alive
|| !self.inside.get(ti).copied().unwrap_or(false)
|| self.tris[ti].v.iter().any(|&x| x >= self.super_base)
|| !self.tri_is_skinny(ti, self.cos_min_angle)
{
self.skinny.remove(&ti); continue;
}
let Some(cc) = self.circumcenter(ti) else {
self.skinny.remove(&ti);
continue;
};
if self.is_encroached(cc) {
self.skinny.remove(&ti); continue;
}
match self.locate_from(ti, cc) {
Some(loc) if self.inside.get(loc).copied().unwrap_or(false) => {
return Some((cc, loc));
}
_ => {
self.skinny.remove(&ti);
continue;
}
}
}
}
fn insert_steiner(&mut self, p: P2, loc: usize) {
let vi = self.n_real;
if vi >= self.super_base {
self.failed = true; return;
}
self.points[vi] = p;
self.n_real += 1;
self.insert_point_at(vi, loc);
}
fn is_encroached(&self, p: P2) -> bool {
let hit = |i: u32| {
let i = i as usize;
dist2(p, self.enc.mid[i]) < self.enc.r2[i] * (1.0 - 1e-12)
};
if self.enc.nx == 0 {
return self.constraints.iter().any(|&(a, b)| {
let (pa, pb) = (self.points[a], self.points[b]);
let mid = [(pa[0] + pb[0]) * 0.5, (pa[1] + pb[1]) * 0.5];
dist2(p, mid) < dist2(pa, pb) * 0.25 * (1.0 - 1e-12)
});
}
if self.enc.big.iter().copied().any(hit) {
return true;
}
let gx = (p[0] - self.enc.minx) * self.enc.inv;
let gy = (p[1] - self.enc.miny) * self.enc.inv;
if !(gx >= 0.0 && gy >= 0.0) {
return false;
}
let (gx, gy) = (gx as usize, gy as usize);
if gx >= self.enc.nx || gy >= self.enc.ny {
return false;
}
let c = gy * self.enc.nx + gx;
let (s, e) = (
self.enc.starts[c] as usize,
self.enc.starts[c + 1] as usize,
);
self.enc.items[s..e].iter().copied().any(hit)
}
fn build_enc_grid(&mut self) {
let mut mid: Vec<P2> = Vec::with_capacity(self.constraints.len());
let mut r2: Vec<f64> = Vec::with_capacity(self.constraints.len());
let mut rad: Vec<f64> = Vec::with_capacity(self.constraints.len());
for &(a, b) in &self.constraints {
let (pa, pb) = (self.points[a], self.points[b]);
mid.push([(pa[0] + pb[0]) * 0.5, (pa[1] + pb[1]) * 0.5]);
let d2 = dist2(pa, pb) * 0.25;
r2.push(d2);
rad.push(d2.sqrt());
}
self.enc = EncGrid {
mid,
r2,
..EncGrid::default()
};
let n = self.enc.mid.len();
if n == 0 {
return;
}
let (mut minx, mut miny, mut maxx, mut maxy) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
for (m, r) in self.enc.mid.iter().zip(rad.iter()) {
minx = minx.min(m[0] - r);
miny = miny.min(m[1] - r);
maxx = maxx.max(m[0] + r);
maxy = maxy.max(m[1] + r);
}
let span = (maxx - minx).max(maxy - miny);
if !(span > 0.0) || !span.is_finite() {
return;
}
let side = ((n as f64).sqrt().ceil() as usize).clamp(1, 64);
let cell = span / side as f64;
let inv = 1.0 / cell;
let (nx, ny) = (side, side);
let cellrange = |m: P2, r: f64| -> (usize, usize, usize, usize) {
let c = |v: f64, lo: f64, hi: usize| {
let g = ((v - lo) * inv).floor();
if g < 0.0 {
0
} else if g >= hi as f64 {
hi - 1
} else {
g as usize
}
};
(
c(m[0] - r, minx, nx),
c(m[0] + r, minx, nx),
c(m[1] - r, miny, ny),
c(m[1] + r, miny, ny),
)
};
let mut counts = vec![0u32; nx * ny + 1];
let mut big: Vec<u32> = Vec::new();
let mut is_big = vec![false; n];
for i in 0..n {
let (x0, x1, y0, y1) = cellrange(self.enc.mid[i], rad[i]);
if (x1 - x0 + 1) * (y1 - y0 + 1) > 32 {
big.push(i as u32);
is_big[i] = true;
continue;
}
for gy in y0..=y1 {
for gx in x0..=x1 {
counts[gy * nx + gx + 1] += 1;
}
}
}
for i in 1..counts.len() {
counts[i] += counts[i - 1];
}
let total = counts[nx * ny] as usize;
let mut items = vec![0u32; total];
let mut cursor = counts.clone();
for i in 0..n {
if is_big[i] {
continue;
}
let (x0, x1, y0, y1) = cellrange(self.enc.mid[i], rad[i]);
for gy in y0..=y1 {
for gx in x0..=x1 {
let c = gy * nx + gx;
items[cursor[c] as usize] = i as u32;
cursor[c] += 1;
}
}
}
self.enc.minx = minx;
self.enc.miny = miny;
self.enc.inv = inv;
self.enc.nx = nx;
self.enc.ny = ny;
self.enc.starts = counts;
self.enc.items = items;
self.enc.big = big;
}
fn tri_is_skinny(&self, ti: usize, cos_min_angle: f64) -> bool {
let v = self.tris[ti].v;
let a = self.points[v[0]];
let b = self.points[v[1]];
let c = self.points[v[2]];
let la2 = dist2(b, c);
let lb2 = dist2(c, a);
let lc2 = dist2(a, b);
let (mut e0, mut e1, mut e2) = (la2.sqrt(), lb2.sqrt(), lc2.sqrt());
if e0 > e1 {
std::mem::swap(&mut e0, &mut e1);
}
if e1 > e2 {
std::mem::swap(&mut e1, &mut e2);
}
if e0 > e1 {
std::mem::swap(&mut e0, &mut e1);
}
if e0 <= 1e-15 {
return false; }
if e2 / e0 > MAX_ASPECT {
return true;
}
let cos_min = (e1 * e1 + e2 * e2 - e0 * e0) / (2.0 * e1 * e2);
cos_min > cos_min_angle
}
fn circumcenter(&self, ti: usize) -> Option<P2> {
let v = self.tris[ti].v;
let a = self.points[v[0]];
let b = self.points[v[1]];
let c = self.points[v[2]];
let dx = b[0] - a[0];
let dy = b[1] - a[1];
let ex = c[0] - a[0];
let ey = c[1] - a[1];
let d = 2.0 * (dx * ey - dy * ex);
if d.abs() < 1e-20 {
return None;
}
let b2 = dx * dx + dy * dy;
let c2 = ex * ex + ey * ey;
let ux = (ey * b2 - dy * c2) / d;
let uy = (dx * c2 - ex * b2) / d;
let cc = [a[0] + ux, a[1] + uy];
if !cc[0].is_finite() || !cc[1].is_finite() {
return None;
}
Some(cc)
}
fn emit(&self) -> (Vec<P2>, Vec<usize>) {
let inside = self.inside_flags();
let keep_upto = self.n_real;
let out_points: Vec<P2> = self.points[..keep_upto].to_vec();
let mut indices: Vec<usize> = Vec::new();
for ti in 0..self.tris.len() {
if !self.tris[ti].alive || !inside[ti] {
continue;
}
let v = self.tris[ti].v;
if v.iter().any(|&x| x >= keep_upto) {
continue;
}
let a = self.points[v[0]];
let b = self.points[v[1]];
let c = self.points[v[2]];
if orient(a, b, c) >= 0 {
indices.extend_from_slice(&[v[0], v[1], v[2]]);
} else {
indices.extend_from_slice(&[v[0], v[2], v[1]]);
}
}
(out_points, indices)
}
}
fn refine_to_fixpoint(points: Vec<P2>, segments: Vec<(usize, usize)>) -> Option<Cdt> {
let n_input = points.len();
let max_steiner = (n_input * 3).max(32);
let mut steiner = 0usize;
let mut cdt = Cdt::build_from(points, &segments, max_steiner.min(MAX_REFINE_ITERS))?;
cdt.start_refinement(COS_MIN_ANGLE);
while steiner < max_steiner.min(MAX_REFINE_ITERS) {
let Some((p, loc)) = cdt.next_steiner() else {
break;
};
cdt.insert_steiner(p, loc);
if cdt.failed {
return None; }
steiner += 1;
}
Some(cdt)
}
pub(crate) fn triangulate_refined(
outer: &[Point2<f64>],
holes: &[Vec<Point2<f64>>],
) -> Option<(Vec<Point2<f64>>, Vec<usize>)> {
let mut rings: Vec<Vec<P2>> = Vec::with_capacity(1 + holes.len());
rings.push(outer.iter().map(p2).collect());
for h in holes {
if h.len() >= 3 {
rings.push(h.iter().map(p2).collect());
}
}
let (points, segments) = rings_to_pslg(&rings);
let cdt = refine_to_fixpoint(points, segments)?;
let (pts, idx) = cdt.emit();
if idx.is_empty() {
return None;
}
let out_pts: Vec<Point2<f64>> = pts.iter().map(|p| Point2::new(p[0], p[1])).collect();
Some((out_pts, idx))
}
pub(crate) fn triangulate_constrained(
outer: &[Point2<f64>],
holes: &[Vec<Point2<f64>>],
) -> Option<(Vec<Point2<f64>>, Vec<usize>)> {
let mut rings: Vec<Vec<P2>> = Vec::with_capacity(1 + holes.len());
rings.push(outer.iter().map(p2).collect());
for h in holes {
if h.len() >= 3 {
rings.push(h.iter().map(p2).collect());
}
}
let (points, segments) = rings_to_pslg(&rings);
let cdt = Cdt::build_from(points, &segments, 0)?;
let (pts, idx) = cdt.emit();
if idx.is_empty() {
return None;
}
let out_pts: Vec<Point2<f64>> = pts.iter().map(|p| Point2::new(p[0], p[1])).collect();
Some((out_pts, idx))
}
pub(crate) fn triangulate_pslg(
points: &[Point2<f64>],
segments: &[(usize, usize)],
) -> Option<(Vec<Point2<f64>>, Vec<usize>)> {
let pts: Vec<P2> = points.iter().map(p2).collect();
let cdt = Cdt::build_from(pts, segments, 0)?;
let keep_upto = cdt.super_base;
let mut indices: Vec<usize> = Vec::new();
for tri in &cdt.tris {
if !tri.alive {
continue;
}
let v = tri.v;
if v.iter().any(|&x| x >= keep_upto) {
continue;
}
let a = cdt.points[v[0]];
let b = cdt.points[v[1]];
let c = cdt.points[v[2]];
if orient(a, b, c) >= 0 {
indices.extend_from_slice(&[v[0], v[1], v[2]]);
} else {
indices.extend_from_slice(&[v[0], v[2], v[1]]);
}
}
if indices.is_empty() {
return None;
}
let out_pts: Vec<Point2<f64>> = cdt.points[..keep_upto]
.iter()
.map(|p| Point2::new(p[0], p[1]))
.collect();
Some((out_pts, indices))
}
#[cfg(test)]
#[path = "cdt_tests.rs"]
mod tests;