Exact 3D Delaunay tetrahedralization (Bowyer–Watson, ghost hull, BRIO/Hilbert, verifier) (#14)

* mesh: exact Bowyer-Watson Delaunay tetrahedralization with ghost tets, BRIO/Hilbert order and a verifier

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: Delaunay benchmark, hash-paired cavity faces, mesh.delaunay.points debug command

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: simplify Delaunay helpers, share predicates and parsers, halve verifier work

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 15:56:42 -04:00
1 parent 2b5b47930f
commit 94dc50bb0c
13 files changed
+1508 -17

No files matched your search

+689
View File
@@ -0,0 +1,689 @@
//! Exact, deterministic 3D Delaunay tetrahedralization of a point set.
//!
//! # Method
//! - **Incremental cavity insertion** (A. Bowyer, *Computing Dirichlet tessellations*, Comput. J.
//! 24(2):162–166, 1981; D. F. Watson, *Computing the n-dimensional Delaunay tessellation with
//! application to Voronoi polytopes*, Comput. J. 24(2):167–172, 1981). Each point finds the
//! tetrahedra whose circumsphere contains it (the cavity, grown by adjacency from one of them),
//! deletes them and joins the cavity's boundary faces to the point.
//! - **Insertion order**: BRIO rounds with Hilbert order inside each round (Amenta, Choi, Rote,
//! SoCG 2003; Skilling 2004). See [`order`].
//! - **Point location**: the stochastic visibility walk with memory (O. Devillers, S. Pion,
//! M. Teillaud, *Walking in a triangulation*, IJFCS 13(2):181–199, 2002, §2–§3), starting at
//! the last tetrahedron created. In a Delaunay triangulation the walk cannot cycle
//! (H. Edelsbrunner, *An acyclicity theorem for cell complexes in d dimensions*, Combinatorica
//! 10(3):251–260, 1990); it is still capped at the number of tetrahedra, after which a linear
//! scan locates the point.
//! - **The infinite vertex.** The convex hull is closed by *ghost* tetrahedra: each hull face is
//! joined to the vertex [`GHOST`], so every face has exactly two tetrahedra and the adjacency is
//! that of a triangulated 3-sphere (no boundary cases). A ghost tetrahedron's "circumsphere" is
//! the open half-space beyond its hull face; a point on the plane of the face is decided by the
//! finite tetrahedron on the other side of that face (the limit of spheres through the face
//! whose centre runs off to infinity), so the decision is the same perturbed one
//! (O. Devillers, M. Teillaud, *Perturbations for Delaunay and weighted Delaunay 3D
//! triangulations*, CGTA 44(3):160–168, 2011, §3 and the infinite-vertex discussion).
//! - **Compact adjacency arrays**: per tetrahedron four vertex ids and four neighbour ids, where
//! neighbour `i` is across the face opposite vertex `i`. Deleted slots are recycled LIFO.
//!
//! # Exactness and degeneracies
//! Every decision is an exact predicate from [`crate::predicates`]: `orient3d` for the walk and
//! for the hull, `insphere_sos` (Devillers–Teillaud symbolic perturbation) for the cavity. There
//! are no tolerances. The perturbation is a function of the coordinates only (it lifts each point
//! by an infinitesimal that depends on its lexicographic rank), so the result is *the* Delaunay
//! triangulation of the perturbed points: unique, independent of the insertion order, and with
//! no flat tetrahedra, even on grids where every eight neighbours are cospherical.
//!
//! - **Duplicates are merged.** Points with equal coordinates (`-0.0 == 0.0`) become one vertex:
//! the one with the lowest input index. The others are reported by
//! [`Triangulation::duplicate_of`] and appear in no tetrahedron.
//! - **Lower-dimensional inputs** (all points collinear or coplanar, a single point, none) have
//! no tetrahedra: [`Triangulation::dimension`] reports their affine dimension and the
//! tetrahedron lists are empty. Points arriving later that leave the plane start the 3D
//! triangulation.
//! - **Hostile coordinates** are rejected at the boundary by [`Point3::new`] (NaN, infinities,
//! magnitudes outside `[2⁻¹²⁰, 2¹⁶⁰]`); at most [`MAX_POINTS`] points.
//!
//! # Determinism
//! Same points in the same order → same tetrahedra in the same slots, bit for bit: the order is
//! a fixed function of the input, the walk's "random" face choice comes from a fixed-seed
//! generator, sorting has total keys, and nothing iterates a hash map.
//!
//! # Verification
//! [`Triangulation::verify`] checks the adjacency invariants, the orientation of every
//! tetrahedron, the convexity of the hull and the empty-circumsphere property of every interior
//! face (exact and perturbed), which by Delaunay's lemma makes every circumsphere empty. Tests
//! also use [`Triangulation::verify_empty_spheres`], the brute-force check of every point against
//! every circumsphere.
mod order;
mod verify;
#[cfg(test)]
mod tests;
use crate::predicates::{Axis, GenericPoint, Point3, Sign, generic_orient2d, insphere_sos, orient3d, tet_volume_sign};
use crate::{Error, Result};
/// The infinite vertex of the ghost tetrahedra that close the convex hull.
pub const GHOST: u32 = u32::MAX;
/// Marks a deleted tetrahedron slot (first vertex).
const DEAD: u32 = u32::MAX - 1;
/// No tetrahedron / no vertex.
const NONE: u32 = u32::MAX;
/// An empty slot of the edge table (no edge has two equal endpoints).
const EMPTY_EDGE: u64 = u64::MAX;
/// The most points a triangulation takes (vertex ids stay far below [`GHOST`] and the ~6.7
/// tetrahedra per point fit `u32` slot ids).
pub const MAX_POINTS: usize = 1 << 24;
/// Where a point lies relative to the triangulation ([`Triangulation::locate`]).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Location {
/// In the closed finite tetrahedron `t` and not one of its vertices.
Inside(u32),
/// Strictly beyond the hull face of the ghost tetrahedron `t`.
Outside(u32),
/// Equal to this vertex.
Vertex(u32),
}
/// The outcome of [`Triangulation::insert`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Insertion {
/// A new vertex.
Inserted(u32),
/// The point equals this existing vertex; it was recorded as a duplicate (its own id is the
/// last point index).
Duplicate(u32),
}
/// A Delaunay tetrahedralization with ghost tetrahedra closing the hull.
///
/// Vertex ids are input point indices. Tetrahedron ids are slots: [`Triangulation::slots`]
/// counts them, and [`Triangulation::tet`] returns `None` for a recycled slot. A tetrahedron's
/// vertices `[a, b, c, d]` have positive volume, `det[b − a, c − a, d − a] > 0` (the tet4 node
/// order). A ghost tetrahedron has [`GHOST`] in one slot; putting a point in place of the ghost
/// gives positive volume exactly when that point is beyond the hull face.
#[derive(Debug, Clone)]
pub struct Triangulation {
points: Vec<Point3>,
/// `NONE`, or the vertex this point duplicates.
duplicate_of: Vec<u32>,
tets: Vec<[u32; 4]>,
/// Neighbour `i` is across the face opposite vertex `i`.
adj: Vec<[u32; 4]>,
free: Vec<u32>,
/// A live tetrahedron incident to each vertex (`NONE` until it is in one).
vertex_tet: Vec<u32>,
/// While the points span less than 3D: affinely independent vertices, and the others.
basis: Vec<u32>,
pending: Vec<u32>,
/// Cavity marks: `epoch` = in the cavity, `epoch + 1` = tested and outside.
mark: Vec<u32>,
epoch: u32,
/// Where the next walk starts.
hint: u32,
/// State of the walk's face-order generator.
rng: u64,
scratch: Scratch,
}
#[derive(Debug, Clone, Default)]
struct Scratch {
cavity: Vec<u32>,
stack: Vec<u32>,
/// `(cavity tet, face slot, outside neighbour)`.
boundary: Vec<(u32, usize, u32)>,
/// `(new tet vertices, slot of the outside neighbour, neighbour, the neighbour's slot back)`.
new_tets: Vec<([u32; 4], usize, u32, usize)>,
/// Open-addressing table `(edge key, tet, slot)` linking the new tetrahedra to each other.
edges: Vec<(u64, u32, usize)>,
}
/// A Delaunay triangulation is corrupt: this never happens with exact predicates, and is
/// reported instead of panicking.
fn corrupt(what: &str) -> Error {
Error::Failed(format!("Delaunay triangulation is inconsistent: {what}"))
}
/// The sign of the volume `det[b − a, c − a, d − a]` (the tet4 convention).
#[inline]
fn volume_sign(p: [&Point3; 4]) -> Sign {
let [a, b, c, d] = p;
tet_volume_sign(a, b, c, d)
}
/// Whether three points are collinear: all three axis projections are (exactly).
fn collinear(a: &Point3, b: &Point3, c: &Point3) -> bool {
let [a, b, c] = [a, b, c].map(|p| GenericPoint::from(*p));
[Axis::X, Axis::Y, Axis::Z].into_iter().all(|axis| generic_orient2d(&a, &b, &c, axis).is_zero())
}
/// A coordinate triple as a [`Point3`], with `-0.0` made `+0.0` so equal points have equal bits.
fn admit([x, y, z]: [f64; 3]) -> Result<Point3> {
Point3::new(x + 0.0, y + 0.0, z + 0.0)
}
impl Triangulation {
fn empty() -> Self {
Triangulation {
points: Vec::new(),
duplicate_of: Vec::new(),
tets: Vec::new(),
adj: Vec::new(),
free: Vec::new(),
vertex_tet: Vec::new(),
basis: Vec::new(),
pending: Vec::new(),
mark: Vec::new(),
epoch: 0,
hint: NONE,
rng: 0x2545_F491_4F6C_DD1D,
scratch: Scratch::default(),
}
}
/// The Delaunay tetrahedralization of `points`. Duplicates are merged into the lowest
/// index; fewer than four affinely independent points give no tetrahedra.
pub fn new(points: &[[f64; 3]]) -> Result<Self> {
if points.len() > MAX_POINTS {
return Err(Error::Limit(format!("{} points; a triangulation takes at most {MAX_POINTS}", points.len())));
}
let mut tri = Self::empty();
tri.points =
points.iter().enumerate().map(|(i, &c)| admit(c).map_err(|e| Error::Invalid(format!("point {i}: {e}")))).collect::<Result<_>>()?;
let n = tri.points.len();
tri.duplicate_of = vec![NONE; n];
tri.vertex_tet = vec![NONE; n];
// Merge duplicates: sort by coordinates, ties by index, keep the first of each run.
let mut by_coords: Vec<u32> = (0..n as u32).collect();
let key = |v: u32| tri.points.get(v as usize).map(|p| p.coords().map(f64::to_bits)).unwrap_or_default();
// Equal values have equal bits after `admit`; the bit order is total and deterministic.
by_coords.sort_unstable_by_key(|&v| (key(v), v));
for run in by_coords.chunk_by(|&a, &b| key(a) == key(b)) {
if let [keep, rest @ ..] = run {
for &v in rest {
if let Some(d) = tri.duplicate_of.get_mut(v as usize) {
*d = *keep;
}
}
}
}
let unique: Vec<u32> = (0..n as u32).filter(|&v| tri.duplicate_of.get(v as usize) == Some(&NONE)).collect();
let expected = unique.len().saturating_mul(7);
tri.tets.reserve(expected);
tri.adj.reserve(expected);
tri.mark.reserve(expected);
for v in order::brio_hilbert(&tri.points, &unique) {
tri.add(v, true)?;
}
Ok(tri)
}
/// Insert one more point (a Steiner point, say). Its vertex id is its index in
/// [`Triangulation::points`]. A point equal to an existing vertex is recorded as a duplicate.
pub fn insert(&mut self, p: [f64; 3]) -> Result<Insertion> {
if self.points.len() >= MAX_POINTS {
return Err(Error::Limit(format!("a triangulation takes at most {MAX_POINTS} points")));
}
let v = self.points.len() as u32;
self.points.push(admit(p)?);
self.duplicate_of.push(NONE);
self.vertex_tet.push(NONE);
self.add(v, false)
}
/// The points, by vertex id (including duplicates).
pub fn points(&self) -> &[Point3] {
&self.points
}
/// The vertex a duplicate point was merged into.
pub fn duplicate_of(&self, v: u32) -> Option<u32> {
self.duplicate_of.get(v as usize).copied().filter(|&d| d != NONE)
}
/// The affine dimension of the distinct points: 3 when there are tetrahedra, otherwise 0
/// (none or one point), 1 (collinear) or 2 (coplanar).
pub fn dimension(&self) -> usize {
if self.tets.is_empty() { self.basis.len().saturating_sub(1) } else { 3 }
}
/// Tetrahedron slots, live or recycled, finite or ghost.
pub fn slots(&self) -> usize {
self.tets.len()
}
/// The vertices of the tetrahedron in slot `t` (one may be [`GHOST`]); `None` for a recycled
/// or out-of-range slot.
pub fn tet(&self, t: u32) -> Option<[u32; 4]> {
self.tets.get(t as usize).copied().filter(|v| v[0] != DEAD)
}
/// The neighbours of tetrahedron `t`: neighbour `i` shares the face opposite vertex `i`.
pub fn neighbors(&self, t: u32) -> Option<[u32; 4]> {
self.tet(t)?;
self.adj.get(t as usize).copied()
}
/// Whether `t` is a ghost tetrahedron (one vertex is [`GHOST`]).
pub fn is_ghost(&self, t: u32) -> bool {
self.tet(t).is_some_and(|v| v.contains(&GHOST))
}
/// A live tetrahedron incident to vertex `v` (finite or ghost), once `v` is triangulated.
pub fn incident_tet(&self, v: u32) -> Option<u32> {
self.vertex_tet.get(v as usize).copied().filter(|&t| t != NONE)
}
/// The finite tetrahedra with their slot ids, in slot order; vertices in tet4 order.
pub fn finite_tets(&self) -> impl Iterator<Item = (u32, [u32; 4])> + '_ {
self.tets.iter().enumerate().filter(|(_, v)| v[0] != DEAD && !v.contains(&GHOST)).map(|(t, v)| (t as u32, *v))
}
/// The number of finite tetrahedra.
pub fn finite_tet_count(&self) -> usize {
self.finite_tets().count()
}
/// Locate `p`: the walk from the last tetrahedron created.
pub fn locate(&self, p: [f64; 3]) -> Result<Option<Location>> {
if self.tets.is_empty() {
return Ok(None);
}
let point = admit(p)?;
let mut rng = self.rng;
self.walk(&point, self.hint, &mut rng).map(Some)
}
// ---- internals ----
fn pt(&self, v: u32) -> Result<&Point3> {
self.points.get(v as usize).ok_or_else(|| corrupt("vertex id"))
}
fn tet_at(&self, t: u32) -> Result<[u32; 4]> {
self.tet(t).ok_or_else(|| corrupt("tetrahedron id"))
}
fn adj_at(&self, t: u32) -> Result<[u32; 4]> {
self.adj.get(t as usize).copied().ok_or_else(|| corrupt("tetrahedron id"))
}
/// The four points of a finite tetrahedron.
fn pts(&self, [a, b, c, d]: [u32; 4]) -> Result<[&Point3; 4]> {
Ok([self.pt(a)?, self.pt(b)?, self.pt(c)?, self.pt(d)?])
}
/// The four points of `vs` with slot `k` replaced by `p` (`k` may hold the ghost).
fn with_point<'a>(&'a self, vs: [u32; 4], k: usize, p: &'a Point3) -> Result<[&'a Point3; 4]> {
let mut out = [p; 4];
for (i, (o, &v)) in out.iter_mut().zip(&vs).enumerate() {
if i != k {
*o = self.pt(v)?;
}
}
Ok(out)
}
/// Add vertex `v` (already in `points`). A duplicate is detected and recorded: in 3D by the
/// walk, below 3D by comparing with the vertices so far unless the caller knows `v` is
/// `unique` (the sort-based merge of [`Triangulation::new`]).
fn add(&mut self, v: u32, unique: bool) -> Result<Insertion> {
if !self.tets.is_empty() {
return self.insert_vertex(v);
}
let p = *self.pt(v)?;
if !unique && let Some(&u) = self.basis.iter().chain(&self.pending).find(|&&u| self.points.get(u as usize) == Some(&p)) {
if let Some(d) = self.duplicate_of.get_mut(v as usize) {
*d = u;
}
return Ok(Insertion::Duplicate(u));
}
match self.basis.as_slice() {
[] | [_] => self.basis.push(v),
&[a, b] => {
if collinear(self.pt(a)?, self.pt(b)?, &p) {
self.pending.push(v);
} else {
self.basis.push(v);
}
}
&[a, b, c, ..] => {
if orient3d(self.pt(a)?, self.pt(b)?, self.pt(c)?, &p).is_zero() {
self.pending.push(v);
} else {
self.start([a, b, c, v])?;
for u in std::mem::take(&mut self.pending) {
self.insert_vertex(u)?;
}
}
}
}
Ok(Insertion::Inserted(v))
}
/// The first tetrahedron and its four ghosts.
fn start(&mut self, [a, b, c, d]: [u32; 4]) -> Result<()> {
let t0 = if volume_sign(self.pts([a, b, c, d])?) == Sign::Positive { [a, b, c, d] } else { [b, a, c, d] };
let mut all = vec![t0];
for j in 0..4 {
let mut g = t0;
g[j] = GHOST;
// Swapping two other slots turns the ghost to face away from the hull.
g.swap((j + 1) % 4, (j + 2) % 4);
all.push(g);
}
let mut faces: Vec<([u32; 3], u32, usize)> = Vec::with_capacity(20);
for vs in all {
let t = self.alloc(vs)?;
for k in 0..4 {
faces.push((verify::face(vs, k), t, k));
}
self.hint = t;
}
faces.sort_unstable();
for pair in faces.chunks(2) {
match pair {
[(f, t, i), (g, u, j)] if f == g => {
self.set_adj(*t, *i, *u)?;
self.set_adj(*u, *j, *t)?;
}
_ => return Err(corrupt("initial tetrahedron faces")),
}
}
self.basis.clear();
Ok(())
}
fn set_adj(&mut self, t: u32, i: usize, n: u32) -> Result<()> {
let slot = self.adj.get_mut(t as usize).and_then(|a| a.get_mut(i)).ok_or_else(|| corrupt("tetrahedron id"))?;
*slot = n;
Ok(())
}
fn alloc(&mut self, vs: [u32; 4]) -> Result<u32> {
let t = match self.free.pop() {
Some(t) => {
*self.tets.get_mut(t as usize).ok_or_else(|| corrupt("free slot"))? = vs;
*self.adj.get_mut(t as usize).ok_or_else(|| corrupt("free slot"))? = [NONE; 4];
t
}
None => {
if self.tets.len() >= (DEAD - 1) as usize {
return Err(Error::Limit("too many tetrahedra".into()));
}
self.tets.push(vs);
self.adj.push([NONE; 4]);
self.mark.push(0);
(self.tets.len() - 1) as u32
}
};
for v in vs {
if let Some(s) = self.vertex_tet.get_mut(v as usize) {
*s = t;
}
}
Ok(t)
}
fn kill(&mut self, t: u32) -> Result<()> {
*self.tets.get_mut(t as usize).ok_or_else(|| corrupt("tetrahedron id"))? = [DEAD; 4];
self.free.push(t);
Ok(())
}
/// Whether `p` is in conflict with tetrahedron `t` (inside its perturbed circumsphere). Only
/// `dig` calls it: it reads the current cavity marks.
fn conflict(&self, t: u32, p: &Point3) -> Result<bool> {
let vs = self.tet_at(t)?;
let Some(g) = vs.iter().position(|&v| v == GHOST) else {
let [a, b, c, d] = self.pts(vs)?;
// `insphere` wants Shewchuk-positive orientation: swap the first two vertices.
return Ok(insphere_sos(b, a, c, d, p) == Sign::Positive);
};
Ok(match volume_sign(self.with_point(vs, g, p)?) {
Sign::Positive => true,
Sign::Negative => false,
// On the hull face's plane: the finite tetrahedron across that face decides.
Sign::Zero => {
let o = self.adj_at(t)?.get(g).copied().ok_or_else(|| corrupt("slot"))?;
// During `dig` the finite tetrahedron may already be decided.
match self.mark_of(o) {
m if m == self.epoch => return Ok(true),
m if m == self.epoch + 1 => return Ok(false),
_ => {}
}
let ws = self.tet_at(o)?;
if ws.contains(&GHOST) {
return Err(corrupt("two ghost tetrahedra share a hull face"));
}
let [a, b, c, d] = self.pts(ws)?;
insphere_sos(b, a, c, d, p) == Sign::Positive
}
})
}
/// The visibility walk towards `p` from `start` (Devillers, Pion, Teillaud 2002).
fn walk(&self, p: &Point3, start: u32, rng: &mut u64) -> Result<Location> {
let mut t = start;
let vs = self.tet_at(t)?;
if let Some(g) = vs.iter().position(|&v| v == GHOST) {
t = self.adj_at(t)?.get(g).copied().ok_or_else(|| corrupt("slot"))?;
}
let mut prev = NONE;
for _ in 0..self.tets.len().saturating_add(16) {
let vs = self.tet_at(t)?;
if vs.contains(&GHOST) {
return Ok(Location::Outside(t));
}
let adj = self.adj_at(t)?;
let ps = self.pts(vs)?;
// xorshift64 (Marsaglia 2003): which face to try first.
*rng ^= *rng << 13;
*rng ^= *rng >> 7;
*rng ^= *rng << 17;
let first = (*rng >> 61) as usize;
let mut next = NONE;
for i in 0..4 {
let k = (first + i) % 4;
let n = adj.get(k).copied().unwrap_or(NONE);
if n == prev {
continue;
}
let mut q = ps;
if let Some(slot) = q.get_mut(k) {
*slot = p;
}
if volume_sign(q) == Sign::Negative {
next = n;
break;
}
}
if next == NONE {
return self.inside(vs, t, p);
}
prev = t;
t = next;
}
self.locate_by_scan(p)
}
/// `p` lies in the closed finite tetrahedron `t`: one of its vertices, or inside.
fn inside(&self, vs: [u32; 4], t: u32, p: &Point3) -> Result<Location> {
for v in vs {
if self.pt(v)? == p {
return Ok(Location::Vertex(v));
}
}
Ok(Location::Inside(t))
}
/// Point location by testing every tetrahedron (the walk's fallback).
fn locate_by_scan(&self, p: &Point3) -> Result<Location> {
let mut outside = None;
for (t, &vs) in self.tets.iter().enumerate() {
if vs[0] == DEAD {
continue;
}
match vs.iter().position(|&v| v == GHOST) {
Some(g) => {
if outside.is_none() && volume_sign(self.with_point(vs, g, p)?) == Sign::Positive {
outside = Some(t as u32);
}
}
None => {
let mut inside = true;
for k in 0..4 {
if volume_sign(self.with_point(vs, k, p)?) == Sign::Negative {
inside = false;
break;
}
}
if inside {
return self.inside(vs, t as u32, p);
}
}
}
}
outside.map(Location::Outside).ok_or_else(|| corrupt("a point is neither inside nor outside the hull"))
}
fn next_epoch(&mut self) {
if self.epoch >= u32::MAX - 3 {
self.mark.fill(0);
self.epoch = 0;
}
self.epoch += 2;
}
fn mark_of(&self, t: u32) -> u32 {
self.mark.get(t as usize).copied().unwrap_or(0)
}
fn set_mark(&mut self, t: u32, m: u32) {
if let Some(s) = self.mark.get_mut(t as usize) {
*s = m;
}
}
/// Insert vertex `v` into the 3D triangulation.
fn insert_vertex(&mut self, v: u32) -> Result<Insertion> {
let p = *self.pt(v)?;
let mut rng = self.rng;
let located = self.walk(&p, self.hint, &mut rng)?;
self.rng = rng;
let seed = match located {
Location::Vertex(u) => {
if let Some(d) = self.duplicate_of.get_mut(v as usize) {
*d = u;
}
return Ok(Insertion::Duplicate(u));
}
Location::Inside(t) | Location::Outside(t) => t,
};
let mut s = std::mem::take(&mut self.scratch);
let result = self.dig(seed, &p, &mut s).and_then(|()| self.fill(v, &mut s));
self.scratch = s;
result.map(|()| Insertion::Inserted(v))
}
/// The cavity of `p`: every tetrahedron in conflict, grown by adjacency from `seed`, and the
/// faces between it and the rest.
fn dig(&mut self, seed: u32, p: &Point3, s: &mut Scratch) -> Result<()> {
self.next_epoch();
let (inside, outside) = (self.epoch, self.epoch + 1);
s.cavity.clear();
s.stack.clear();
s.boundary.clear();
self.set_mark(seed, inside);
s.cavity.push(seed);
s.stack.push(seed);
while let Some(t) = s.stack.pop() {
for (j, o) in self.adj_at(t)?.into_iter().enumerate() {
let m = self.mark_of(o);
if m == inside {
continue;
}
if m != outside && self.conflict(o, p)? {
self.set_mark(o, inside);
s.cavity.push(o);
s.stack.push(o);
} else {
self.set_mark(o, outside);
s.boundary.push((t, j, o));
}
}
}
Ok(())
}
/// Replace the cavity by the tetrahedra joining its boundary faces to `v`.
fn fill(&mut self, v: u32, s: &mut Scratch) -> Result<()> {
s.new_tets.clear();
// Everything is read before any slot is recycled: a new tetrahedron may reuse the id of
// a cavity tetrahedron that another outside neighbour still points to.
for &(t, j, o) in &s.boundary {
let mut vs = self.tet_at(t)?;
*vs.get_mut(j).ok_or_else(|| corrupt("slot"))? = v;
let back = self.adj_at(o)?.iter().position(|&n| n == t).ok_or_else(|| corrupt("one-sided adjacency"))?;
s.new_tets.push((vs, j, o, back));
}
for &t in &s.cavity {
self.kill(t)?;
}
// Each edge of the cavity boundary is shared by exactly two boundary faces; their new
// tetrahedra meet across the face spanned by that edge and `v`. Pair them through a
// small open-addressing table keyed by the edge (lookups only, so no order leaks out).
// 1.5 edges per new tetrahedron: at most 75% load.
let size = (s.new_tets.len() * 2).next_power_of_two().max(16);
s.edges.clear();
s.edges.resize(size, (EMPTY_EDGE, NONE, 0));
let mask = size - 1;
let mut open = 0usize;
for &(vs, j, o, back) in &s.new_tets {
let t = self.alloc(vs)?;
self.set_adj(t, j, o)?;
self.set_adj(o, back, t)?;
for k in (0..4).filter(|&k| k != j) {
let mut e = vs.iter().enumerate().filter(|&(i, _)| i != j && i != k).map(|(_, &u)| u);
let (a, b) = (e.next().unwrap_or(NONE), e.next().unwrap_or(NONE));
let key = (u64::from(a.min(b)) << 32) | u64::from(a.max(b));
let mut h = (key.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 32) as usize & mask;
let mut placed = false;
for _ in 0..size {
let entry = s.edges.get_mut(h).ok_or_else(|| corrupt("edge table"))?;
if entry.0 == EMPTY_EDGE {
*entry = (key, t, k);
open += 1;
placed = true;
break;
}
if entry.0 == key {
let (_, u, l) = *entry;
if u == NONE {
return Err(corrupt("an edge of the cavity boundary has more than two faces"));
}
entry.1 = NONE;
open -= 1;
self.set_adj(t, k, u)?;
self.set_adj(u, l, t)?;
placed = true;
break;
}
h = (h + 1) & mask;
}
if !placed {
return Err(corrupt("edge table full"));
}
}
self.hint = t;
}
if open != 0 {
return Err(corrupt("the cavity boundary is not a closed surface"));
}
Ok(())
}
}