geometry_predicates/lib.rs
1//! A safe Rust port of the [robust adaptive floating-point geometric predicates](https://www.cs.cmu.edu/~quake/robust.html).
2//!
3//! This crate provides a Rust solution to efficient exact geometry predicates
4//! used widely for computational geometry.
5//!
6//! In addition, the building blocks of these predicates, namely the adaptive precision
7//! floating-point arithmetic primitives, are also exposed in [`predicates`] to allow for extensions
8//! to other predicates or exact geometric constructions.
9//!
10//! ## Background
11//!
12//! These predicates have been a staple in computational geometry for many years now
13//! and are widely used in industry. In the context of geometry algorithms, it is
14//! often essential to determine the orientation of a point with respect to a line (or a
15//! plane) and whether a point lies inside a circle (or a sphere) or not. The reason
16//! why these tests often need to be exact is because many geometry algorithms
17//! ask questions (to determine orientation or in-circle/sphere) about point
18//! configurations that require consistent answers. For instance, if `a`, `b`, and
19//! `c` are three points on a 2D plane, to ask where `b` with respect to the line
20//! through `a` and `c` (left-of, right-of, or coincident) is the same as asking where
21//! `a` lies with respect to the line through `c` and `b`.
22//! In Rust, this condition can be written as
23//! ```
24//! # use geometry_predicates::orient2d;
25//! # let a = [1.0, 2.0];
26//! # let b = [3.0, 4.0];
27//! # let c = [5.0, 6.0];
28//! assert_eq!(orient2d(a,c,b).signum(), orient2d(c,b,a).signum());
29//! ```
30//!
31//! Mathematically, predicates like `orient2d` are
32//! defined as
33//!```verbatim
34//! ⎛⎡ax ay 1⎤⎞
35//!orient2d([ax,ay],[bx,by],[cx,cy]) := det⎜⎢bx by 1⎥⎟
36//! ⎝⎣cx cy 1⎦⎠
37//!```
38//!
39//! It's easy to see that these predicates solve the problem of
40//! computing the determinant of small matrices with the correct sign, regardless of how
41//! close the matrix is to being singular.
42//!
43//! For instance to compute the determinant of a matrix `[a b; c d]` with the
44//! correct sign, we can invoke
45//! ```
46//! # use geometry_predicates::orient2d;
47//! # let a = 1.0;
48//! # let b = 2.0;
49//! # let c = 3.0;
50//! # let d = 4.0;
51//! assert_eq!(orient2d([a,b], [c,d], [0.0,0.0]), a*d - b*c);
52//! ```
53//!
54//! For more details please refer to the [original
55//! webpage](https://www.cs.cmu.edu/~quake/robust.html) for these predicates.
56//!
57//! ## Caveats
58//!
59//! These predicates do NOT handle exponent overflow [\[1\]], which means inputs with floats smaller than
60//! `1e-142` or larger than `1e201` may not produce accurate results. This is true for the original
61//! predicates in `predicates.c` as well as other Rust ports and bindings for these predicates.
62//!
63//! ## References
64//!
65//! - [\[1\] Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates][\[1\]],
66//! Discrete & Computational Geometry 18(3):305–363, October 1997.
67//! - [\[2\] Robust Adaptive Floating-Point Geometric Predicates Proceedings of the Twelfth Annual][\[2\]],
68//! Symposium on Computational Geometry (Philadelphia, Pennsylvania), pages 141–150, Association for
69//! Computing Machinery, May 1996
70//!
71//! [\[1\]]: http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
72//! [\[2\]]: http://www.cs.berkeley.edu/~jrs/papers/robust-predicates.pdf
73#![no_std]
74pub mod predicates;
75
76#[cfg(feature = "transpiled")]
77mod transpiled;
78
79// The following predicates are exposed at the top level.
80// There are other alternative robust implementations (but typically slower) of these predicates.
81// We use those to check the adaptive implementations.
82pub use predicates::{
83 // Adaptive robust predicates.
84 incircle,
85 // Fast inexact predicates.
86 incircle_fast,
87 insphere,
88 insphere_fast,
89 orient2d,
90 orient2d_fast,
91 orient3d,
92 orient3d_fast,
93};
94
95#[cfg(test)]
96mod tests {
97 extern crate rand;
98 use self::rand::{Rng, SeedableRng, StdRng};
99 use super::*;
100
101 use predicates::{
102 incircle_exact, incircle_slow, insphere_exact, insphere_slow, orient2d_exact,
103 orient2d_slow, orient3d_exact, orient3d_slow,
104 };
105
106 const SEED: &[usize] = &[1, 2, 3, 4];
107
108 /*
109 * Note on robustness testing.
110 *
111 * These predicates do NOT handle overflow or underflowo of the exponent.
112 * Quoting Shechuk's predicates paper directly:
113 *
114 * > The four predicates implemented [in this library] will not overflow nor underflow if their
115 * > inputs have exponents in the range [-142, 201] and IEEE 754 double precision arithmetic
116 * > is used.
117 * - Jonathan Shechuk 1997
118 *
119 * We will therefore be careful to test for inputs with exponents in that range and not beyond.
120 */
121
122 const EXP_BOUNDS: [i32; 2] = [-142, 201];
123
124 /// Generate a tolerance value with exponent no less than -142.
125 fn tol(rng: &mut StdRng) -> f64 {
126 let max_exp = (EXP_BOUNDS[0] + 1) as f64;
127 10.0_f64.powi((max_exp * rng.gen::<f64>()).round() as i32) * (rng.gen::<f64>() - 0.5)
128 }
129
130 /*
131 * Many of the tests below ensure that the predicates produce expected results for all
132 * permutations of the inputs. Thus, below we write an adhoc QuickPerm implementation to
133 * generate swap based permutations.
134 *
135 * The following is a basic countdown implementation of QuickPerm (see quickperm.org).
136 * This implementation can be refactored to use const generics when those stabilize.
137 */
138 macro_rules! quick_perm_impl {
139 ($n:expr, $struct_name:ident) => {
140 struct $struct_name<T> {
141 perm: [T; $n],
142 p: [usize; $n + 1],
143 index: usize,
144 }
145
146 impl<T> $struct_name<T> {
147 fn new(v: [T; $n]) -> Self {
148 let mut p = [0; $n + 1];
149 for i in 0..$n + 1 {
150 p[i] = i;
151 }
152 $struct_name {
153 perm: v,
154 p,
155 index: 0,
156 }
157 }
158 }
159
160 impl<T: Clone> Iterator for $struct_name<T> {
161 type Item = [T; $n];
162 fn next(&mut self) -> Option<Self::Item> {
163 let $struct_name { perm, p, index } = self;
164 let mut i = *index;
165 let n = p.len() - 1;
166 if i == 0 {
167 *index += 1;
168 return Some(perm.clone());
169 } else if i >= n {
170 return None;
171 }
172 p[i] -= 1;
173 let j = if i % 2 == 0 { 0 } else { p[i] };
174 perm.swap(j, i);
175 i = 1;
176 while p[i] == 0 {
177 p[i] = i;
178 i += 1;
179 }
180 *index = i;
181 Some(perm.clone())
182 }
183 }
184 };
185 }
186
187 quick_perm_impl!(3, QuickPerm3);
188 quick_perm_impl!(4, QuickPerm4);
189 quick_perm_impl!(5, QuickPerm5);
190
191 /*
192 * The tests below have specific purposes.
193 *
194 * - _robustness_ tests check that the adaptive predicates work in degenerate or close to degenerate
195 * configurations
196 * - _regression_ tests verify the adpative predicates against their _slow and _exact variants.
197 * - _transpiled_regression_ tests verify the predicates against the directly transpiled
198 * (unrefactored) version of the library.
199 */
200
201 #[test]
202 fn orient2d_test() {
203 let a = [0.0, 1.0];
204 let b = [2.0, 3.0];
205 let c = [4.0, 5.0];
206 assert_eq!(orient2d(a, b, c), 0.0);
207 }
208
209 #[cfg(feature = "transpiled")]
210 #[test]
211 fn orient2d_transpiled_regression_test() {
212 unsafe {
213 transpiled::exactinit();
214 }
215 let mut rng: StdRng = SeedableRng::from_seed(SEED);
216
217 let n = 99999;
218 for _ in 0..n {
219 let a = [tol(&mut rng), tol(&mut rng)];
220 let b = [12.0, 12.0];
221 let c = [24.0, 24.0];
222 let o2d = orient2d;
223 let o2d_transpiled = transpiled::orient2d;
224 for p in QuickPerm3::new([a, b, c]) {
225 assert_eq!(
226 o2d(p[0], p[1], p[2]),
227 o2d_transpiled(p[0], p[1], p[2]),
228 "{:?}",
229 a
230 );
231 }
232 }
233 }
234
235 #[test]
236 fn orient2d_robustness_test() {
237 let mut rng: StdRng = SeedableRng::from_seed(SEED);
238 let n = 99999;
239
240 for o2d in &[orient2d, orient2d_exact, orient2d_slow] {
241 for _ in 0..n {
242 let pa = [tol(&mut rng), tol(&mut rng)];
243 let pb = [12.0, 12.0];
244 let pc = [24.0, 24.0];
245
246 let main = o2d(pa, pb, pc);
247 if main == 0.0 {
248 for [a, b, c] in QuickPerm3::new([pa, pb, pc]) {
249 assert_eq!(o2d(a, b, c), 0.0);
250 }
251 }
252
253 let pred = main > 0.0;
254 for (i, [a, b, c]) in QuickPerm3::new([pa, pb, pc]).enumerate() {
255 let t = o2d(a, b, c);
256 assert_eq!(pred, if i % 2 == 0 { t > 0.0 } else { t < 0.0 });
257 }
258 }
259 }
260 }
261
262 // The following test verifies equivalence of all of the robust orient2d variants (including
263 // exact and slow variants).
264 #[test]
265 fn orient2d_regression_test() {
266 let mut rng: StdRng = SeedableRng::from_seed(SEED);
267
268 let n = 99999;
269 for _ in 0..n {
270 let pa = [tol(&mut rng), tol(&mut rng)];
271 let pb = [12.0, 12.0];
272 let pc = [24.0, 24.0];
273
274 let o2d = predicates::orient2d;
275 let o2de = predicates::orient2d_exact;
276 let o2ds = predicates::orient2d_slow;
277
278 // Test all permutations.
279
280 for [a, b, c] in QuickPerm3::new([pa, pb, pc]) {
281 assert_eq!(
282 o2d(a, b, c).partial_cmp(&0.0),
283 o2de(a, b, c).partial_cmp(&0.0),
284 "{:?}",
285 pa
286 );
287 assert_eq!(
288 o2d(a, b, c).partial_cmp(&0.0),
289 o2ds(a, b, c).partial_cmp(&0.0),
290 "{:?}",
291 pa
292 );
293 }
294 }
295 }
296
297 #[test]
298 fn orient2d_fast_test() {
299 let a = [0.0, 1.0];
300 let b = [2.0, 3.0];
301 let c = [4.0, 5.0];
302 assert_eq!(orient2d_fast(a, b, c), 0.0);
303
304 // The fast orientation test should also work when the given points are sufficiently
305 // non-colinear.
306 let mut rng: StdRng = SeedableRng::from_seed(SEED);
307 let tol = 5.0e-10; // will not work with 5.0e-14
308
309 for _ in 0..999 {
310 let a = [0.5 + tol * rng.gen::<f64>(), 0.5 + tol * rng.gen::<f64>()];
311 let b = [12.0, 12.0];
312 let c = [24.0, 24.0];
313 assert_eq!(orient2d_fast(a, b, c) > 0.0, orient2d_fast(b, c, a) > 0.0);
314 assert_eq!(orient2d_fast(b, c, a) > 0.0, orient2d_fast(c, a, b) > 0.0);
315 assert_eq!(orient2d_fast(a, b, c) > 0.0, orient2d_fast(b, a, c) < 0.0);
316 assert_eq!(orient2d_fast(a, b, c) > 0.0, orient2d_fast(a, c, b) < 0.0);
317 }
318 }
319
320 #[test]
321 fn orient3d_test() {
322 let a = [0.0, 1.0, 6.0];
323 let b = [2.0, 3.0, 4.0];
324 let c = [4.0, 5.0, 1.0];
325 let d = [6.0, 2.0, 5.3];
326 assert_eq!(orient3d(a, b, c, d), 10.0);
327 }
328
329 #[cfg(feature = "transpiled")]
330 #[test]
331 fn orient3d_transpiled_regression_test() {
332 unsafe {
333 transpiled::exactinit();
334 }
335 let mut rng: StdRng = SeedableRng::from_seed(SEED);
336
337 let n = 9999;
338 for _ in 0..n {
339 let pa = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
340 let pb = [12.0, 12.0, 12.0];
341 let pc = [24.0, 24.0, 24.0];
342 let pd = [48.0, 48.0, 48.0];
343
344 let o3d = predicates::orient3d;
345 let o3d_transpiled = transpiled::orient3d;
346
347 // Test all permutations.
348 for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
349 assert_eq!(o3d(a, c, d, b), o3d_transpiled(a, c, d, b), "{:?}", pa);
350 }
351 }
352 }
353
354 // The following test verifies equivalence of all of the robust orient3d variants.
355 #[test]
356 fn orient3d_regression_test() {
357 let mut rng: StdRng = SeedableRng::from_seed(SEED);
358
359 let n = 5000;
360 for _ in 0..n {
361 let pa = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
362 let pb = [12.0, 12.0, 12.0];
363 let pc = [24.0, 24.0, 24.0];
364 let pd = [48.0, 48.0, 48.0];
365
366 let o3d = predicates::orient3d;
367 let o3de = predicates::orient3d_exact;
368 let o3ds = predicates::orient3d_slow;
369
370 // Test all permutations.
371 // Actually these don't need to be exactly equal, they just need to compare equally to
372 // 0.0. It just so happens that they are also equal.
373 for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
374 assert_eq!(o3d(a, c, d, b), o3de(a, c, d, b), "{:?}", pa);
375 assert_eq!(o3d(a, c, d, b), o3ds(a, c, d, b), "{:?}", pa);
376 }
377 }
378 }
379
380 #[test]
381 fn orient3d_robustness_test() {
382 let mut rng: StdRng = SeedableRng::from_seed(SEED);
383
384 let n = 999;
385
386 for o3d in &[orient3d, orient3d_exact, orient3d_slow] {
387 for _ in 0..n {
388 let pa = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
389 let pb = [12.0, 12.0, 12.0];
390 let pc = [24.0, 24.0, 24.0];
391 let pd = [48.0, 48.0, 48.0];
392
393 // Test all permutations.
394
395 let main = o3d(pa, pb, pc, pd);
396
397 if main == 0.0 {
398 for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
399 assert_eq!(o3d(a, b, c, d), 0.0);
400 }
401 }
402
403 let pred = main > 0.0;
404 for (i, [a, b, c, d]) in QuickPerm4::new([pa, pb, pc, pd]).enumerate() {
405 let t = o3d(a, b, c, d);
406 assert_eq!(
407 pred,
408 if i % 2 == 0 { t > 0.0 } else { t < 0.0 },
409 "{} vs. {} at {:?}",
410 t,
411 -main,
412 pa
413 );
414 }
415 }
416 }
417 }
418
419 #[test]
420 fn orient3d_fast_test() {
421 let a = [0.0, 1.0, 6.0];
422 let b = [2.0, 3.0, 4.0];
423 let c = [4.0, 5.0, 1.0];
424 let d = [6.0, 2.0, 5.3];
425 assert!(f64::abs(orient3d_fast(a, b, c, d) - 10.0) < 1.0e-4);
426
427 // The fast orientation test should also work when the given points are sufficiently
428 // non-colinear.
429 let mut rng: StdRng = SeedableRng::from_seed(SEED);
430 let tol = 5.0;
431
432 let a = [
433 0.5 + tol * rng.gen::<f64>(),
434 0.5 + tol * rng.gen::<f64>(),
435 0.5 + tol * rng.gen::<f64>(),
436 ];
437 let b = [12.0, 12.0, 12.0];
438 let c = [24.0, 24.0, 24.0];
439 let d = [48.0, 48.0, 48.0];
440 assert_eq!(
441 orient3d_fast(a, b, c, d) > 0.0,
442 orient3d_fast(b, c, a, d) > 0.0
443 );
444 assert_eq!(
445 orient3d_fast(b, a, c, d) < 0.0,
446 orient3d_fast(c, b, d, a) < 0.0
447 );
448 // The following orientations are expected to be inconsistent
449 // (this is why we need exact predicates)
450 assert_ne!(
451 orient3d_fast(b, c, d, a) > 0.0,
452 orient3d_fast(c, d, a, b) > 0.0
453 );
454 assert_ne!(
455 orient3d_fast(b, d, c, a) < 0.0,
456 orient3d_fast(c, d, b, a) < 0.0
457 );
458 }
459
460 #[test]
461 fn incircle_test() {
462 let a = [0.0, 1.0];
463 let b = [1.0, 0.0];
464 let c = [1.0, 1.0];
465 let d = [0.0, 0.0];
466 assert_eq!(incircle(a, b, c, d), 0.0);
467 let d = [0.1, 0.1];
468 assert!(incircle(a, b, c, d) > 0.0);
469 let d = [-0.1, -0.1];
470 assert!(incircle(a, b, c, d) < 0.0);
471 }
472
473 #[test]
474 fn incircle_robustness_test() {
475 let mut rng: StdRng = SeedableRng::from_seed(SEED);
476
477 let n = 999;
478 for ic in &[incircle, incircle_exact, incircle_slow] {
479 for _ in 0..n {
480 let pa = [0.0, 1.0];
481 let pb = [1.0, 0.0];
482 let pc = [1.0, 1.0];
483 let pd = [tol(&mut rng), tol(&mut rng)];
484
485 let main = ic(pa, pb, pc, pd);
486
487 if main == 0.0 {
488 for [a, b, c, d] in QuickPerm4::new([pa, pb, pc, pd]) {
489 assert_eq!(ic(a, b, c, d), 0.0);
490 }
491 }
492
493 let pred = main > 0.0;
494 for (i, [a, b, c, d]) in QuickPerm4::new([pa, pb, pc, pd]).enumerate() {
495 let t = ic(a, b, c, d);
496 assert_eq!(
497 pred,
498 if i % 2 == 0 { t > 0.0 } else { t < 0.0 },
499 "{} vs. {} at {:?}",
500 t,
501 -main,
502 pd
503 );
504 }
505 }
506 }
507 }
508
509 #[test]
510 fn insphere_test() {
511 let a = [0.0, 1.0, 0.0];
512 let b = [1.0, 0.0, 0.0];
513 let c = [0.0, 0.0, 1.0];
514 let d = [1.0, 1.0, 1.0];
515 let e = [0.0, 0.0, 0.0];
516 assert_eq!(insphere(a, b, c, d, e), 0.0);
517 let e = [0.1, 0.1, 0.1];
518 assert!(insphere(a, b, c, d, e) > 0.0);
519 let e = [-0.1, -0.1, -0.1];
520 assert!(insphere(a, b, c, d, e) < 0.0);
521 }
522
523 #[test]
524 fn insphere_robustness_test() {
525 let mut rng: StdRng = SeedableRng::from_seed(SEED);
526
527 let n = 99;
528 for is in &[insphere, insphere_exact, insphere_slow] {
529 for _ in 0..n {
530 let pa = [0.0, 1.0, 0.0];
531 let pb = [1.0, 0.0, 0.0];
532 let pc = [0.0, 0.0, 1.0];
533 let pd = [1.0, 1.0, 1.0];
534 let pe = [tol(&mut rng), tol(&mut rng), tol(&mut rng)];
535
536 let main = is(pa, pb, pc, pd, pe);
537
538 if main == 0.0 {
539 for [a, b, c, d, e] in QuickPerm5::new([pa, pb, pc, pd, pe]) {
540 assert_eq!(is(a, b, c, d, e), 0.0);
541 }
542 }
543
544 let pred = main > 0.0;
545 for (i, [a, b, c, d, e]) in QuickPerm5::new([pa, pb, pc, pd, pe]).enumerate() {
546 let t = is(a, b, c, d, e);
547 assert_eq!(
548 pred,
549 if i % 2 == 0 { t > 0.0 } else { t < 0.0 },
550 "{} vs. {} at {:?}",
551 t,
552 -main,
553 pe
554 );
555 }
556 }
557 }
558 }
559}