Skip to main content

implicit_predicates/
lib.rs

1//! Exact geometric predicates over explicit and implicit points.
2//!
3//! An implicit point is defined by an unevaluated construction, such as a
4//! point on an edge or inside a triangle. Predicates receive the construction
5//! itself and return the exact sign, without rounding the point's
6//! coordinates.
7//!
8//! **Status: early development.** Today the crate provides one predicate,
9//! `orient3d`, over points of any type that implements [`Point`]: an
10//! approximation of its coordinates, a bound on the error of each one, and
11//! its exact coordinates. The point types of the crate are `[f64; 3]`, an
12//! explicit point that is not validated; [`Explicit`], a validated explicit
13//! point; [`Lnc`], a point on the segment between two explicit points; and
14//! [`AnyPoint`], either of the last two. Like every predicate of the crate,
15//! `orient3d` is generated by `implicit-predicates-gen` from a short
16//! definition: a floating-point filter decides the common cases, and exact
17//! stages built on the expansions of `implicit-predicates-core` decide the
18//! rest. The point types are generated from their declarations in the same
19//! way. A type of your own can implement [`Point`] too, but the predicates
20//! rely on its contract without checking it: an implementation that breaks
21//! it makes them return wrong signs. `insphere` and the other kinds of
22//! implicit points do not exist yet.
23//!
24//! ```
25//! # #[cfg(feature = "std")] {
26//! use implicit_predicates::{Lnc, Sign, orient3d};
27//!
28//! let (a, b, c) = ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]);
29//! // a, b and c appear counter-clockwise from (1, 1, 1), above the plane.
30//! assert_eq!(orient3d(&a, &b, &c, &[1.0, 1.0, 1.0]), Some(Sign::Negative));
31//! assert_eq!(orient3d(&a, &b, &c, &[0.0, 0.0, 0.0]), Some(Sign::Positive));
32//! // A point on the plane.
33//! assert_eq!(orient3d(&a, &b, &c, &[0.5, 0.25, 0.25]), Some(Sign::Zero));
34//! // A point on the edge between b and c, c + 0.3 (b - c): on the plane,
35//! // exactly.
36//! let m = Lnc::new(b, c, 0.3)?;
37//! assert_eq!(orient3d(&a, &b, &c, &m), Some(Sign::Zero));
38//! # }
39//! # Ok::<(), implicit_predicates::PointError>(())
40//! ```
41//!
42//! A point of type [`Lnc`] is `q + t (p - q)`; its error bound is zero on
43//! the coordinates that are exact, here those that `p` and `q` share:
44//!
45//! ```
46//! use implicit_predicates::{Lnc, Point};
47//!
48//! let m = Lnc::new([0.3, 2.0, 5.0], [0.1, 2.0, 5.0], 0.5)?;
49//! assert!(m.err()[0] > 0.0);
50//! assert_eq!(&m.approx()[1..], &[2.0, 5.0]);
51//! assert_eq!(&m.err()[1..], &[0.0, 0.0]);
52//! # Ok::<(), implicit_predicates::PointError>(())
53//! ```
54//!
55//! # Features
56//!
57//! - `std`, on by default: `orient3d`, which keeps the exact coordinates
58//!   of implicit points in an arena of each thread. Without it the crate is
59//!   `no_std`, and [`orient3d_in`], which takes the arena, is the way to
60//!   call the predicate.
61#![cfg_attr(not(feature = "std"), no_std)]
62#![forbid(unsafe_code)]
63
64#[cfg(feature = "std")]
65mod arena;
66mod generated;
67
68pub use generated::points::{AnyPoint, Explicit, Lnc, PointError};
69pub use implicit_predicates_core::{
70    ExactCoords, ExactError, Point, Scratch, ScratchExhausted, Sign, Weight,
71};
72
73use generated::orient3d as orient3d_stages;
74
75/// The orientation of `d` with respect to the plane through `a`, `b` and
76/// `c`: the exact sign of the determinant
77///
78/// ```text
79/// | a.x - d.x   a.y - d.y   a.z - d.z |
80/// | b.x - d.x   b.y - d.y   b.z - d.z |
81/// | c.x - d.x   c.y - d.y   c.z - d.z |
82/// ```
83///
84/// at the exact coordinates of the points, which may be of any type that
85/// implements [`Point`]: `[f64; 3]`, [`Explicit`], [`Lnc`], [`AnyPoint`],
86/// or a type of your own.
87///
88/// The sign is [`Sign::Positive`] when `d` lies below the plane, taking
89/// "above" to be the side from which `a`, `b` and `c` appear in
90/// counter-clockwise order; [`Sign::Negative`] when `d` lies above it; and
91/// [`Sign::Zero`] when the four points are coplanar. This is the convention
92/// of Shewchuk's `orient3d`. Swapping any two points flips the sign.
93///
94/// A floating-point filter on the approximations of the points decides most
95/// calls. When it cannot, explicit points go through exact stages whose
96/// temporaries are on the stack; with an implicit point, the exact
97/// coordinates of the points are written to an arena of the calling thread,
98/// which starts at 16,384 `f64` and grows on the heap when a call needs
99/// more. [`orient3d_in`] takes the arena instead, and needs no `std`.
100///
101/// Requires the `std` feature, which is on by default.
102///
103/// # When the result is `None`
104///
105/// The result is `None` whenever an input of a point is not finite. For
106/// explicit points it is `Some` whenever every nonzero coordinate has a
107/// magnitude between `2^-188` and `2^239` (about `2.5e-57` and `8.8e71`):
108/// the last two stages evaluate with expansions, one on coordinates up to
109/// `2^238` and the other on coordinates up to `2^239`, and together they
110/// decide every such input. With an implicit point it is `Some` whenever
111/// every point can compute its exact coordinates (for an [`Lnc`], its
112/// inputs lie within the range of its exact arithmetic), with a weight of
113/// [`Weight::One`], and every nonzero component of those coordinates has a
114/// magnitude between `2^-188` and `2^235`, with at most 16 components per
115/// coordinate. A point of your own type whose exact coordinates have
116/// another weight gives `None`: the predicates do not support weights yet.
117/// Outside those ranges the sign is still exact when it is returned, but
118/// `None` is returned when no stage can decide. This is provisional: a
119/// later version will decide every finite input.
120///
121/// An [`Lnc`] computes each exact coordinate `q_k + t (p_k - q_k)` as the
122/// expansion of `p_k - q_k`, scaled by `t`, plus `q_k`: at most five
123/// components. It fails, and the result is `None`, when a coordinate of `p`
124/// or `q`, or a component of `t (p_k - q_k)`, reaches `2^1020` in magnitude,
125/// or when `t` or a nonzero component of `p_k - q_k` has a magnitude below
126/// `2^-480` or above `2^480`. Its components can be far smaller than its
127/// inputs, though, so a condition on the inputs is more useful: the result
128/// is `Some` whenever every point is
129///
130/// - explicit, with every nonzero coordinate of a magnitude between
131///   `2^-188` and `2^235`, or
132/// - an [`Lnc`] built with [`Lnc::new`] for which, for some integer `k`
133///   with `0 <= k <= 317`, `t` is zero or at least `2^-k` and every
134///   nonzero coordinate of `p` and `q` has a magnitude between `2^(k - 84)`
135///   and `2^233`: for example, coordinates between `2^-30` and `2^233` and
136///   `t` zero or at least `2^-54`. So `t` is always zero or at least
137///   `2^-317`, even when every coordinate of `p` and `q` is zero.
138///
139/// For such an [`Lnc`], every coordinate of `p` and `q` is a multiple of
140/// `2^(k - 136)` and `t` one of `2^(-k - 52)`, so every value its
141/// computation handles is a multiple of `2^-188`: so are the exact sums and
142/// products of such values, their roundings and their rounding errors. And,
143/// as `0 <= t <= 1`, the components add up in magnitude to at most
144/// `|t| (|p_k| + |q_k|) + |q_k|`, at most `3 * 2^233`, up to a relative
145/// error far below `1/3`, so each is below `2^235`. And `t`, being zero or
146/// between `2^-317` and 1, is in the product domain. The condition is
147/// sufficient, not necessary. For `k >= 1` its lower ends are reached:
148/// with `p_k = 2^(k - 84) (1 + 2^-52)` below the last bit of `q_k` and
149/// `t = 2^-k (1 + 2^-52)`, a component is exactly `2^-188`, and with half
150/// that `p_k` or half that `t` one is below `2^-188`, so a call that only
151/// an exact stage can decide gives `None`. Three inputs outside the
152/// condition that give `None`:
153///
154/// ```
155/// use implicit_predicates::{Lnc, orient3d};
156///
157/// let (a, c) = ([1.0, 0.25, 0.5], [0.3, 0.7, 5.0]);
158/// // `t = 0.1` is at least `2^-4`, but `q.x` is below `2^-80`.
159/// let b = [2f64.powi(-140), 3.0, 0.75];
160/// assert_eq!(orient3d(&a, &b, &c, &Lnc::new(a, b, 0.1)?), None);
161/// // The coordinates are at least `2^-4`, so `k` can be up to 80, but `t`
162/// // is below `2^-80`.
163/// let b = [0.1, 3.0, 0.75];
164/// assert_eq!(orient3d(&a, &b, &c, &Lnc::new(a, b, 2f64.powi(-200))?), None);
165/// // Every coordinate is zero, but `t` is below `2^-317`, and below the
166/// // product domain too.
167/// let (a, b, c) = ([1.0, -1.0, 0.0], [0.0, 1.0, -1.0], [1.0, 0.0, -1.0]);
168/// let m = Lnc::new([0.0; 3], [0.0; 3], 2f64.powi(-600))?;
169/// assert_eq!(orient3d(&a, &b, &c, &m), None);
170/// # Ok::<(), implicit_predicates::PointError>(())
171/// ```
172///
173/// # Examples
174///
175/// ```
176/// use implicit_predicates::{Lnc, Sign, orient3d};
177///
178/// let (a, b, c) = ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]);
179/// // a, b and c appear counter-clockwise from (1, 1, 1), above the plane.
180/// assert_eq!(orient3d(&a, &b, &c, &[1.0, 1.0, 1.0]), Some(Sign::Negative));
181/// assert_eq!(orient3d(&a, &b, &c, &[0.0, 0.0, 0.0]), Some(Sign::Positive));
182/// // A point on the plane, and one that is not a point.
183/// assert_eq!(orient3d(&a, &b, &c, &[0.5, 0.25, 0.25]), Some(Sign::Zero));
184/// assert_eq!(orient3d(&a, &b, &c, &[f64::NAN, 0.0, 0.0]), None);
185///
186/// // A point on the edge between a and b, b + 0.1 (a - b), is on the
187/// // plane, exactly, although its coordinates are not representable.
188/// let m = Lnc::new(a, b, 0.1)?;
189/// assert_eq!(orient3d(&a, &b, &c, &m), Some(Sign::Zero));
190/// assert_eq!(orient3d(&m, &b, &c, &[1.0, 1.0, 1.0]), Some(Sign::Negative));
191/// # Ok::<(), implicit_predicates::PointError>(())
192/// ```
193#[cfg(feature = "std")]
194#[inline]
195pub fn orient3d(a: &impl Point, b: &impl Point, c: &impl Point, d: &impl Point) -> Option<Sign> {
196    if let Some(sign) = orient3d_stages::filter_points(a, b, c, d) {
197        return Some(sign);
198    }
199    if a.is_explicit() & b.is_explicit() & c.is_explicit() & d.is_explicit() {
200        return orient3d_stages::exact_stages([a.approx(), b.approx(), c.approx(), d.approx()]);
201    }
202    orient3d_in_thread_arena(a, b, c, d)
203}
204
205/// The exact stages of [`orient3d`] for points that are not all explicit,
206/// in the arena of the thread.
207#[cfg(feature = "std")]
208#[cold]
209#[inline(never)]
210fn orient3d_in_thread_arena(
211    a: &impl Point,
212    b: &impl Point,
213    c: &impl Point,
214    d: &impl Point,
215) -> Option<Sign> {
216    // The arena grows as needed: it is exhausted only past `u32::MAX`
217    // components, far more than any call of this predicate takes.
218    arena::with(|scratch| orient3d_stages::exact_stages_in(scratch, a, b, c, d)).unwrap_or(None)
219}
220
221/// `orient3d` with the arena `scratch` for the exact coordinates of
222/// implicit points, instead of the arena of the thread: the same result,
223/// or an error if `scratch` runs out of room. Available without `std`.
224///
225/// `scratch` is left as it was, on success and on error. It is not used
226/// when the filter decides, nor when every point is explicit. With implicit
227/// points the exact stages may need several thousand `f64`; an arena made
228/// with `Scratch::growable` (with the `alloc` feature of
229/// `implicit-predicates-core`) never runs out.
230///
231/// # Errors
232///
233/// [`ScratchExhausted`] if `scratch` runs out of room; a larger arena then
234/// gives the result.
235///
236/// # Examples
237///
238/// ```
239/// use implicit_predicates::{Lnc, Scratch, Sign, orient3d_in};
240///
241/// let (a, b, c) = ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]);
242/// let m = Lnc::new(a, b, 0.1)?;
243/// let mut buf = vec![0.0; 4096];
244/// let mut scratch = Scratch::borrowed(&mut buf);
245/// assert_eq!(orient3d_in(&mut scratch, &a, &b, &c, &m), Ok(Some(Sign::Zero)));
246/// assert_eq!(scratch.used(), 0);
247///
248/// // An arena too small is reported.
249/// let mut small = [0.0; 8];
250/// assert!(orient3d_in(&mut Scratch::borrowed(&mut small), &a, &b, &c, &m).is_err());
251/// # Ok::<(), implicit_predicates::PointError>(())
252/// ```
253#[inline]
254pub fn orient3d_in(
255    scratch: &mut Scratch<'_>,
256    a: &impl Point,
257    b: &impl Point,
258    c: &impl Point,
259    d: &impl Point,
260) -> Result<Option<Sign>, ScratchExhausted> {
261    orient3d_stages::staged_in(scratch, a, b, c, d)
262}
263
264/// The individual stages of each predicate, for tests and benchmarks.
265#[doc(hidden)]
266pub mod stages {
267    pub use crate::generated::orient3d;
268}