forked from 1b-agent/fieldcraft-upstream
* mesh: boundary surface preparation (weld, manifold and orientation checks, exact self-intersection, feature edges) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * engine: mesh.surface.check on an open design's body or an imported surface Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * engine: struct-init the test session Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * surface: weld at an absolute distance too; the engine welds CAD tessellations at twice SolidCraft's linear tolerance Faces of a SolidCraft tessellation carry their own copies of shared boundary points, which agree only to within the kernel's tolerance (up to ~2e-6 mm on the test corpus). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: surface preparation in the literature survey, architecture and roadmap Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * surface: simplify after review - The weld distance travels with the mesh (SurfaceMesh::weld_distance, set by Shape::tessellate), so every consumer welds a tessellation correctly without opting in. - Exact tests are infallible on predicates::orient2d_projected (shared with generic_orient2d), and compute each vertex's plane side once per pair with an early reject. - One Box3 for the weld, the BVH and shells; a Half struct for edges; the weld's two lookups share one loop, with chained grid cells; cached normals for feature edges; shared parameter helpers. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * corpus: boundary surface preparation as a corpus stage 20 of 21 parts are valid boundaries with their expected genus; cross-cylinders' tessellation has cracks and flipped triangles where its two intersection curves meet. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --------- Co-authored-by: jyviko <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
177 lines
5.8 KiB
Rust
177 lines
5.8 KiB
Rust
//! 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, orient2d_projected};
|
||
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,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The sign of the volume `det[b − a, c − a, d − a]`: positive when `d` lies on the side of
|
||
/// face `a, b, c`'s right-hand normal (the tet4 node order). It is [`orient3d`] negated.
|
||
#[inline]
|
||
pub fn tet_volume_sign(a: &Point3, b: &Point3, c: &Point3, d: &Point3) -> Sign {
|
||
-orient3d(a, b, c, d)
|
||
}
|
||
|
||
/// 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;
|