1use crate::vec::{Point2, Point3};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum Orientation {
15 CounterClockwise,
17 Clockwise,
19 Collinear,
21}
22
23const fn to_coord(p: Point2) -> robust::Coord<f64> {
25 robust::Coord { x: p.x(), y: p.y() }
26}
27
28#[inline]
33#[must_use]
34pub fn orient2d(a: Point2, b: Point2, c: Point2) -> f64 {
35 robust::orient2d(to_coord(a), to_coord(b), to_coord(c))
36}
37
38#[must_use]
40pub fn orientation2d(a: Point2, b: Point2, c: Point2) -> Orientation {
41 let det = orient2d(a, b, c);
42 if det > 0.0 {
43 Orientation::CounterClockwise
44 } else if det < 0.0 {
45 Orientation::Clockwise
46 } else {
47 Orientation::Collinear
48 }
49}
50
51#[must_use]
57pub fn in_circle(a: Point2, b: Point2, c: Point2, d: Point2) -> f64 {
58 robust::incircle(to_coord(a), to_coord(b), to_coord(c), to_coord(d))
59}
60
61#[must_use]
67pub fn winding_number(point: Point2, polygon: &[Point2]) -> i32 {
68 let n = polygon.len();
69 if n < 3 {
70 return 0;
71 }
72
73 let mut wn = 0i32;
74 for i in 0..n {
75 let j = (i + 1) % n;
76 let vi = polygon[i];
77 let vj = polygon[j];
78
79 if vi.y() <= point.y() {
80 if vj.y() > point.y() {
81 if orient2d(vi, vj, point) > 0.0 {
83 wn += 1;
84 }
85 }
86 } else if vj.y() <= point.y() {
87 if orient2d(vi, vj, point) < 0.0 {
89 wn -= 1;
90 }
91 }
92 }
93 wn
94}
95
96#[must_use]
100pub fn point_in_polygon(point: Point2, polygon: &[Point2]) -> bool {
101 winding_number(point, polygon) != 0
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
110pub enum Orientation3D {
111 Above,
113 Below,
115 Coplanar,
117}
118
119const fn to_coord3d(p: Point3) -> robust::Coord3D<f64> {
121 robust::Coord3D {
122 x: p.x(),
123 y: p.y(),
124 z: p.z(),
125 }
126}
127
128#[inline]
135#[must_use]
136pub fn orient3d(a: Point3, b: Point3, c: Point3, d: Point3) -> f64 {
137 robust::orient3d(to_coord3d(a), to_coord3d(b), to_coord3d(c), to_coord3d(d))
138}
139
140#[must_use]
143pub fn orientation3d(a: Point3, b: Point3, c: Point3, d: Point3) -> Orientation3D {
144 let det = orient3d(a, b, c, d);
145 if det > 0.0 {
146 Orientation3D::Below
147 } else if det < 0.0 {
148 Orientation3D::Above
149 } else {
150 Orientation3D::Coplanar
151 }
152}
153
154#[must_use]
160#[allow(clippy::many_single_char_names)]
161pub fn insphere(a: Point3, b: Point3, c: Point3, d: Point3, e: Point3) -> f64 {
162 robust::insphere(
163 to_coord3d(a),
164 to_coord3d(b),
165 to_coord3d(c),
166 to_coord3d(d),
167 to_coord3d(e),
168 )
169}
170
171#[allow(clippy::float_cmp)]
185#[must_use]
186pub fn orient2d_sos(a: Point2, b: Point2, c: Point2, ia: usize, ib: usize, ic: usize) -> f64 {
187 let det = orient2d(a, b, c);
188 if det != 0.0 {
189 return det;
190 }
191
192 let max_idx = ia.max(ib).max(ic);
195 if max_idx == ic {
196 1.0 } else if max_idx == ib {
198 -1.0 } else {
200 1.0 }
202}
203
204#[allow(clippy::float_cmp)]
214#[must_use]
215#[allow(clippy::too_many_arguments)]
216pub fn orient3d_sos(
217 a: Point3,
218 b: Point3,
219 c: Point3,
220 d: Point3,
221 ia: usize,
222 ib: usize,
223 ic: usize,
224 id: usize,
225) -> f64 {
226 let det = orient3d(a, b, c, d);
227 if det != 0.0 {
228 return det;
229 }
230
231 let max_idx = ia.max(ib).max(ic).max(id);
235 if max_idx == id {
236 1.0 } else if max_idx == ic {
238 -1.0 } else if max_idx == ib {
240 1.0 } else {
242 -1.0 }
244}
245
246#[cfg(test)]
247#[allow(clippy::float_cmp)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn orient2d_ccw() {
253 let a = Point2::new(0.0, 0.0);
254 let b = Point2::new(1.0, 0.0);
255 let c = Point2::new(0.0, 1.0);
256 assert!(orient2d(a, b, c) > 0.0);
257 assert_eq!(orientation2d(a, b, c), Orientation::CounterClockwise);
258 }
259
260 #[test]
261 fn orient2d_cw() {
262 let a = Point2::new(0.0, 0.0);
263 let b = Point2::new(0.0, 1.0);
264 let c = Point2::new(1.0, 0.0);
265 assert!(orient2d(a, b, c) < 0.0);
266 assert_eq!(orientation2d(a, b, c), Orientation::Clockwise);
267 }
268
269 #[test]
270 fn orient2d_collinear() {
271 let a = Point2::new(0.0, 0.0);
272 let b = Point2::new(1.0, 1.0);
273 let c = Point2::new(2.0, 2.0);
274 assert_eq!(orient2d(a, b, c), 0.0);
275 assert_eq!(orientation2d(a, b, c), Orientation::Collinear);
276 }
277
278 #[test]
279 fn orient2d_swap_reverses_sign() {
280 let a = Point2::new(0.0, 0.0);
281 let b = Point2::new(1.0, 0.0);
282 let c = Point2::new(0.5, 1.0);
283 let d1 = orient2d(a, b, c);
284 let d2 = orient2d(b, a, c);
285 assert!((d1 + d2).abs() < 1e-15, "swap should reverse sign");
286 }
287
288 #[test]
289 fn orient3d_basic() {
290 let a = Point3::new(0.0, 0.0, 0.0);
291 let b = Point3::new(1.0, 0.0, 0.0);
292 let c = Point3::new(0.0, 1.0, 0.0);
293 let above = Point3::new(0.0, 0.0, 1.0);
294 let below = Point3::new(0.0, 0.0, -1.0);
295 let on = Point3::new(0.5, 0.5, 0.0);
296
297 assert!(orient3d(a, b, c, above) < 0.0); assert!(orient3d(a, b, c, below) > 0.0); assert_eq!(orient3d(a, b, c, on), 0.0); assert_eq!(orientation3d(a, b, c, above), Orientation3D::Above);
302 assert_eq!(orientation3d(a, b, c, below), Orientation3D::Below);
303 assert_eq!(orientation3d(a, b, c, on), Orientation3D::Coplanar);
304 }
305
306 #[test]
307 fn insphere_inside() {
308 let a = Point3::new(1.0, 0.0, 0.0);
310 let b = Point3::new(0.0, 1.0, 0.0);
311 let c = Point3::new(0.0, 0.0, 1.0);
312 let d = Point3::new(0.0, 0.0, 0.0);
313 let center = Point3::new(0.25, 0.25, 0.25);
316 let result = insphere(a, b, c, d, center);
318 assert!(result.is_finite());
320 }
321
322 #[test]
323 fn winding_number_square() {
324 let square = vec![
325 Point2::new(0.0, 0.0),
326 Point2::new(1.0, 0.0),
327 Point2::new(1.0, 1.0),
328 Point2::new(0.0, 1.0),
329 ];
330 assert_eq!(winding_number(Point2::new(0.5, 0.5), &square), 1);
332 assert!(point_in_polygon(Point2::new(0.5, 0.5), &square));
333 assert_eq!(winding_number(Point2::new(2.0, 2.0), &square), 0);
335 assert!(!point_in_polygon(Point2::new(2.0, 2.0), &square));
336 }
337
338 #[test]
339 fn winding_number_triangle() {
340 let tri = vec![
341 Point2::new(0.0, 0.0),
342 Point2::new(4.0, 0.0),
343 Point2::new(2.0, 3.0),
344 ];
345 assert!(point_in_polygon(Point2::new(2.0, 1.0), &tri));
346 assert!(!point_in_polygon(Point2::new(5.0, 0.0), &tri));
347 }
348
349 #[test]
350 fn winding_number_degenerate() {
351 assert_eq!(winding_number(Point2::new(0.0, 0.0), &[]), 0);
353 assert_eq!(
354 winding_number(
355 Point2::new(0.0, 0.0),
356 &[Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)]
357 ),
358 0
359 );
360 }
361
362 use proptest::prelude::*;
363
364 proptest! {
365 #[test]
366 fn prop_orient2d_swap_sign(
367 ax in -10.0f64..10.0, ay in -10.0f64..10.0,
368 bx in -10.0f64..10.0, by in -10.0f64..10.0,
369 cx in -10.0f64..10.0, cy in -10.0f64..10.0,
370 ) {
371 let a = Point2::new(ax, ay);
372 let b = Point2::new(bx, by);
373 let c = Point2::new(cx, cy);
374 let d1 = orient2d(a, b, c);
375 let d2 = orient2d(b, a, c);
376 prop_assert!((d1 + d2).abs() < 1e-10, "d1={}, d2={}", d1, d2);
377 }
378 }
379
380 #[test]
381 fn orient2d_sos_never_zero() {
382 let a = Point2::new(0.0, 0.0);
384 let b = Point2::new(1.0, 1.0);
385 let c = Point2::new(2.0, 2.0);
386 assert_eq!(orient2d(a, b, c), 0.0);
387 assert_ne!(orient2d_sos(a, b, c, 0, 1, 2), 0.0);
388 }
389
390 #[test]
391 fn orient2d_sos_consistent() {
392 let a = Point2::new(0.0, 0.0);
394 let b = Point2::new(1.0, 1.0);
395 let c = Point2::new(2.0, 2.0);
396 let s1 = orient2d_sos(a, b, c, 0, 1, 2);
397 let s2 = orient2d_sos(a, b, c, 0, 1, 2);
398 assert_eq!(s1.signum(), s2.signum());
399 }
400
401 #[test]
402 fn orient3d_sos_never_zero() {
403 let a = Point3::new(0.0, 0.0, 0.0);
405 let b = Point3::new(1.0, 0.0, 0.0);
406 let c = Point3::new(0.0, 1.0, 0.0);
407 let d = Point3::new(0.5, 0.5, 0.0);
408 assert_eq!(orient3d(a, b, c, d), 0.0);
409 assert_ne!(orient3d_sos(a, b, c, d, 0, 1, 2, 3), 0.0);
410 }
411
412 #[test]
413 fn orient3d_sos_passes_through_nonzero() {
414 let a = Point3::new(0.0, 0.0, 0.0);
416 let b = Point3::new(1.0, 0.0, 0.0);
417 let c = Point3::new(0.0, 1.0, 0.0);
418 let d = Point3::new(0.0, 0.0, 1.0);
419 let exact = orient3d(a, b, c, d);
420 let sos = orient3d_sos(a, b, c, d, 0, 1, 2, 3);
421 assert_eq!(exact.signum(), sos.signum());
422 }
423}