Files
OneCAD-Simulation/crates/mesh/src/delaunay/mod.rs
T
94dc50bb0c 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>
2026-10-13 15:56:42 -04:00

690 lines
28 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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(())
}
}