simplicity/lib.rs
1//! Implementation of [Simulation of Simplicity by Edelsbrunner and Mücke](https://arxiv.org/pdf/math/9410209.pdf)
2//!
3//! Simulation of simplicity is a technique for ignoring
4//! degeneracies when calculating geometric predicates,
5//! such as the orientation of one point with respect to a list of points.
6//! Each point **p**\_ *i* is perturbed by some polynomial
7//! in ε, a sufficiently small positive number.
8//! Specifically, coordinate *p\_(i,j)* is perturbed by ε^(3^(*d*\**i* - *j*)),
9//! where *d* is more than the number of dimensions.
10//!
11//! # Predicates
12//!
13//! ## Orientation
14//!
15//! The orientation of 2 points **p**\_0, **p**\_1 in 1-dimensional space is
16//! positive if **p**\_0 is to the right of **p**\_1 and negative otherwise.
17//! We don't consider the case where **p**\_0 = **p**\_1 because of the perturbations.
18//!
19//! The orientation of *n* points **p**\_0, ..., **p**\_(n - 1) in (n - 1)-dimensional space is
20//! the same as the orientation of **p**\_1, ..., **p**\_(n - 1) when looked at
21//! from **p**_0. In particular, the orientation of 3 points in 2-dimensional space
22//! is positive iff they form a left turn.
23//!
24//! Orientation predicates for 1, 2, and 3 dimensions are implemented.
25//! They return whether the orientation is positive.
26//!
27//! ## In Hypersphere
28//!
29//! The in-circle of 4 points measures whether the last point is inside
30//! the circle that goes through the first 3 points. Those 3 points
31//! are not collinear because of the perturbations.
32//!
33//! The in-sphere of 5 points measures whether the last point is inside
34//! the sphere that goes through the first 4 points. Those 4 points
35//! are not coplanar because of the perturbations.
36//!
37//! # Usage
38//!
39//! ```rust
40//! use simplicity::{nalgebra, orient_2d};
41//! use nalgebra::Vector2;
42//!
43//! let points = vec![
44//! Vector2::new(0.0, 0.0),
45//! Vector2::new(1.0, 0.0),
46//! Vector2::new(1.0, 1.0),
47//! Vector2::new(0.0, 1.0),
48//! Vector2::new(2.0, 0.0),
49//! ];
50//!
51//! // Positive orientation
52//! let result = orient_2d(&points, |l, i| l[i], 0, 1, 2);
53//! assert!(result);
54//!
55//! // Negative orientation
56//! let result = orient_2d(&points, |l, i| l[i], 0, 3, 2);
57//! assert!(!result);
58//!
59//! // Degenerate orientation, tie broken by perturbance
60//! let result = orient_2d(&points, |l, i| l[i], 0, 1, 4);
61//! assert!(result);
62//! let result = orient_2d(&points, |l, i| l[i], 4, 1, 0);
63//! assert!(!result);
64//! ```
65//!
66//! Because the predicates take an indexing function, this can be
67//! used for arbitrary lists without having to implement `Index` for them:
68//!
69//! ```rust
70//! # use simplicity::{nalgebra, orient_2d};
71//! # use nalgebra::Vector2;
72//! let points = vec![
73//! (Vector2::new(0.0, 0.0), 0.8),
74//! (Vector2::new(1.0, 0.0), 0.4),
75//! (Vector2::new(2.0, 0.0), 0.6),
76//! ];
77//!
78//! let result = orient_2d(&points, |l, i| l[i].0, 0, 1, 2);
79//! ```
80
81use robust_geo as rg;
82pub use nalgebra;
83
84use nalgebra::{Vector1, Vector2, Vector3};
85type Vec1 = Vector1<f64>;
86type Vec2 = Vector2<f64>;
87type Vec3 = Vector3<f64>;
88
89macro_rules! sorted_fn {
90 ($name:ident, $n:expr) => {
91 /// Sorts an array of $n elements
92 /// and returns the sorted array,
93 /// along with the parity of the permutation;
94 /// `false` if even and `true` if odd.
95 fn $name<Idx: Ord + Copy>(mut arr: [Idx; $n]) -> ([Idx; $n], bool) {
96 let mut num_swaps = 0;
97
98 for i in 1..$n {
99 for j in (0..i).rev() {
100 if arr[j] > arr[j + 1] {
101 arr.swap(j, j + 1);
102 num_swaps += 1;
103 } else {
104 break;
105 }
106 }
107 }
108 (arr, num_swaps % 2 != 0)
109 }
110 };
111}
112
113sorted_fn!(sorted_3, 3);
114sorted_fn!(sorted_4, 4);
115sorted_fn!(sorted_5, 5);
116
117/// Returns whether the orientation of 2 points in 1-dimensional space
118/// is positive after perturbing them; that is, if the 1st one is
119/// to the right of the 2nd one.
120///
121/// Takes a list of all the points in consideration, an indexing function,
122/// and 2 indexes to the points to calculate the orientation of.
123///
124/// # Example
125///
126/// ```
127/// # use simplicity::{nalgebra, orient_1d};
128/// # use nalgebra::Vector1;
129/// let points = vec![0.0, 1.0, 2.0, 1.0];
130/// let positive = orient_1d(&points, |l, i| Vector1::new(l[i]), 1, 3);
131/// // points[1] gets perturbed farther to the right than points[3]
132/// assert!(positive);
133/// ```
134pub fn orient_1d<T: ?Sized, Idx: Ord + Copy>(
135 list: &T,
136 index_fn: impl Fn(&T, Idx) -> Vec1,
137 i: Idx,
138 j: Idx,
139) -> bool {
140 let pi = index_fn(list, i);
141 let pj = index_fn(list, j);
142 pi > pj || (pi == pj && i < j)
143}
144
145macro_rules! case {
146 (2: $pi:ident, $pj:ident, @ m2, != $odd:expr) => {
147 let val = rg::magnitude_cmp_2d($pi, $pj);
148 if val != 0.0 {
149 return (val > 0.0) != $odd;
150 }
151 };
152
153 (2: $pi:ident, $pj:ident, @ m3, != $odd:expr) => {
154 let val = rg::magnitude_cmp_3d($pi, $pj);
155 if val != 0.0 {
156 return (val > 0.0) != $odd;
157 }
158 };
159
160 (2: $pi:ident, $pj:ident, $(@ $swiz:ident,)? != $odd:expr) => {
161 if $pi$(.$swiz)? != $pj$(.$swiz)? {
162 return ($pi$(.$swiz)? > $pj$(.$swiz)?) != $odd;
163 }
164 };
165
166 (3: $pi:ident, $pj:ident, $pk:ident, @ $swiz:ident m2, != $odd:expr) => {
167 let val = rg::sign_det_x_x2y2($pi.$swiz(), $pj.$swiz(), $pk.$swiz());
168 if val != 0.0 {
169 return (val > 0.0) != $odd;
170 }
171 };
172
173 (3: $pi:ident, $pj:ident, $pk:ident, @ $swiz:ident m3, != $odd:expr) => {
174 let val = rg::sign_det_x_x2y2z2($pi.$swiz(), $pj.$swiz(), $pk.$swiz());
175 if val != 0.0 {
176 return (val > 0.0) != $odd;
177 }
178 };
179
180 (3: $pi:ident, $pj:ident, $pk:ident, $(@ $swiz:ident,)? != $odd:expr) => {
181 let val = rg::orient_2d($pi$(.$swiz())?, $pj$(.$swiz())?, $pk$(.$swiz())?);
182 if val != 0.0 {
183 return (val > 0.0) != $odd;
184 }
185 };
186
187 (4: $pi:ident, $pj:ident, $pk:ident, $pl:ident, @ xy m2, != $odd:expr) => {
188 let val = rg::in_circle($pi, $pj, $pk, $pl);
189 if val != 0.0 {
190 return (val > 0.0) != $odd;
191 }
192 };
193
194 (4: $pi:ident, $pj:ident, $pk:ident, $pl:ident, @ $swiz:ident m3, != $odd:expr) => {
195 let val = rg::sign_det_x_y_x2y2z2($pi.$swiz(), $pj.$swiz(), $pk.$swiz(), $pl.$swiz());
196 if val != 0.0 {
197 return (val > 0.0) != $odd;
198 }
199 };
200
201 (4: $pi:ident, $pj:ident, $pk:ident, $pl:ident, $(@ $swiz:ident,)? != $odd:expr) => {
202 let val = rg::orient_3d($pi$(.$swiz())?, $pj$(.$swiz())?, $pk$(.$swiz())?, $pl$(.$swiz())?);
203 if val != 0.0 {
204 return (val > 0.0) != $odd;
205 }
206 };
207
208 (5: $pi:ident, $pj:ident, $pk:ident, $pl:ident, $pm:ident, @ xyz m3, != $odd:expr) => {
209 let val = rg::in_sphere($pi, $pj, $pk, $pl, $pm);
210 if val != 0.0 {
211 return (val > 0.0) != $odd;
212 }
213 };
214
215}
216
217/// Returns whether the orientation of 3 points in 2-dimensional space
218/// is positive after perturbing them; that is, if the 3 points
219/// form a left turn when visited in order.
220///
221/// Takes a list of all the points in consideration, an indexing function,
222/// and 3 indexes to the points to calculate the orientation of.
223///
224/// # Example
225///
226/// ```
227/// # use simplicity::{nalgebra, orient_2d};
228/// # use nalgebra::Vector2;
229/// let points = vec![
230/// Vector2::new(0.0, 0.0),
231/// Vector2::new(1.0, 0.0),
232/// Vector2::new(1.0, 1.0),
233/// Vector2::new(2.0, 2.0),
234/// ];
235/// let positive = orient_2d(&points, |l, i| l[i], 0, 1, 2);
236/// assert!(positive);
237/// let positive = orient_2d(&points, |l, i| l[i], 0, 3, 2);
238/// assert!(!positive);
239/// ```
240pub fn orient_2d<T: ?Sized, Idx: Ord + Copy>(
241 list: &T,
242 index_fn: impl Fn(&T, Idx) -> Vec2,
243 i: Idx,
244 j: Idx,
245 k: Idx,
246) -> bool {
247 let ([i, j, k], odd) = sorted_3([i, j, k]);
248 let pi = index_fn(list, i);
249 let pj = index_fn(list, j);
250 let pk = index_fn(list, k);
251
252 case!(3: pi, pj, pk, != odd);
253 case!(2: pk, pj, @ x, != odd);
254 case!(2: pj, pk, @ y, != odd);
255 case!(2: pi, pk, @ x, != odd);
256 !odd
257}
258
259/// Returns whether the orientation of 4 points in 3-dimensional space
260/// is positive after perturbing them; that is, if the last 3 points
261/// form a left turn when visited in order, looking from the first point.
262///
263/// Takes a list of all the points in consideration, an indexing function,
264/// and 4 indexes to the points to calculate the orientation of.
265///
266/// # Example
267///
268/// ```
269/// # use simplicity::{nalgebra, orient_3d};
270/// # use nalgebra::Vector3;
271/// let points = vec![
272/// Vector3::new(0.0, 0.0, 0.0),
273/// Vector3::new(1.0, 0.0, 0.0),
274/// Vector3::new(1.0, 1.0, 1.0),
275/// Vector3::new(2.0, -2.0, 0.0),
276/// Vector3::new(2.0, 3.0, 4.0),
277/// Vector3::new(0.0, 0.0, 1.0),
278/// Vector3::new(0.0, 1.0, 0.0),
279/// Vector3::new(3.0, 4.0, 5.0),
280/// ];
281/// let positive = orient_3d(&points, |l, i| l[i], 0, 1, 6, 5);
282/// assert!(!positive);
283/// let positive = orient_3d(&points, |l, i| l[i], 7, 4, 0, 2);
284/// assert!(positive);
285/// ```
286pub fn orient_3d<T: ?Sized, Idx: Ord + Copy>(
287 list: &T,
288 index_fn: impl Fn(&T, Idx) -> Vec3,
289 i: Idx,
290 j: Idx,
291 k: Idx,
292 l: Idx,
293) -> bool {
294 let ([i, j, k, l], odd) = sorted_4([i, j, k, l]);
295 let pi = index_fn(list, i);
296 let pj = index_fn(list, j);
297 let pk = index_fn(list, k);
298 let pl = index_fn(list, l);
299
300 case!(4: pi, pj, pk, pl, != odd);
301 case!(3: pj, pk, pl, @ xy, != odd);
302 case!(3: pj, pk, pl, @ zx, != odd);
303 case!(3: pj, pk, pl, @ yz, != odd);
304 case!(3: pi, pk, pl, @ yx, != odd);
305 case!(2: pk, pl, @ x, != odd);
306 case!(2: pl, pk, @ y, != odd);
307 case!(3: pi, pk, pl, @ xz, != odd);
308 case!(2: pk, pl, @ z, != odd);
309 // case!(3: pi, pk, pl, @ zy, != odd); Impossible
310 case!(3: pi, pj, pl, @ xy, != odd);
311 case!(2: pl, pj, @ x, != odd);
312 case!(2: pj, pl, @ y, != odd);
313 case!(2: pi, pl, @ x, != odd);
314 !odd
315}
316
317/// Returns whether the last point is inside the oriented circle that goes through
318/// the first 3 points after perturbing them.
319/// The first 3 points should be oriented positive or the result will be flipped.
320///
321/// Takes a list of all the points in consideration, an indexing function,
322/// and 4 indexes to the points to calculate the in-circle of.
323///
324/// # Example
325///
326/// ```
327/// # use simplicity::{nalgebra, in_circle};
328/// # use nalgebra::Vector2;
329/// let points = vec![
330/// Vector2::new(0.0, 2.0),
331/// Vector2::new(1.0, 1.0),
332/// Vector2::new(2.0, 1.0),
333/// Vector2::new(0.0, 0.0),
334/// Vector2::new(2.0, 3.0),
335/// ];
336/// let inside = in_circle(&points, |l, i| l[i], 0, 3, 2, 1);
337/// assert!(inside);
338/// let inside = in_circle(&points, |l, i| l[i], 2, 1, 3, 4);
339/// assert!(!inside);
340/// ```
341pub fn in_circle<T: ?Sized, Idx: Ord + Copy>(
342 list: &T,
343 index_fn: impl Fn(&T, Idx) -> Vec2 + Clone,
344 i: Idx,
345 j: Idx,
346 k: Idx,
347 l: Idx,
348) -> bool {
349 simplicity_derive::generate_in_hypersphere!{list, index_fn, i, j, k, l}
350 // let flip = !orient_2d(list, index_fn.clone(), i, j, k);
351 // let ([i, j, k, l], odd) = sorted_4([i, j, k, l]);
352 // let odd = odd != flip;
353
354 // let pi = index_fn(list, i);
355 // let pj = index_fn(list, j);
356 // let pk = index_fn(list, k);
357 // let pl = index_fn(list, l);
358
359 // case!(4: pi, pj, pk, pl, @ xy m2, != odd);
360 // case!(3: pj, pk, pl, @ xy, != odd);
361 // case!(3: pj, pl, pk, @ xy m2, != odd);
362 // case!(3: pj, pk, pl, @ yx m2, != odd);
363 // case!(3: pi, pk, pl, @ yx, != odd);
364 // case!(2: pk, pl, @ x, != odd);
365 // case!(2: pl, pk, @ y, != odd);
366 // // case!(3: pi, pk, pl, @ xy m2, != odd); Impossible
367 // // case!(2: pk, pl, @ m2, != odd); Impossible
368 // // case!(3: pi, pk, pl, @ zy, != odd); Impossible
369 // case!(3: pi, pj, pl, @ xy, != odd);
370 // case!(2: pl, pj, @ x, != odd);
371 // case!(2: pj, pl, @ y, != odd);
372 // case!(2: pi, pl, @ x, != odd);
373 // !odd
374}
375
376/// Returns whether the last point is inside the circle that goes through
377/// the first 3 points after perturbing them.
378///
379/// Takes a list of all the points in consideration, an indexing function,
380/// and 4 indexes to the points to calculate the in-circle of.
381///
382/// # Example
383///
384/// ```
385/// # use simplicity::{nalgebra, in_circle_unoriented};
386/// # use nalgebra::Vector2;
387/// let points = vec![
388/// Vector2::new(0.0, 2.0),
389/// Vector2::new(1.0, 1.0),
390/// Vector2::new(2.0, 1.0),
391/// Vector2::new(0.0, 0.0),
392/// Vector2::new(2.0, 3.0),
393/// ];
394/// let inside = in_circle_unoriented(&points, |l, i| l[i], 0, 2, 3, 1);
395/// assert!(inside);
396/// let inside = in_circle_unoriented(&points, |l, i| l[i], 2, 3, 1, 4);
397/// assert!(!inside);
398/// ```
399pub fn in_circle_unoriented<T: ?Sized, Idx: Ord + Copy>(
400 list: &T,
401 index_fn: impl Fn(&T, Idx) -> Vec2 + Clone,
402 i: Idx,
403 j: Idx,
404 k: Idx,
405 l: Idx,
406) -> bool {
407 orient_2d(list, index_fn.clone(), i, j, k) == in_circle(list, index_fn, i, j, k, l)
408}
409
410/// Returns whether the last point is inside the sphere that goes through
411/// the first 4 points after perturbing them.
412///
413/// Takes a list of all the points in consideration, an indexing function,
414/// and 5 indexes to the points to calculate the in-sphere of.
415///
416/// # Example
417///
418/// ```
419/// # use simplicity::{nalgebra, in_sphere};
420/// # use nalgebra::Vector3;
421/// let points = vec![
422/// Vector3::new(0.0, 0.0, 0.0),
423/// Vector3::new(4.0, 0.0, 0.0),
424/// Vector3::new(0.0, 4.0, 0.0),
425/// Vector3::new(0.0, 0.0, 4.0),
426/// Vector3::new(1.0, 1.0, 1.0),
427/// ];
428/// let inside = in_sphere(&points, |l, i| l[i], 0, 2, 1, 3, 4);
429/// assert!(inside);
430/// let inside = in_sphere(&points, |l, i| l[i], 2, 3, 1, 4, 0);
431/// assert!(!inside);
432/// ```
433pub fn in_sphere<T: ?Sized, Idx: Ord + Copy>(
434 list: &T,
435 index_fn: impl Fn(&T, Idx) -> Vec3 + Clone,
436 i: Idx,
437 j: Idx,
438 k: Idx,
439 l: Idx,
440 m: Idx,
441) -> bool {
442 simplicity_derive::generate_in_hypersphere!{list, index_fn, i, j, k, l, m}
443 // let flip = !orient_3d(list, index_fn.clone(), i, j, k, l);
444 // let ([i, j, k, l, m], odd) = sorted_5([i, j, k, l, m]);
445 // let odd = odd != flip;
446
447 // let pi = index_fn(list, i);
448 // let pj = index_fn(list, j);
449 // let pk = index_fn(list, k);
450 // let pl = index_fn(list, l);
451 // let pm = index_fn(list, m);
452
453 // case!(5: pi, pj, pk, pl, pm, @ xyz m3, != odd);
454 // case!(4: pj, pk, pm, pl, != odd);
455 // case!(4: pj, pk, pl, pm, @ xyz m3, != odd);
456 // case!(4: pj, pk, pl, pm, @ zxy m3, != odd);
457 // case!(4: pj, pk, pl, pm, @ yzx m3, != odd);
458 // case!(4: pi, pk, pl, pm, != odd);
459 // case!(3: pk, pl, pm, @ xy, != odd);
460 // case!(3: pk, pl, pm, @ zx, != odd);
461 // case!(3: pk, pl, pm, @ yz, != odd);
462 // case!(4: pi, pk, pl, pm, @ yxz m3, != odd);
463 // case!(3: pk, pl, pm, @ xyz m3, != odd);
464 // case!(3: pk, pm, pl, @ yzx m3, != odd);
465 // case!(4: pi, pk, pl, pm, @ xzy m3, != odd);
466 // case!(3: pk, pl, pm, @ zxy m3, != odd);
467 // case!(4: pi, pk, pl, pm, @ zyx m3, != odd);
468 // case!(4: pi, pj, pm, pl, != odd);
469 // case!(3: pj, pl, pm, @ yx, != odd);
470 // case!(3: pj, pl, pm, @ xz, != odd);
471 // case!(3: pj, pl, pm, @ zy, != odd);
472 // case!(3: pi, pl, pm, @ xy, != odd);
473 // case!(2: pm, pl, @ x, != odd);
474 // case!(2: pl, pm, @ y, != odd);
475 // case!(3: pi, pl, pm, @ zx, != odd);
476 // case!(2: pm, pl, @ z, != odd);
477 // case!(3: pi, pl, pm, @ yz, != odd);
478 // case!(4: pi, pj, pl, pm, @ xyz m3, != odd);
479 // case!(3: pj, pm, pl, @ xyz m3, != odd);
480 // case!(3: pj, pl, pm, @ yzx m3, != odd);
481 // case!(3: pi, pl, pm, @ xyz m3, != odd);
482 // case!(2: pl, pm, @ m3, != odd);
483 // case!(3: pi, pm, pl, @ yzx m3, != odd);
484 // case!(4: pi, pj, pl, pm, @ zxy m3, != odd);
485 // case!(3: pj, pm, pl, @ zxy m3, != odd);
486 // case!(3: pi, pl, pm, @ zxy m3, != odd);
487 // case!(4: pi, pj, pl, pm, @ yzx m3, != odd);
488 // case!(4: pi, pj, pk, pm, != odd);
489 // case!(3: pj, pk, pm, @ xy, != odd);
490 // case!(3: pj, pk, pm, @ zx, != odd);
491 // case!(3: pj, pk, pm, @ yz, != odd);
492 // case!(3: pi, pk, pm, @ yx, != odd);
493 // case!(2: pk, pm, @ x, != odd);
494 // case!(2: pm, pk, @ y, != odd);
495 // case!(3: pi, pk, pm, @ xz, != odd);
496 // case!(2: pk, pm, @ z, != odd);
497 // // case!(3: pi, pk, pm, @ zy, != odd); Impossible
498 // case!(3: pi, pj, pm, @ xy, != odd);
499 // case!(2: pm, pj, @ x, != odd);
500 // case!(2: pj, pm, @ y, != odd);
501 // case!(2: pi, pm, @ x, != odd);
502 // !odd
503}
504
505/// Returns whether the last point is inside the sphere that goes through
506/// the first 4 points after perturbing them.
507/// The first 4 points must be oriented positive or the result will be flipped.
508///
509/// Takes a list of all the points in consideration, an indexing function,
510/// and 5 indexes to the points to calculate the in-sphere of.
511///
512/// # Example
513///
514/// ```
515/// # use simplicity::{nalgebra, in_sphere_unoriented};
516/// # use nalgebra::Vector3;
517/// let points = vec![
518/// Vector3::new(0.0, 0.0, 0.0),
519/// Vector3::new(4.0, 0.0, 0.0),
520/// Vector3::new(0.0, 4.0, 0.0),
521/// Vector3::new(0.0, 0.0, 4.0),
522/// Vector3::new(1.0, 1.0, 1.0),
523/// ];
524/// let inside = in_sphere_unoriented(&points, |l, i| l[i], 0, 2, 3, 1, 4);
525/// assert!(inside);
526/// let inside = in_sphere_unoriented(&points, |l, i| l[i], 2, 3, 1, 4, 0);
527/// assert!(!inside);
528/// ```
529pub fn in_sphere_unoriented<T: ?Sized, Idx: Ord + Copy>(
530 list: &T,
531 index_fn: impl Fn(&T, Idx) -> Vec3 + Clone,
532 i: Idx,
533 j: Idx,
534 k: Idx,
535 l: Idx,
536 m: Idx,
537) -> bool {
538 orient_3d(list, index_fn.clone(), i, j, k, l) == in_sphere(list, index_fn, i, j, k, l, m)
539}
540
541///// Returns whether the last point is closer to the second point
542///// than it is to the first point.
543/////
544///// Takes a list of all the points in consideration, an indexing function,
545///// and 3 indexes to the points to calculate the distance-compare-3d of.
546//pub fn distance_cmp_3d<T: ?Sized>(
547// list: &T,
548// index_fn: impl Fn(&T, usize) -> Vec3 + Clone,
549// i: usize,
550// j: usize,
551// k: usize,
552//) -> bool {
553// let pi = index_fn(list, i);
554// let pj = index_fn(list, j);
555// let pk = index_fn(list, k);
556//
557// let val = rg::distance_cmp_3d(pi, pj, pk);
558// if val != 0.0 {
559// return val > 0.0;
560// }
561//
562// const DUMMY: bool = false;
563// if k < i && k < j {
564// case!(2: pj, pi, @ z, != DUMMY);
565// case!(2: pj, pi, @ y, != DUMMY);
566// case!(2: pj, pi, @ x, != DUMMY);
567// }
568//
569// return i < j
570//}
571
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576 use test_case::test_case;
577
578 // Test-specific to determine case reached
579 macro_rules! case {
580 ($arr:expr => $pi:ident, $pj:ident, @ m2) => {
581 let val = rg::magnitude_cmp_2d($pi, $pj);
582 if val != 0.0 {
583 return $arr;
584 }
585 };
586
587 ($arr:expr => $pi:ident, $pj:ident, @ m3) => {
588 let val = rg::magnitude_cmp_3d($pi, $pj);
589 if val != 0.0 {
590 return $arr;
591 }
592 };
593
594 ($arr:expr => $pi:ident, $pj:ident $(, @ $swiz:ident)?) => {
595 if $pi$(.$swiz)? != $pj$(.$swiz)? {
596 return $arr;
597 }
598 };
599
600 ($arr:expr => $pi:ident, $pj:ident, $pk:ident, @ $swiz:ident m2) => {
601 let val = rg::sign_det_x_x2y2($pi.$swiz(), $pj.$swiz(), $pk.$swiz());
602 if val != 0.0 {
603 return $arr;
604 }
605 };
606
607 ($arr:expr => $pi:ident, $pj:ident, $pk:ident, @ $swiz:ident m3) => {
608 let val = rg::sign_det_x_x2y2z2($pi.$swiz(), $pj.$swiz(), $pk.$swiz());
609 if val != 0.0 {
610 return $arr;
611 }
612 };
613
614 ($arr:expr => $pi:ident, $pj:ident, $pk:ident $(, @ $swiz:ident)?) => {
615 let val = rg::orient_2d($pi$(.$swiz())?, $pj$(.$swiz())?, $pk$(.$swiz())?);
616 if val != 0.0 {
617 return $arr;
618 }
619 };
620
621 ($arr:expr => $pi:ident, $pj:ident, $pk:ident, $pl:ident, @ xy m2) => {
622 let val = rg::in_circle($pi, $pj, $pk, $pl);
623 if val != 0.0 {
624 return $arr;
625 }
626 };
627
628 ($arr:expr => $pi:ident, $pj:ident, $pk:ident, $pl:ident, @ $swiz:ident m3) => {
629 let val = rg::sign_det_x_y_x2y2z2($pi.$swiz(), $pj.$swiz(), $pk.$swiz(), $pl.$swiz());
630 if val != 0.0 {
631 return $arr;
632 }
633 };
634
635 ($arr:expr => $pi:ident, $pj:ident, $pk:ident, $pl:ident $(, @ $swiz:ident)?) => {
636 let val = rg::orient_3d($pi$(.$swiz())?, $pj$(.$swiz())?, $pk$(.$swiz())?, $pl$(.$swiz())?);
637 if val != 0.0 {
638 return $arr;
639 }
640 };
641
642 ($arr:expr => $pi:ident, $pj:ident, $pk:ident, $pl:ident, $pm:ident, @ xyz m3) => {
643 let val = rg::in_sphere($pi, $pj, $pk, $pl, $pm);
644 if val != 0.0 {
645 return $arr;
646 }
647 };
648 }
649
650 // Copied from orient_2d
651 pub fn orient_2d_case<T: ?Sized>(
652 list: &T,
653 index_fn: impl Fn(&T, usize) -> Vec2,
654 i: usize,
655 j: usize,
656 k: usize,
657 ) -> [usize; 3] {
658 let ([i, j, k], _) = sorted_3([i, j, k]);
659 let pi = index_fn(list, i);
660 let pj = index_fn(list, j);
661 let pk = index_fn(list, k);
662
663 case!([3, 3, 3] => pi, pj, pk);
664 case!([2, 3, 3] => pk, pj, @ x);
665 case!([1, 3, 3] => pj, pk, @ y);
666 case!([2, 2, 3] => pi, pk, @ x);
667 [1, 2, 3]
668 }
669
670 // Copied from orient_3d
671 pub fn orient_3d_case<T: ?Sized>(
672 list: &T,
673 index_fn: impl Fn(&T, usize) -> Vec3,
674 i: usize,
675 j: usize,
676 k: usize,
677 l: usize,
678 ) -> [usize; 4] {
679 let ([i, j, k, l], _) = sorted_4([i, j, k, l]);
680 let pi = index_fn(list, i);
681 let pj = index_fn(list, j);
682 let pk = index_fn(list, k);
683 let pl = index_fn(list, l);
684
685 case!([4, 4, 4, 4] => pi, pj, pk, pl);
686 case!([3, 4, 4, 4] => pj, pk, pl, @ xy);
687 case!([2, 4, 4, 4] => pj, pk, pl, @ zx);
688 case!([1, 4, 4, 4] => pj, pk, pl, @ yz);
689 case!([3, 3, 4, 4] => pi, pk, pl, @ yx);
690 case!([2, 3, 4, 4] => pk, pl, @ x);
691 case!([1, 3, 4, 4] => pl, pk, @ y);
692 case!([2, 2, 4, 4] => pi, pk, pl, @ xz);
693 case!([1, 2, 4, 4] => pk, pl, @ z);
694 //case!([1, 1, 4, 4] => pi, pk, pl, @ zy); Impossible
695 case!([3, 3, 3, 4] => pi, pj, pl, @ xy);
696 case!([2, 3, 3, 4] => pl, pj, @ x);
697 case!([1, 3, 3, 4] => pj, pl, @ y);
698 case!([2, 2, 3, 4] => pi, pl, @ x);
699 [1, 2, 3, 4]
700 }
701
702 #[test]
703 fn orient_1d_positive() {
704 let points = vec![0.0, 1.0];
705 assert!(orient_1d(&points, |l, i| Vector1::new(l[i]), 1, 0))
706 }
707
708 #[test]
709 fn orient_1d_negative() {
710 let points = vec![0.0, 1.0];
711 assert!(!orient_1d(&points, |l, i| Vector1::new(l[i]), 0, 1))
712 }
713
714 #[test]
715 fn orient_1d_positive_degenerate() {
716 let points = vec![0.0, 0.0];
717 assert!(orient_1d(&points, |l, i| Vector1::new(l[i]), 0, 1))
718 }
719
720 #[test]
721 fn orient_1d_negative_degenerate() {
722 let points = vec![0.0, 0.0];
723 assert!(!orient_1d(&points, |l, i| Vector1::new(l[i]), 1, 0))
724 }
725
726 #[test_case([[0.0, 0.0], [1.0, 0.0], [2.0, 1.0]], [3,3,3] ; "General")]
727 #[test_case([[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]], [2,3,3] ; "Collinear")]
728 #[test_case([[0.0, 0.0], [0.0, 2.0], [0.0, 1.0]], [1,3,3] ; "Collinear, pj.x = pk.x")]
729 #[test_case([[1.0, 0.0], [0.0, 2.0], [0.0, 2.0]], [2,2,3] ; "pj = pk")]
730 #[test_case([[0.0, 0.0], [0.0, 2.0], [0.0, 2.0]], [1,2,3] ; "pj = pk, pi.x = pk.x")]
731 fn test_orient_2d(points: [[f64; 2]; 3], case: [usize; 3]) {
732 let points = points
733 .iter()
734 .copied()
735 .map(Vector2::from)
736 .collect::<Vec<_>>();
737 assert!(orient_2d(&points, |l, i| l[i], 0, 1, 2));
738 assert!(!orient_2d(&points, |l, i| l[i], 0, 2, 1));
739 assert!(!orient_2d(&points, |l, i| l[i], 1, 0, 2));
740 assert!(orient_2d(&points, |l, i| l[i], 1, 2, 0));
741 assert!(orient_2d(&points, |l, i| l[i], 2, 0, 1));
742 assert!(!orient_2d(&points, |l, i| l[i], 2, 1, 0));
743 assert_eq!(orient_2d_case(&points, |l, i| l[i], 0, 1, 2), case);
744 }
745
746 #[test_case([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], [4,4,4,4] ; "General")]
747 #[test_case([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [3.0, 4.0, 5.0], [2.0, 3.0, 4.0]], [3,4,4,4] ; "Coplanar")]
748 #[test_case([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [2.0, 2.0, 4.0], [3.0, 3.0, 5.0]], [2,4,4,4] ; "Coplanar, pj pk pl @ xy collinear")]
749 #[test_case([[1.0, 0.0, 0.0], [1.0, 1.0, 1.0], [1.0, 4.0, 2.0], [1.0, 5.0, 3.0]], [1,4,4,4] ; "Coplanar, pj.x = pk.x = pl.x or pj pk pl collinear")]
750 #[test_case([[0.0, 0.0, 0.0], [1.0, 2.0, 3.0], [2.0, 3.0, 4.0], [3.0, 4.0, 5.0]], [3,3,4,4] ; "pj pk pl collinear")]
751 #[test_case([[0.0, 0.0, 0.0], [1.0, 1.0, 3.0], [3.0, 3.0, 5.0], [2.0, 2.0, 4.0]], [2,3,4,4] ; "pj pk pl collinear, pi pk pl @ xy collinear")]
752 #[test_case([[0.0, 0.0, 0.0], [0.0, 1.0, 3.0], [0.0, 2.0, 4.0], [0.0, 3.0, 5.0]], [1,3,4,4] ; "pj pk pl collinear, pi pk pl @ xy collinear, pk.x = pl.x")]
753 #[test_case([[1.0, 0.0, 0.0], [0.0, 2.0, 3.0], [0.0, 2.0, 5.0], [0.0, 2.0, 4.0]], [2,2,4,4] ; "pj pk pl collinear, pi pk pl @ xy collinear, pk.xy = pl.xy")]
754 #[test_case([[0.0, 0.0, 0.0], [0.0, 2.0, 3.0], [0.0, 2.0, 4.0], [0.0, 2.0, 3.0]], [1,2,4,4] ; "pj pk pl collinear, pi.x = pk.x = pl.x or pi pk pl collinear, pk.xy = pl.xy")]
755 // , [1,1,4,4] ; "pk = pl and pi pk pl @ yz not collinear is impossible
756 #[test_case([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 1.0, 0.0], [2.0, 1.0, 0.0]], [3,3,3,4] ; "pk = pl")]
757 #[test_case([[0.0, 0.0, 0.0], [1.0, 1.0, 0.0], [2.0, 2.0, 0.0], [2.0, 2.0, 0.0]], [2,3,3,4] ; "pk = pl, pi pj pk @ xy collinear")]
758 #[test_case([[0.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 1.0, 0.0], [0.0, 1.0, 0.0]], [1,3,3,4] ; "pk = pl, pi pj pk @ xy collinear, pj.x = pk.x")]
759 #[test_case([[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 2.0, 0.0], [0.0, 2.0, 0.0]], [2,2,3,4] ; "pk = pl, pi pj pk @ xy collinear, pj.xy = pk.xy")]
760 #[test_case([[0.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 2.0, 0.0], [0.0, 2.0, 0.0]], [1,2,3,4] ; "pk = pl, pi pj pk @ xy collinear, pj.xy = pk.xy, pi.x = pk.x")]
761 fn test_orient_3d(points: [[f64; 3]; 4], case: [usize; 4]) {
762 let points = points
763 .iter()
764 .copied()
765 .map(Vector3::from)
766 .collect::<Vec<_>>();
767 // Trusting the insertion sort now
768 assert!(orient_3d(&points, |l, i| l[i], 0, 1, 2, 3));
769 assert!(!orient_3d(&points, |l, i| l[i], 3, 2, 0, 1));
770 assert_eq!(orient_3d_case(&points, |l, i| l[i], 0, 1, 2, 3), case);
771 }
772
773 #[test]
774 fn test_in_circle_unoriented_general() {
775 let points = [[0.0, 0.0], [0.0, 2.0], [2.0, 2.0], [1.0, 1.0]];
776 let points = points
777 .iter()
778 .copied()
779 .map(Vector2::from)
780 .collect::<Vec<_>>();
781 // Trusting the insertion sort now
782 assert!(in_circle_unoriented(&points, |l, i| l[i], 0, 1, 2, 3));
783 assert!(in_circle_unoriented(&points, |l, i| l[i], 0, 2, 1, 3));
784 assert!(in_circle_unoriented(&points, |l, i| l[i], 1, 2, 0, 3));
785 assert!(in_circle_unoriented(&points, |l, i| l[i], 1, 0, 2, 3));
786 assert!(in_circle_unoriented(&points, |l, i| l[i], 2, 0, 1, 3));
787 assert!(in_circle_unoriented(&points, |l, i| l[i], 2, 1, 0, 3));
788 assert!(
789 (in_circle_unoriented(&points, |l, i| l[i], 0, 1, 2, 3)
790 == in_circle_unoriented(&points, |l, i| l[i], 0, 1, 3, 2))
791 == (orient_2d(&points, |l, i| l[i], 0, 1, 3)
792 != orient_2d(&points, |l, i| l[i], 0, 1, 2))
793 );
794 }
795
796 // Not sure how to test this properly in a non-tedious way.
797 // Let's just test the first degenerate expansion for now.
798 #[test]
799 fn test_in_circle_unoriented_cocircular() {
800 let points = [[0.0, 0.0], [0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
801 let points = points
802 .iter()
803 .copied()
804 .map(Vector2::from)
805 .collect::<Vec<_>>();
806 // Trusting the insertion sort now
807 assert!(in_circle_unoriented(&points, |l, i| l[i], 1, 2, 3, 0));
808 assert!(in_circle_unoriented(&points, |l, i| l[i], 1, 3, 2, 0));
809 assert!(in_circle_unoriented(&points, |l, i| l[i], 2, 3, 1, 0));
810 assert!(in_circle_unoriented(&points, |l, i| l[i], 1, 2, 3, 0));
811 assert!(in_circle_unoriented(&points, |l, i| l[i], 3, 1, 2, 0));
812 assert!(in_circle_unoriented(&points, |l, i| l[i], 3, 2, 1, 0));
813 assert!(
814 (in_circle_unoriented(&points, |l, i| l[i], 0, 1, 2, 3)
815 == in_circle_unoriented(&points, |l, i| l[i], 0, 1, 3, 2))
816 == (orient_2d(&points, |l, i| l[i], 0, 1, 3)
817 != orient_2d(&points, |l, i| l[i], 0, 1, 2))
818 );
819 }
820
821 #[test]
822 fn test_in_sphere_unoriented_general() {
823 // Taking integers to shorten things
824 let points = [[0,0,0], [4,0,0], [0,4,0], [0,0,4], [1,1,1]];
825 let points = points
826 .iter()
827 .copied()
828 .map(|[x, y, z]| Vector3::new(x as f64, y as f64, z as f64))
829 .collect::<Vec<_>>();
830 // Trusting the insertion sort now
831 assert!(in_sphere_unoriented(&points, |l, i| l[i], 0, 1, 2, 3, 4));
832 assert!(in_sphere_unoriented(&points, |l, i| l[i], 0, 2, 1, 3, 4));
833 assert!(in_sphere_unoriented(&points, |l, i| l[i], 1, 2, 0, 3, 4));
834 assert!(in_sphere_unoriented(&points, |l, i| l[i], 1, 3, 0, 2, 4));
835 assert!(in_sphere_unoriented(&points, |l, i| l[i], 2, 3, 0, 1, 4));
836 assert!(in_sphere_unoriented(&points, |l, i| l[i], 2, 3, 1, 0, 4));
837 assert!(
838 (in_sphere_unoriented(&points, |l, i| l[i], 0, 1, 2, 3, 4)
839 == in_sphere_unoriented(&points, |l, i| l[i], 0, 1, 2, 4, 3))
840 == (orient_3d(&points, |l, i| l[i], 0, 1, 2, 3)
841 != orient_3d(&points, |l, i| l[i], 0, 1, 2, 4))
842 );
843 }
844
845 // Not sure how to test this properly in a non-tedious way.
846 // Let's just test the first degenerate expansion for now.
847 #[test]
848 fn test_in_sphere_unoriented_cospherical() {
849 let points = [[0,0,0], [0,0,0], [1,0,0], [0,0,1], [0,1,0]];
850 let points = points
851 .iter()
852 .copied()
853 .map(|[x, y, z]| Vector3::new(x as f64, y as f64, z as f64))
854 .collect::<Vec<_>>();
855 // Trusting the insertion sort now
856 assert!(in_sphere_unoriented(&points, |l, i| l[i], 1, 2, 3, 4, 0));
857 assert!(in_sphere_unoriented(&points, |l, i| l[i], 1, 3, 2, 4, 0));
858 assert!(in_sphere_unoriented(&points, |l, i| l[i], 2, 3, 1, 4, 0));
859 assert!(in_sphere_unoriented(&points, |l, i| l[i], 2, 4, 1, 3, 0));
860 assert!(in_sphere_unoriented(&points, |l, i| l[i], 3, 4, 1, 2, 0));
861 assert!(in_sphere_unoriented(&points, |l, i| l[i], 3, 4, 2, 1, 0));
862 assert!(
863 (in_sphere_unoriented(&points, |l, i| l[i], 0, 1, 2, 3, 4)
864 == in_sphere_unoriented(&points, |l, i| l[i], 0, 1, 2, 4, 3))
865 == (orient_3d(&points, |l, i| l[i], 0, 1, 2, 3)
866 != orient_3d(&points, |l, i| l[i], 0, 1, 2, 4))
867 );
868 }
869}