1use std::f64::consts::TAU;
7
8use crate::vec::{Point2, Vec2};
9
10#[derive(Debug, Clone, Copy)]
12pub enum Boundary2 {
13 Segment(Point2, Point2),
15 Arc {
19 center: Point2,
21 u: Vec2,
23 v: Vec2,
25 a: f64,
27 b: f64,
29 t0: f64,
31 t1: f64,
33 },
34}
35
36const DIRECTIONS: [f64; 6] = [0.613, 1.931, 2.871, 4.127, 5.369, 0.229];
38
39#[must_use]
45pub fn point_in_region(pieces: &[Boundary2], p: Point2, tol: f64) -> Option<bool> {
46 'direction: for angle in DIRECTIONS {
47 let d = Vec2::new(angle.cos(), angle.sin());
48 let mut odd = false;
49 for piece in pieces {
50 match crossings(piece, p, d, tol) {
51 Crossing::Count(n) => odd ^= n % 2 == 1,
52 Crossing::Grazes => continue 'direction,
53 Crossing::OnBoundary => return None,
54 }
55 }
56 return Some(odd);
57 }
58 None
59}
60
61#[must_use]
63pub fn on_boundary(pieces: &[Boundary2], p: Point2, tol: f64) -> bool {
64 let along = Vec2::new(1.0, 0.0);
65 pieces
66 .iter()
67 .any(|piece| matches!(crossings(piece, p, along, tol), Crossing::OnBoundary))
68}
69
70enum Crossing {
72 OnBoundary,
74 Grazes,
77 Count(u32),
79}
80
81fn crossings(piece: &Boundary2, p: Point2, d: Vec2, tol: f64) -> Crossing {
83 let cross = |x: Vec2, y: Vec2| x.x().mul_add(y.y(), -(x.y() * y.x()));
84 match *piece {
85 Boundary2::Segment(a, b) => {
86 let (ab, ap) = (b - a, p - a);
87 let len = ab.length();
88 if len <= tol {
89 return Crossing::Count(0);
90 }
91 let along = (ap.dot(ab) / (len * len)).clamp(0.0, 1.0);
94 if (ap - ab * along).length() <= tol {
95 return Crossing::OnBoundary;
96 }
97 let det = cross(d, ab);
98 if det.abs() <= 1e-12 * len {
99 return if cross(d, ap).abs() <= tol && ap.dot(d) < 0.0 {
101 Crossing::Grazes
102 } else {
103 Crossing::Count(0)
104 };
105 }
106 let s = cross(ap * -1.0, ab) / det;
107 let w = cross(ap * -1.0, d) / det;
108 if s <= 0.0 || w < -tol / len || w > 1.0 + tol / len {
109 return Crossing::Count(0);
110 }
111 if w * len <= tol || (1.0 - w) * len <= tol {
112 return Crossing::Grazes;
113 }
114 Crossing::Count(1)
115 }
116 Boundary2::Arc {
117 center,
118 u,
119 v,
120 a,
121 b,
122 t0,
123 t1,
124 } => {
125 let q = p - center;
126 let (x0, y0) = (q.dot(u) / a, q.dot(v) / b);
127 let (dx, dy) = (d.dot(u) / a, d.dot(v) / b);
128 let qa = dx.mul_add(dx, dy * dy);
129 let qb = 2.0 * x0.mul_add(dx, y0 * dy);
130 let qc = x0.mul_add(x0, y0 * y0) - 1.0;
131 let size = a.max(b);
132 let on_arc = |t: f64| t0 + (t - t0).rem_euclid(TAU) <= t1;
133 let rho = x0.hypot(y0);
137 let radial = if rho > 0.0 {
138 q.length() * (rho - 1.0).abs() / rho
139 } else {
140 a.min(b)
141 };
142 let end = |t: f64| center + u * (a * t.cos()) + v * (b * t.sin());
143 if (radial <= tol && on_arc(y0.atan2(x0)))
144 || (p - end(t0)).length() <= tol
145 || (p - end(t1)).length() <= tol
146 {
147 return Crossing::OnBoundary;
148 }
149 let disc = qb.mul_add(qb, -4.0 * qa * qc);
150 if disc < 0.0 {
151 return Crossing::Count(0);
152 }
153 let half = disc.sqrt() / (2.0 * qa);
154 if half <= tol {
155 return Crossing::Grazes;
156 }
157 let mut n = 0;
158 for s in [-qb / (2.0 * qa) - half, -qb / (2.0 * qa) + half] {
159 if s <= 0.0 {
160 continue;
161 }
162 let t = (y0 + s * dy).atan2(x0 + s * dx);
163 let t = t0 + (t - t0).rem_euclid(TAU);
164 let (from_start, to_end) = (t - t0, t1 - t);
165 if (from_start * size <= tol || to_end.abs() * size <= tol)
166 || (t > t1 && (t - TAU - t0).abs() * size <= tol)
167 {
168 return Crossing::Grazes;
169 }
170 if t <= t1 {
171 n += 1;
172 }
173 }
174 Crossing::Count(n)
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 #![allow(clippy::unwrap_used)]
182 use std::f64::consts::PI;
183
184 use super::*;
185
186 fn circle(center: (f64, f64), r: f64, t0: f64, t1: f64) -> Boundary2 {
187 Boundary2::Arc {
188 center: Point2::new(center.0, center.1),
189 u: Vec2::new(1.0, 0.0),
190 v: Vec2::new(0.0, 1.0),
191 a: r,
192 b: r,
193 t0,
194 t1,
195 }
196 }
197
198 #[test]
201 fn a_disc_holds_points_up_to_its_rim() {
202 let disc = [circle((0.0, 0.0), 1.0, 0.0, TAU)];
203 for r in [0.0, 0.5, 0.999, 0.9999] {
204 for k in 0..12 {
205 let t = f64::from(k) * PI / 6.0 + 0.1;
206 let p = Point2::new(r * t.cos(), r * t.sin());
207 assert_eq!(point_in_region(&disc, p, 1e-9), Some(true), "r {r} t {t}");
208 }
209 }
210 assert_eq!(
211 point_in_region(&disc, Point2::new(1.001, 0.0), 1e-9),
212 Some(false)
213 );
214 assert_eq!(point_in_region(&disc, Point2::new(1.0, 0.0), 1e-9), None);
215 }
216
217 #[test]
220 fn points_at_corners_read_on_the_boundary() {
221 let (a, b, c) = (
222 Point2::new(-18.75, -16.75),
223 Point2::new(-20.673_141_121_612_92, -17.530_361_288_064_512),
224 Point2::new(-20.75, 4.0),
225 );
226 let triangle = [
227 Boundary2::Segment(a, b),
228 Boundary2::Segment(b, c),
229 Boundary2::Segment(c, a),
230 ];
231 for corner in [a, b, c] {
232 for (dx, dy) in [(0.0, 0.0), (3e-15, -2e-15), (-4e-15, 1e-15)] {
233 let p = Point2::new(corner.x() + dx, corner.y() + dy);
234 assert_eq!(point_in_region(&triangle, p, 1e-9), None, "{p:?}");
235 }
236 }
237 let half = [
238 circle((0.0, 0.0), 2.0, 0.3, 2.9),
239 Boundary2::Segment(
240 Point2::new(2.0 * 2.9_f64.cos(), 2.0 * 2.9_f64.sin()),
241 Point2::new(2.0 * 0.3_f64.cos(), 2.0 * 0.3_f64.sin()),
242 ),
243 ];
244 for t in [0.3_f64, 2.9] {
245 let p = Point2::new(2.0 * t.cos() + 1e-15, 2.0 * t.sin());
246 assert_eq!(point_in_region(&half, p, 1e-9), None, "t {t}");
247 }
248 }
249
250 #[test]
253 fn on_boundary_reads_distance_not_ray_parity() {
254 let half = [
255 circle((0.0, 0.0), 2.0, 0.0, PI),
256 Boundary2::Segment(Point2::new(-2.0, 0.0), Point2::new(2.0, 0.0)),
257 ];
258 assert!(on_boundary(&half, Point2::new(0.5, 0.0), 1e-9));
259 assert!(on_boundary(&half, Point2::new(0.0, 2.0), 1e-9));
260 assert!(on_boundary(&half, Point2::new(2.0, 1e-15), 1e-9));
261 assert!(!on_boundary(&half, Point2::new(0.5, 0.5), 1e-9));
262 assert!(!on_boundary(&half, Point2::new(0.0, 2.1), 1e-9));
263 }
264
265 #[test]
268 fn a_half_disc_of_a_segment_and_an_arc() {
269 let half = [
270 circle((0.0, 0.0), 2.0, 0.0, PI),
271 Boundary2::Segment(Point2::new(-2.0, 0.0), Point2::new(2.0, 0.0)),
272 ];
273 assert_eq!(
274 point_in_region(&half, Point2::new(0.0, 1.999), 1e-9),
275 Some(true)
276 );
277 assert_eq!(
278 point_in_region(&half, Point2::new(1.4, 1.4), 1e-9),
279 Some(true)
280 );
281 assert_eq!(
282 point_in_region(&half, Point2::new(0.0, -0.001), 1e-9),
283 Some(false)
284 );
285 assert_eq!(
286 point_in_region(&half, Point2::new(1.42, 1.42), 1e-9),
287 Some(false)
288 );
289 }
290
291 #[test]
294 fn a_square_with_a_round_hole() {
295 let (lo, hi) = (Point2::new(-3.0, -3.0), Point2::new(3.0, 3.0));
296 let region = [
297 Boundary2::Segment(lo, Point2::new(hi.x(), lo.y())),
298 Boundary2::Segment(Point2::new(hi.x(), lo.y()), hi),
299 Boundary2::Segment(hi, Point2::new(lo.x(), hi.y())),
300 Boundary2::Segment(Point2::new(lo.x(), hi.y()), lo),
301 circle((0.0, 0.0), 1.0, 0.0, TAU),
302 ];
303 assert_eq!(
304 point_in_region(®ion, Point2::new(0.0, 0.0), 1e-9),
305 Some(false)
306 );
307 assert_eq!(
308 point_in_region(®ion, Point2::new(0.0, 1.0005), 1e-9),
309 Some(true)
310 );
311 assert_eq!(
312 point_in_region(®ion, Point2::new(0.0, 0.9995), 1e-9),
313 Some(false)
314 );
315 assert_eq!(
316 point_in_region(®ion, Point2::new(2.9, -2.9), 1e-9),
317 Some(true)
318 );
319 assert_eq!(
320 point_in_region(®ion, Point2::new(3.1, 0.0), 1e-9),
321 Some(false)
322 );
323 }
324
325 #[test]
327 fn a_turned_ellipse() {
328 let (c, s) = (0.5_f64.cos(), 0.5_f64.sin());
329 let ellipse = [Boundary2::Arc {
330 center: Point2::new(1.0, 2.0),
331 u: Vec2::new(c, s),
332 v: Vec2::new(-s, c),
333 a: 3.0,
334 b: 1.0,
335 t0: 0.3,
336 t1: 0.3 + TAU,
337 }];
338 let at = |x: f64, y: f64| Point2::new(1.0 + x * c - y * s, 2.0 + x * s + y * c);
339 assert_eq!(point_in_region(&ellipse, at(2.999, 0.0), 1e-9), Some(true));
340 assert_eq!(point_in_region(&ellipse, at(3.001, 0.0), 1e-9), Some(false));
341 assert_eq!(point_in_region(&ellipse, at(0.0, 0.999), 1e-9), Some(true));
342 }
343
344 #[test]
347 fn a_ray_through_a_vertex_tries_another() {
348 let d = Vec2::new(DIRECTIONS[0].cos(), DIRECTIONS[0].sin());
349 let apex = Point2::new(0.0, 0.0) + d * 2.0;
350 let (b, c) = (Point2::new(-2.0, 0.5), Point2::new(0.5, -2.0));
351 let triangle = [
352 Boundary2::Segment(apex, b),
353 Boundary2::Segment(b, c),
354 Boundary2::Segment(c, apex),
355 ];
356 assert_eq!(
357 point_in_region(&triangle, Point2::new(0.0, 0.0), 1e-9),
358 Some(true)
359 );
360 let behind = Point2::new(0.0, 0.0) + d * -3.0;
361 assert_eq!(point_in_region(&triangle, behind, 1e-9), Some(false));
362 }
363
364 #[test]
367 fn a_side_along_the_ray_tries_another() {
368 let d = Vec2::new(DIRECTIONS[0].cos(), DIRECTIONS[0].sin());
369 let n = Vec2::new(-d.y(), d.x());
370 let p = Point2::new(0.0, 0.0);
371 let (q1, q2) = (p + d, p + d * 2.0);
372 let q3 = p + d * 1.5 + n;
373 let triangle = [
374 Boundary2::Segment(q1, q2),
375 Boundary2::Segment(q2, q3),
376 Boundary2::Segment(q3, q1),
377 ];
378 assert_eq!(point_in_region(&triangle, p, 1e-9), Some(false));
379 }
380
381 #[test]
384 fn an_arc_across_the_wrap() {
385 let (t0, t1) = (2.5_f64, 4.0_f64);
386 let segment = [
387 circle((0.0, 0.0), 1.0, t0, t1),
388 Boundary2::Segment(
389 Point2::new(t1.cos(), t1.sin()),
390 Point2::new(t0.cos(), t0.sin()),
391 ),
392 ];
393 assert_eq!(
394 point_in_region(&segment, Point2::new(-0.9, 0.0), 1e-9),
395 Some(true)
396 );
397 assert_eq!(
398 point_in_region(&segment, Point2::new(-0.7, 0.0), 1e-9),
399 Some(false)
400 );
401 assert_eq!(
402 point_in_region(&segment, Point2::new(-1.01, 0.0), 1e-9),
403 Some(false)
404 );
405 }
406
407 #[test]
409 fn half_an_ellipse() {
410 let half = [
411 Boundary2::Arc {
412 center: Point2::new(0.0, 0.0),
413 u: Vec2::new(1.0, 0.0),
414 v: Vec2::new(0.0, 1.0),
415 a: 3.0,
416 b: 1.0,
417 t0: 0.0,
418 t1: PI,
419 },
420 Boundary2::Segment(Point2::new(-3.0, 0.0), Point2::new(3.0, 0.0)),
421 ];
422 assert_eq!(
423 point_in_region(&half, Point2::new(0.0, 0.99), 1e-9),
424 Some(true)
425 );
426 assert_eq!(
427 point_in_region(&half, Point2::new(0.0, 1.01), 1e-9),
428 Some(false)
429 );
430 assert_eq!(
431 point_in_region(&half, Point2::new(2.9, 0.1), 1e-9),
432 Some(true)
433 );
434 assert_eq!(
435 point_in_region(&half, Point2::new(2.9, 0.3), 1e-9),
436 Some(false)
437 );
438 assert_eq!(
439 point_in_region(&half, Point2::new(0.0, -0.1), 1e-9),
440 Some(false)
441 );
442 }
443
444 #[test]
446 fn a_long_ellipse_reads_its_end_by_distance() {
447 let ellipse = [Boundary2::Arc {
448 center: Point2::new(0.0, 0.0),
449 u: Vec2::new(1.0, 0.0),
450 v: Vec2::new(0.0, 1.0),
451 a: 100.0,
452 b: 1.0,
453 t0: 0.0,
454 t1: TAU,
455 }];
456 let past = Point2::new(100.000_000_5, 0.0);
457 assert_eq!(point_in_region(&ellipse, past, 1e-7), Some(false));
458 }
459}