forked from 1b-agent/fieldcraft-upstream
mesh: exact predicates (Shewchuk adaptive, symbolic perturbation, indirect LPI/TPI) (#8)
* mesh: exact predicates (Shewchuk adaptive orient/incircle/insphere), symbolic perturbation, indirect LPI/TPI predicates Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * mesh: predicate benchmark (filter hit rate, CAD-degenerate inputs); record the predicate decision Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * mesh: simplify predicates (destructuring over fallbacks, shared-minor exact insphere, cheaper expansion product) 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:
17 files changed
+2683
-2
No files matched your search
@@ -0,0 +1,169 @@
|
||||
//! Exact geometric predicates: the sign of `orient2d`, `orient3d`, `incircle` and `insphere`
|
||||
//! for every admissible input, symbolic perturbation for the Delaunay tie-breaks, and indirect
|
||||
//! predicates on points constructed as line–plane and three-plane intersections.
|
||||
//!
|
||||
//! # Sources
|
||||
//! - J. R. Shewchuk, *Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric
|
||||
//! Predicates*, DCG 18:305–363, 1997: expansions (§2), adaptive evaluation (§3), the four
|
||||
//! predicates and their error bounds (§4). [`orient2d`], [`orient3d`], [`incircle`],
|
||||
//! [`insphere`] and their `*_filter` stage-A variants.
|
||||
//! - O. Devillers, M. Teillaud, *Perturbations for Delaunay and weighted Delaunay 3D
|
||||
//! triangulations*, CGTA 44(3):160–168, 2011; H. Edelsbrunner, E. P. Mücke, *Simulation of
|
||||
//! Simplicity*, ACM TOG 9(1), 1990: [`insphere_sos`], [`incircle_sos`].
|
||||
//! - M. Attene, *Indirect Predicates for Geometric Constructions*, CAD 126:102856, 2020 (paper
|
||||
//! only; his code is LGPL and is not read): [`Lpi`], [`Tpi`], [`GenericPoint`] and the
|
||||
//! `generic_*` predicates.
|
||||
//!
|
||||
//! # Arithmetic assumptions
|
||||
//! IEEE 754 binary64, round-to-nearest-even, no extended intermediate precision, no FMA
|
||||
//! contraction. Rust's `f64` gives exactly that on x86_64 (SSE2), aarch64 and
|
||||
//! wasm32-unknown-unknown: rustc never fuses `a * b + c` and never uses x87 on these targets, and
|
||||
//! the code uses no platform intrinsics, so every predicate returns the same sign on every
|
||||
//! platform. The results are deterministic functions of the inputs.
|
||||
//!
|
||||
//! # Domain
|
||||
//! Shewchuk's error bounds and the exactness of expansion arithmetic need no overflow and no
|
||||
//! underflow (§5). Every coordinate of a [`Point2`] / [`Point3`] is therefore either zero or has
|
||||
//! magnitude in [[`MIN_MAGNITUDE`], [`MAX_MAGNITUDE`]] = [2⁻¹²⁰, 2¹⁶⁰]: then every intermediate
|
||||
//! value of the degree-5 `insphere` is either zero or above 2⁻⁸⁶⁰ and below 2⁸¹², all normal
|
||||
//! doubles. The constructors reject anything else (NaN, infinities, out-of-range magnitudes) with
|
||||
//! [`crate::Error::Invalid`]; the predicates themselves are total and never fail. Callers snap
|
||||
//! tessellation noise (e.g. `1e-40`) to zero before building points.
|
||||
|
||||
mod adaptive;
|
||||
mod dyadic;
|
||||
mod eft;
|
||||
mod expansion;
|
||||
mod indirect;
|
||||
mod interval;
|
||||
mod ring;
|
||||
mod sos;
|
||||
|
||||
pub use adaptive::{incircle, incircle_filter, insphere, insphere_filter, orient2d, orient2d_filter, orient3d, orient3d_filter};
|
||||
pub use indirect::{Axis, GenericPoint, Lpi, Tpi, generic_compare, generic_orient2d, generic_orient3d};
|
||||
pub use sos::{incircle_sos, insphere_sos};
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// The smallest nonzero coordinate magnitude a point may have: 2⁻¹²⁰ ≈ 7.5e-37.
|
||||
pub const MIN_MAGNITUDE: f64 = f64::from_bits((1023 - 120) << 52);
|
||||
/// The largest coordinate magnitude a point may have: 2¹⁶⁰ ≈ 1.5e48.
|
||||
pub const MAX_MAGNITUDE: f64 = f64::from_bits((1023 + 160) << 52);
|
||||
|
||||
/// The sign of a predicate.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Sign {
|
||||
Negative = -1,
|
||||
Zero = 0,
|
||||
Positive = 1,
|
||||
}
|
||||
|
||||
impl Sign {
|
||||
/// The sign of a double (`Zero` for both zeros; NaN, which the domain excludes, maps to
|
||||
/// `Zero`).
|
||||
#[inline(always)]
|
||||
pub fn of(x: f64) -> Sign {
|
||||
if x > 0.0 {
|
||||
Sign::Positive
|
||||
} else if x < 0.0 {
|
||||
Sign::Negative
|
||||
} else {
|
||||
Sign::Zero
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_zero(self) -> bool {
|
||||
self == Sign::Zero
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Sign {
|
||||
type Output = Sign;
|
||||
|
||||
#[inline(always)]
|
||||
fn neg(self) -> Sign {
|
||||
match self {
|
||||
Sign::Negative => Sign::Positive,
|
||||
Sign::Zero => Sign::Zero,
|
||||
Sign::Positive => Sign::Negative,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Sign {
|
||||
type Output = Sign;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, rhs: Sign) -> Sign {
|
||||
match (self, rhs) {
|
||||
(Sign::Zero, _) | (_, Sign::Zero) => Sign::Zero,
|
||||
(a, b) if a == b => Sign::Positive,
|
||||
_ => Sign::Negative,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `x` may be a point coordinate (see the module docs).
|
||||
#[inline]
|
||||
pub fn is_admissible(x: f64) -> bool {
|
||||
x == 0.0 || (x.abs() >= MIN_MAGNITUDE && x.abs() <= MAX_MAGNITUDE)
|
||||
}
|
||||
|
||||
fn check<const N: usize>(c: [f64; N]) -> Result<[f64; N]> {
|
||||
match c.iter().find(|x| !is_admissible(**x)) {
|
||||
None => Ok(c),
|
||||
Some(x) => {
|
||||
Err(Error::Invalid(format!("point coordinate {x:e} is outside the exact-predicate domain (zero, or magnitude in [2^-120, 2^160])")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2D point whose coordinates are admissible for the exact predicates.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Point2([f64; 2]);
|
||||
|
||||
impl Point2 {
|
||||
pub fn new(x: f64, y: f64) -> Result<Self> {
|
||||
check([x, y]).map(Self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn coords(&self) -> [f64; 2] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[f64; 2]> for Point2 {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(c: [f64; 2]) -> Result<Self> {
|
||||
check(c).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A 3D point whose coordinates are admissible for the exact predicates.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Point3([f64; 3]);
|
||||
|
||||
impl Point3 {
|
||||
pub fn new(x: f64, y: f64, z: f64) -> Result<Self> {
|
||||
check([x, y, z]).map(Self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn coords(&self) -> [f64; 3] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[f64; 3]> for Point3 {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(c: [f64; 3]) -> Result<Self> {
|
||||
check(c).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
Reference in new issue
Block a user