Skip to main content

geometry_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod next_after;
4
5use crate::next_after::NextAfter;
6use rtree_rs::{RTree, Rect as RTreeRect};
7
8#[derive(Copy, Clone, Debug)]
9pub struct Point {
10    pub x: f64,
11    pub y: f64,
12}
13
14#[derive(Copy, Clone, Debug)]
15pub struct Rect {
16    pub min: Point,
17    pub max: Point,
18}
19
20impl Rect {
21    pub fn contains_point(&self, p: Point) -> bool {
22        return p.x >= self.min.x && p.x <= self.max.x && p.y >= self.min.y && p.y <= self.max.y;
23    }
24
25    pub fn intersects_rect(&self, other: Rect) -> bool {
26        if self.min.y > other.max.y || self.max.y < other.min.y {
27            return false;
28        }
29        if self.min.x > other.max.x || self.max.x < other.min.x {
30            return false;
31        }
32        return true;
33    }
34
35    pub fn nw(&self) -> Point {
36        Point {
37            x: self.min.x,
38            y: self.max.y,
39        }
40    }
41
42    pub fn sw(&self) -> Point {
43        Point {
44            x: self.min.x,
45            y: self.min.y,
46        }
47    }
48
49    pub fn se(&self) -> Point {
50        Point {
51            x: self.max.x,
52            y: self.min.y,
53        }
54    }
55
56    pub fn ne(&self) -> Point {
57        Point {
58            x: self.max.x,
59            y: self.max.y,
60        }
61    }
62
63    pub fn south(&self) -> Segment {
64        Segment {
65            a: self.sw(),
66            b: self.se(),
67        }
68    }
69
70    pub fn east(&self) -> Segment {
71        Segment {
72            a: self.se(),
73            b: self.ne(),
74        }
75    }
76
77    pub fn north(&self) -> Segment {
78        Segment {
79            a: self.ne(),
80            b: self.nw(),
81        }
82    }
83
84    pub fn west(&self) -> Segment {
85        Segment {
86            a: self.nw(),
87            b: self.sw(),
88        }
89    }
90
91    pub fn segment_at(&self, index: i64) -> Segment {
92        match index {
93            0 => self.south(),
94            1 => self.east(),
95            2 => self.north(),
96            3 => self.west(),
97            _ => self.south(), // TODO(ringsaturn): raise err
98        }
99    }
100}
101
102#[derive(Copy, Clone, Debug)]
103pub struct PolygonBuildOptions {
104    pub enable_rtree: bool,
105    pub enable_compressed_quad: bool,
106    pub rtree_min_segments: usize,
107}
108
109impl Default for PolygonBuildOptions {
110    fn default() -> Self {
111        Self {
112            enable_rtree: true,
113            enable_compressed_quad: true,
114            rtree_min_segments: 64,
115        }
116    }
117}
118
119struct RingIndex {
120    x_min: f64,
121    x_max: f64,
122    seg_count: usize,
123    rtree: Option<RTree<2, f64, usize>>,
124    compressed_quad: Option<CompressedQuadIndex>,
125}
126
127impl RingIndex {
128    fn search_candidates(&self, point_y: f64, out: &mut Vec<usize>) {
129        let query_rect = Rect {
130            min: Point {
131                x: self.x_min,
132                y: point_y,
133            },
134            max: Point {
135                x: self.x_max,
136                y: point_y,
137            },
138        };
139
140        let mut seen = vec![false; self.seg_count];
141
142        if let Some(tree) = self.rtree.as_ref() {
143            let query = RTreeRect::new([self.x_min, point_y], [self.x_max, point_y]);
144            for item in tree.search(query) {
145                let idx = *item.data;
146                if idx < self.seg_count && !seen[idx] {
147                    seen[idx] = true;
148                    out.push(idx);
149                }
150            }
151        }
152
153        if let Some(index) = self.compressed_quad.as_ref() {
154            let mut tmp = Vec::new();
155            index.search_intersects(query_rect, &mut tmp);
156            for idx in tmp {
157                if idx < self.seg_count && !seen[idx] {
158                    seen[idx] = true;
159                    out.push(idx);
160                }
161            }
162        }
163    }
164}
165
166const Q_MAX_ITEMS: usize = 12;
167const Q_MAX_DEPTH: usize = 64;
168
169#[derive(Default)]
170struct QuadNode {
171    split: bool,
172    items: Vec<usize>,
173    quads: [Option<Box<QuadNode>>; 4],
174}
175
176impl QuadNode {
177    fn new() -> Self {
178        Self {
179            split: false,
180            items: Vec::new(),
181            quads: [None, None, None, None],
182        }
183    }
184}
185
186struct CompressedQuadIndex {
187    bounds: Rect,
188    seg_rects: Vec<Rect>,
189    data: Vec<u8>,
190}
191
192impl CompressedQuadIndex {
193    fn build(ring: &[Point]) -> Option<Self> {
194        let seg_count = ring_segment_count(ring);
195        if seg_count == 0 {
196            return None;
197        }
198
199        let mut min_x = ring[0].x;
200        let mut min_y = ring[0].y;
201        let mut max_x = ring[0].x;
202        let mut max_y = ring[0].y;
203
204        for p in ring.iter().take(seg_count) {
205            if p.x < min_x {
206                min_x = p.x;
207            }
208            if p.y < min_y {
209                min_y = p.y;
210            }
211            if p.x > max_x {
212                max_x = p.x;
213            }
214            if p.y > max_y {
215                max_y = p.y;
216            }
217        }
218
219        let bounds = Rect {
220            min: Point { x: min_x, y: min_y },
221            max: Point { x: max_x, y: max_y },
222        };
223
224        let mut seg_rects = Vec::with_capacity(seg_count);
225        for i in 0..seg_count {
226            seg_rects.push(segment_at_for_slice(ring, i).rect());
227        }
228
229        let mut root = QuadNode::new();
230        for i in 0..seg_rects.len() {
231            insert_quad_node(&mut root, bounds, &seg_rects, i, 0);
232        }
233
234        let mut data = Vec::with_capacity(seg_rects.len() * 2);
235        compress_quad_node(&root, &mut data);
236
237        Some(Self {
238            bounds,
239            seg_rects,
240            data,
241        })
242    }
243
244    fn search_intersects(&self, query: Rect, out: &mut Vec<usize>) {
245        if !self.bounds.intersects_rect(query) {
246            return;
247        }
248        let _ = self.search_intersects_from(0, self.bounds, query, out);
249    }
250
251    fn search_intersects_from(
252        &self,
253        mut addr: usize,
254        bounds: Rect,
255        query: Rect,
256        out: &mut Vec<usize>,
257    ) -> Option<usize> {
258        let (nitems, next_addr) = read_uvarint(&self.data, addr)?;
259        addr = next_addr;
260
261        let mut last: usize = 0;
262        for _ in 0..nitems {
263            let (delta, next_addr) = read_uvarint(&self.data, addr)?;
264            addr = next_addr;
265            last = last.checked_add(delta as usize)?;
266            let seg_rect = self.seg_rects.get(last)?;
267            if seg_rect.intersects_rect(query) {
268                out.push(last);
269            }
270        }
271
272        let split = *self.data.get(addr)?;
273        addr += 1;
274        if split == 0 {
275            return Some(addr);
276        }
277        if split != 1 {
278            return None;
279        }
280
281        for q in 0..4 {
282            let (qsize, next_addr) = read_uvarint(&self.data, addr)?;
283            addr = next_addr;
284            if qsize == 0 {
285                continue;
286            }
287
288            let qsize = usize::try_from(qsize).ok()?;
289            let qbounds = quad_bounds(bounds, q);
290            let child_start = addr;
291            let child_end = child_start.checked_add(qsize)?;
292            if child_end > self.data.len() {
293                return None;
294            }
295
296            if qbounds.intersects_rect(query) {
297                let _ = self.search_intersects_from(child_start, qbounds, query, out)?;
298            }
299            addr = child_end;
300        }
301
302        Some(addr)
303    }
304}
305
306fn ring_segment_count(ring: &[Point]) -> usize {
307    ring.len().saturating_sub(1)
308}
309
310fn segment_at_for_slice(ring: &[Point], index: usize) -> Segment {
311    Segment {
312        a: ring[index],
313        b: ring[index + 1],
314    }
315}
316
317fn build_ring_index(ring: &[Point], options: &PolygonBuildOptions) -> Option<RingIndex> {
318    if !options.enable_rtree && !options.enable_compressed_quad {
319        return None;
320    }
321    if ring.is_empty() {
322        return None;
323    }
324
325    let seg_count = ring_segment_count(ring);
326    if seg_count < options.rtree_min_segments {
327        return None;
328    }
329
330    let mut x_min = ring[0].x;
331    let mut x_max = ring[0].x;
332    for p in ring.iter().take(seg_count) {
333        if p.x < x_min {
334            x_min = p.x;
335        }
336        if p.x > x_max {
337            x_max = p.x;
338        }
339    }
340
341    let rtree = if options.enable_rtree {
342        let mut tree: RTree<2, f64, usize> = RTree::new();
343        for i in 0..seg_count {
344            let seg_rect = segment_at_for_slice(ring, i).rect();
345            tree.insert(
346                RTreeRect::new(
347                    [seg_rect.min.x, seg_rect.min.y],
348                    [seg_rect.max.x, seg_rect.max.y],
349                ),
350                i,
351            );
352        }
353        Some(tree)
354    } else {
355        None
356    };
357
358    let compressed_quad = if options.enable_compressed_quad {
359        CompressedQuadIndex::build(ring)
360    } else {
361        None
362    };
363
364    if rtree.is_none() && compressed_quad.is_none() {
365        return None;
366    }
367
368    Some(RingIndex {
369        x_min,
370        x_max,
371        seg_count,
372        rtree,
373        compressed_quad,
374    })
375}
376
377fn rings_contains_point(
378    ring: &[Point],
379    ring_index: Option<&RingIndex>,
380    point: Point,
381    allow_on_edge: bool,
382) -> bool {
383    let mut inside: bool = false;
384
385    if let Some(index) = ring_index {
386        let mut candidates = Vec::new();
387        index.search_candidates(point.y, &mut candidates);
388        for i in candidates {
389            let seg = segment_at_for_slice(ring, i);
390            let res: RaycastResult = raycast(&seg, point);
391            if res.on {
392                inside = allow_on_edge;
393                break;
394            }
395            if res.inside {
396                inside = !inside;
397            }
398        }
399        return inside;
400    }
401
402    let ray_rect = Rect {
403        min: Point {
404            x: std::f64::NEG_INFINITY,
405            y: point.y,
406        },
407        max: Point {
408            x: std::f64::INFINITY,
409            y: point.y,
410        },
411    };
412
413    for pair in ring.windows(2) {
414        let seg = Segment {
415            a: pair[0],
416            b: pair[1],
417        };
418
419        if seg.rect().intersects_rect(ray_rect) {
420            let res: RaycastResult = raycast(&seg, point);
421            if res.on {
422                inside = allow_on_edge;
423                break;
424            }
425            if res.inside {
426                inside = !inside;
427            }
428        }
429    }
430
431    return inside;
432}
433
434fn choose_quad(bounds: Rect, rect: Rect) -> Option<usize> {
435    let mid_x = (bounds.min.x + bounds.max.x) / 2.0;
436    let mid_y = (bounds.min.y + bounds.max.y) / 2.0;
437
438    if rect.max.x < mid_x {
439        if rect.max.y < mid_y {
440            return Some(2);
441        }
442        if rect.min.y < mid_y {
443            return None;
444        }
445        return Some(0);
446    }
447
448    if rect.min.x < mid_x {
449        return None;
450    }
451
452    if rect.max.y < mid_y {
453        return Some(3);
454    }
455    if rect.min.y < mid_y {
456        return None;
457    }
458    Some(1)
459}
460
461fn quad_bounds(mut bounds: Rect, q: usize) -> Rect {
462    let center_x = (bounds.min.x + bounds.max.x) / 2.0;
463    let center_y = (bounds.min.y + bounds.max.y) / 2.0;
464
465    match q {
466        0 => {
467            bounds.min.y = center_y;
468            bounds.max.x = center_x;
469        }
470        1 => {
471            bounds.min.x = center_x;
472            bounds.min.y = center_y;
473        }
474        2 => {
475            bounds.max.x = center_x;
476            bounds.max.y = center_y;
477        }
478        3 => {
479            bounds.min.x = center_x;
480            bounds.max.y = center_y;
481        }
482        _ => {}
483    }
484    bounds
485}
486
487fn insert_quad_node(
488    node: &mut QuadNode,
489    bounds: Rect,
490    seg_rects: &[Rect],
491    item: usize,
492    depth: usize,
493) {
494    if depth == Q_MAX_DEPTH {
495        node.items.push(item);
496        return;
497    }
498
499    let item_rect = seg_rects[item];
500    if node.split {
501        if let Some(q) = choose_quad(bounds, item_rect) {
502            let qbounds = quad_bounds(bounds, q);
503            if node.quads[q].is_none() {
504                node.quads[q] = Some(Box::new(QuadNode::new()));
505            }
506            if let Some(quad) = node.quads[q].as_deref_mut() {
507                insert_quad_node(quad, qbounds, seg_rects, item, depth + 1);
508            }
509        } else {
510            node.items.push(item);
511        }
512        return;
513    }
514
515    if node.items.len() == Q_MAX_ITEMS {
516        let existing = std::mem::take(&mut node.items);
517        node.split = true;
518        for i in existing {
519            let rect = seg_rects[i];
520            if let Some(q) = choose_quad(bounds, rect) {
521                let qbounds = quad_bounds(bounds, q);
522                if node.quads[q].is_none() {
523                    node.quads[q] = Some(Box::new(QuadNode::new()));
524                }
525                if let Some(quad) = node.quads[q].as_deref_mut() {
526                    insert_quad_node(quad, qbounds, seg_rects, i, depth + 1);
527                }
528            } else {
529                node.items.push(i);
530            }
531        }
532        insert_quad_node(node, bounds, seg_rects, item, depth);
533        return;
534    }
535
536    node.items.push(item);
537}
538
539fn append_uvarint(dst: &mut Vec<u8>, mut x: u64) {
540    while x >= 0x80 {
541        dst.push((x as u8 & 0x7f) | 0x80);
542        x >>= 7;
543    }
544    dst.push(x as u8);
545}
546
547fn read_uvarint(data: &[u8], mut addr: usize) -> Option<(u64, usize)> {
548    let mut x: u64 = 0;
549    let mut shift = 0;
550
551    loop {
552        let b = *data.get(addr)?;
553        addr += 1;
554
555        if shift == 70 {
556            return None;
557        }
558
559        x |= ((b & 0x7f) as u64) << shift;
560        if b < 0x80 {
561            return Some((x, addr));
562        }
563        shift += 7;
564    }
565}
566
567fn compress_quad_node(node: &QuadNode, dst: &mut Vec<u8>) {
568    let mut items = node.items.clone();
569    items.sort_unstable();
570
571    append_uvarint(dst, items.len() as u64);
572    let mut last = 0usize;
573    for item in items {
574        append_uvarint(dst, (item - last) as u64);
575        last = item;
576    }
577
578    if !node.split {
579        dst.push(0);
580        return;
581    }
582
583    dst.push(1);
584    for q in 0..4 {
585        if let Some(child) = node.quads[q].as_deref() {
586            let mut child_bytes = Vec::new();
587            compress_quad_node(child, &mut child_bytes);
588            append_uvarint(dst, child_bytes.len() as u64);
589            dst.extend_from_slice(&child_bytes);
590        } else {
591            append_uvarint(dst, 0);
592        }
593    }
594}
595
596pub struct Polygon {
597    exterior: Vec<Point>,
598    holes: Vec<Vec<Point>>,
599    rect: Rect,
600    options: PolygonBuildOptions,
601    exterior_index: Option<RingIndex>,
602    hole_indexes: Vec<Option<RingIndex>>,
603}
604
605impl Polygon {
606    fn compute_rect(exterior: &[Point]) -> Rect {
607        let mut minx: f64 = exterior[0].x;
608        let mut miny: f64 = exterior[0].y;
609        let mut maxx: f64 = exterior[0].x;
610        let mut maxy: f64 = exterior[0].y;
611
612        for p in exterior.iter() {
613            if p.x < minx {
614                minx = p.x;
615            }
616            if p.y < miny {
617                miny = p.y;
618            }
619            if p.x > maxx {
620                maxx = p.x;
621            }
622            if p.y > maxy {
623                maxy = p.y;
624            }
625        }
626
627        Rect {
628            min: Point { x: minx, y: miny },
629            max: Point { x: maxx, y: maxy },
630        }
631    }
632
633    fn rebuild_cache(&mut self) {
634        self.rect = Self::compute_rect(&self.exterior);
635        self.exterior_index = build_ring_index(&self.exterior, &self.options);
636
637        self.hole_indexes = self
638            .holes
639            .iter()
640            .map(|hole| build_ring_index(hole, &self.options))
641            .collect();
642    }
643
644    /// Point-In-Polygon check, the normal way.
645    /// It's most used algorithm implementation, port from Go's [geojson]
646    ///
647    /// [geojson]: https://github.com/tidwall/geojson
648    fn contains_point_normal(&self, p: Point) -> bool {
649        if !rings_contains_point(&self.exterior, self.exterior_index.as_ref(), p, false) {
650            return false;
651        }
652
653        for (hole, hole_index) in self.holes.iter().zip(self.hole_indexes.iter()) {
654            if rings_contains_point(hole, hole_index.as_ref(), p, false) {
655                return false;
656            }
657        }
658
659        return true;
660    }
661
662    /// Do point-in-polygon search.
663    pub fn contains_point(&self, p: Point) -> bool {
664        if !self.rect.contains_point(p) {
665            return false;
666        }
667
668        return self.contains_point_normal(p);
669    }
670
671    /// Create a new Polygon instance from exterior and holes.
672    ///
673    /// Example:
674    ///
675    /// ```rust
676    /// use std::vec;
677    /// use geometry_rs;
678    /// let poly = geometry_rs::Polygon::new(
679    ///     vec![
680    ///         geometry_rs::Point {
681    ///             x: 90.48826291293898,
682    ///             y: 45.951129815858565,
683    ///         },
684    ///         geometry_rs::Point {
685    ///             x: 90.48826291293898,
686    ///             y: 27.99437617512571,
687    ///         },
688    ///         geometry_rs::Point {
689    ///             x: 122.83201291294,
690    ///             y: 27.99437617512571,
691    ///         },
692    ///         geometry_rs::Point {
693    ///             x: 122.83201291294,
694    ///             y: 45.951129815858565,
695    ///         },
696    ///         geometry_rs::Point {
697    ///             x: 90.48826291293898,
698    ///             y: 45.951129815858565,
699    ///         },
700    ///     ],
701    ///     vec![],
702    ///     None,
703    /// );
704    ///
705    /// let p_out = geometry_rs::Point {
706    ///     x: 130.74216916294148,
707    ///     y: 37.649011392900306,
708    /// };
709    ///
710    /// print!("{:?}\n", poly.contains_point(p_out));
711    ///
712    /// let p_in = geometry_rs::Point {
713    ///     x: 99.9804504129416,
714    ///     y: 39.70716466970461,
715    /// };
716    /// print!("{:?}\n", poly.contains_point(p_in));
717    /// ```
718    pub fn new(
719        exterior: Vec<Point>,
720        holes: Vec<Vec<Point>>,
721        options: Option<PolygonBuildOptions>,
722    ) -> Polygon {
723        let mut poly = Polygon {
724            exterior,
725            holes,
726            rect: Rect {
727                min: Point { x: 0.0, y: 0.0 },
728                max: Point { x: 0.0, y: 0.0 },
729            },
730            options: options.unwrap_or_default(),
731            exterior_index: None,
732            hole_indexes: Vec::new(),
733        };
734        poly.rebuild_cache();
735        poly
736    }
737
738    pub fn exterior(&self) -> &[Point] {
739        &self.exterior
740    }
741
742    pub fn holes(&self) -> &[Vec<Point>] {
743        &self.holes
744    }
745
746    pub fn rect(&self) -> Rect {
747        self.rect
748    }
749
750    pub fn options(&self) -> PolygonBuildOptions {
751        self.options
752    }
753
754    pub fn set_exterior(&mut self, exterior: Vec<Point>) {
755        self.exterior = exterior;
756        self.rebuild_cache();
757    }
758
759    pub fn set_holes(&mut self, holes: Vec<Vec<Point>>) {
760        self.holes = holes;
761        self.rebuild_cache();
762    }
763
764    pub fn set_options(&mut self, options: PolygonBuildOptions) {
765        self.options = options;
766        self.rebuild_cache();
767    }
768}
769
770#[derive(Copy, Clone, Debug)]
771pub struct Segment {
772    pub a: Point,
773    pub b: Point,
774}
775
776impl Segment {
777    pub fn rect(&self) -> Rect {
778        let mut min_x: f64 = self.a.x;
779        let mut min_y: f64 = self.a.y;
780        let mut max_x: f64 = self.b.x;
781        let mut max_y: f64 = self.b.y;
782
783        if min_x > max_x {
784            let actual_min_x = max_x;
785            let actual_max_x = min_x;
786            min_x = actual_min_x;
787            max_x = actual_max_x;
788        }
789
790        if min_y > max_y {
791            let actual_min_y = max_y;
792            let actual_max_y = min_y;
793            min_y = actual_min_y;
794            max_y = actual_max_y;
795        }
796
797        return Rect {
798            min: Point { x: min_x, y: min_y },
799            max: Point { x: max_x, y: max_y },
800        };
801    }
802}
803
804pub struct RaycastResult {
805    inside: bool, // point on the left
806    on: bool,     // point is directly on top of
807}
808
809pub fn raycast(seg: &Segment, point: Point) -> RaycastResult {
810    let mut p = point;
811    let a = seg.a;
812    let b = seg.b;
813
814    // make sure that the point is inside the segment bounds
815    if a.y < b.y && (p.y < a.y || p.y > b.y) {
816        return RaycastResult {
817            inside: false,
818            on: false,
819        };
820    } else if a.y > b.y && (p.y < b.y || p.y > a.y) {
821        return RaycastResult {
822            inside: false,
823            on: false,
824        };
825    }
826
827    // test if point is in on the segment
828    if a.y == b.y {
829        if a.x == b.x {
830            if p.x == a.x && p.y == a.y {
831                return RaycastResult {
832                    inside: false,
833                    on: true,
834                };
835            }
836            return RaycastResult {
837                inside: false,
838                on: false,
839            };
840        }
841        if p.y == b.y {
842            // horizontal segment
843            // check if the point in on the line
844            if a.x < b.x {
845                if p.x >= a.x && p.x <= b.x {
846                    return RaycastResult {
847                        inside: false,
848                        on: true,
849                    };
850                }
851            } else if p.x >= b.x && p.x <= a.x {
852                return RaycastResult {
853                    inside: false,
854                    on: true,
855                };
856            }
857        }
858    }
859    if a.x == b.x && p.x == b.x {
860        // vertical segment
861        // check if the point in on the line
862        if a.y < b.y {
863            if p.y >= a.y && p.y <= b.y {
864                return RaycastResult {
865                    inside: false,
866                    on: true,
867                };
868            }
869        } else if p.y >= b.y && p.y <= a.y {
870            return RaycastResult {
871                inside: false,
872                on: true,
873            };
874        }
875    }
876    if (p.x - a.x) / (b.x - a.x) == (p.y - a.y) / (b.y - a.y) {
877        return RaycastResult {
878            inside: false,
879            on: true,
880        };
881    }
882
883    // do the actual raycast here.
884    while p.y == a.y || p.y == b.y {
885        p.y = p.y.next_after(std::f64::INFINITY);
886    }
887
888    if a.y < b.y {
889        if p.y < a.y || p.y > b.y {
890            return RaycastResult {
891                inside: false,
892                on: false,
893            };
894        }
895    } else if p.y < b.y || p.y > a.y {
896        return RaycastResult {
897            inside: false,
898            on: false,
899        };
900    }
901    if a.x > b.x {
902        if p.x >= a.x {
903            return RaycastResult {
904                inside: false,
905                on: false,
906            };
907        }
908        if p.x <= b.x {
909            return RaycastResult {
910                inside: true,
911                on: false,
912            };
913        }
914    } else {
915        if p.x >= b.x {
916            return RaycastResult {
917                inside: false,
918                on: false,
919            };
920        }
921        if p.x <= a.x {
922            return RaycastResult {
923                inside: true,
924                on: false,
925            };
926        }
927    }
928    if a.y < b.y {
929        if (p.y - a.y) / (p.x - a.x) >= (b.y - a.y) / (b.x - a.x) {
930            return RaycastResult {
931                inside: true,
932                on: false,
933            };
934        }
935    } else if (p.y - b.y) / (p.x - b.x) >= (a.y - b.y) / (a.x - b.x) {
936        return RaycastResult {
937            inside: true,
938            on: false,
939        };
940    }
941    return RaycastResult {
942        inside: false,
943        on: false,
944    };
945}
946
947#[cfg(test)]
948mod tests {
949    use super::*;
950
951    fn square(min: f64, max: f64) -> Vec<Point> {
952        vec![
953            Point { x: min, y: min },
954            Point { x: min, y: max },
955            Point { x: max, y: max },
956            Point { x: max, y: min },
957            Point { x: min, y: min },
958        ]
959    }
960
961    fn polygon_with_segments(segments: usize) -> Vec<Point> {
962        let mut ring = Vec::with_capacity(segments + 1);
963        for i in 0..segments {
964            let theta = (i as f64) / (segments as f64) * std::f64::consts::TAU;
965            ring.push(Point {
966                x: theta.cos(),
967                y: theta.sin(),
968            });
969        }
970        ring.push(ring[0]);
971        ring
972    }
973
974    fn scan_candidates(ring: &[Point], y: f64, x_min: f64, x_max: f64) -> Vec<usize> {
975        let mut out = Vec::new();
976        let query = Rect {
977            min: Point { x: x_min, y },
978            max: Point { x: x_max, y },
979        };
980        for (i, pair) in ring.windows(2).enumerate() {
981            let seg = Segment {
982                a: pair[0],
983                b: pair[1],
984            };
985            if seg.rect().intersects_rect(query) {
986                out.push(i);
987            }
988        }
989        out
990    }
991
992    #[test]
993    fn rings_contains_point_allow_on_edge() {
994        let ring = square(0.0, 10.0);
995        let on_edge = Point { x: 0.0, y: 5.0 };
996        assert!(rings_contains_point(&ring, None, on_edge, true));
997        assert!(!rings_contains_point(&ring, None, on_edge, false));
998    }
999
1000    #[test]
1001    fn polygon_contains_basic_in_and_out() {
1002        let poly = Polygon::new(square(0.0, 10.0), vec![], None);
1003        assert!(poly.contains_point(Point { x: 5.0, y: 5.0 }));
1004        assert!(!poly.contains_point(Point { x: 20.0, y: 5.0 }));
1005    }
1006
1007    #[test]
1008    fn polygon_contains_with_hole() {
1009        let poly = Polygon::new(square(0.0, 10.0), vec![square(3.0, 7.0)], None);
1010        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1011        assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1012    }
1013
1014    #[test]
1015    fn indexed_and_non_indexed_results_match() {
1016        let ring = polygon_with_segments(128);
1017        let p_in = Point { x: 0.2, y: 0.1 };
1018        let p_out = Point { x: 2.0, y: 0.0 };
1019
1020        let p1 = Polygon::new(
1021            ring.clone(),
1022            vec![],
1023            Some(PolygonBuildOptions {
1024                enable_rtree: false,
1025                enable_compressed_quad: false,
1026                rtree_min_segments: 64,
1027            }),
1028        );
1029
1030        let p2 = Polygon::new(
1031            ring,
1032            vec![],
1033            Some(PolygonBuildOptions {
1034                enable_rtree: true,
1035                enable_compressed_quad: true,
1036                rtree_min_segments: 64,
1037            }),
1038        );
1039
1040        assert_eq!(p1.contains_point(p_in), p2.contains_point(p_in));
1041        assert_eq!(p1.contains_point(p_out), p2.contains_point(p_out));
1042    }
1043
1044    #[test]
1045    fn rtree_and_compressed_quad_and_both_match_baseline() {
1046        let ring = polygon_with_segments(160);
1047        let points = [
1048            Point { x: 0.2, y: 0.1 },
1049            Point { x: -0.4, y: -0.3 },
1050            Point { x: 1.2, y: 0.0 },
1051        ];
1052
1053        let base = Polygon::new(
1054            ring.clone(),
1055            vec![],
1056            Some(PolygonBuildOptions {
1057                enable_rtree: false,
1058                enable_compressed_quad: false,
1059                rtree_min_segments: 64,
1060            }),
1061        );
1062        let only_rtree = Polygon::new(
1063            ring.clone(),
1064            vec![],
1065            Some(PolygonBuildOptions {
1066                enable_rtree: true,
1067                enable_compressed_quad: false,
1068                rtree_min_segments: 64,
1069            }),
1070        );
1071        let only_compressed = Polygon::new(
1072            ring.clone(),
1073            vec![],
1074            Some(PolygonBuildOptions {
1075                enable_rtree: false,
1076                enable_compressed_quad: true,
1077                rtree_min_segments: 64,
1078            }),
1079        );
1080        let both = Polygon::new(
1081            ring,
1082            vec![],
1083            Some(PolygonBuildOptions {
1084                enable_rtree: true,
1085                enable_compressed_quad: true,
1086                rtree_min_segments: 64,
1087            }),
1088        );
1089
1090        for p in points {
1091            let expected = base.contains_point(p);
1092            assert_eq!(only_rtree.contains_point(p), expected);
1093            assert_eq!(only_compressed.contains_point(p), expected);
1094            assert_eq!(both.contains_point(p), expected);
1095        }
1096    }
1097
1098    #[test]
1099    fn threshold_boundaries_63_64_65_are_consistent() {
1100        let ring = polygon_with_segments(65);
1101        let p_in = Point { x: 0.2, y: 0.0 };
1102        let p_out = Point { x: 1.5, y: 0.0 };
1103
1104        let p63 = Polygon::new(
1105            ring.clone(),
1106            vec![],
1107            Some(PolygonBuildOptions {
1108                enable_rtree: true,
1109                enable_compressed_quad: true,
1110                rtree_min_segments: 63,
1111            }),
1112        );
1113        let p64 = Polygon::new(
1114            ring.clone(),
1115            vec![],
1116            Some(PolygonBuildOptions {
1117                enable_rtree: true,
1118                enable_compressed_quad: true,
1119                rtree_min_segments: 64,
1120            }),
1121        );
1122        let p65 = Polygon::new(
1123            ring,
1124            vec![],
1125            Some(PolygonBuildOptions {
1126                enable_rtree: true,
1127                enable_compressed_quad: true,
1128                rtree_min_segments: 65,
1129            }),
1130        );
1131
1132        assert_eq!(p63.contains_point(p_in), p64.contains_point(p_in));
1133        assert_eq!(p64.contains_point(p_in), p65.contains_point(p_in));
1134
1135        assert_eq!(p63.contains_point(p_out), p64.contains_point(p_out));
1136        assert_eq!(p64.contains_point(p_out), p65.contains_point(p_out));
1137    }
1138
1139    #[test]
1140    fn setters_rebuild_cache_and_keep_correct_results() {
1141        let mut poly = Polygon::new(square(0.0, 10.0), vec![], None);
1142        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1143
1144        poly.set_exterior(square(20.0, 30.0));
1145        assert!(!poly.contains_point(Point { x: 1.0, y: 1.0 }));
1146        assert!(poly.contains_point(Point { x: 21.0, y: 21.0 }));
1147
1148        poly.set_holes(vec![square(22.0, 24.0)]);
1149        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1150
1151        poly.set_options(PolygonBuildOptions {
1152            enable_rtree: false,
1153            enable_compressed_quad: false,
1154            rtree_min_segments: 64,
1155        });
1156        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1157        assert!(poly.contains_point(Point { x: 25.0, y: 25.0 }));
1158    }
1159
1160    #[test]
1161    fn compressed_quad_candidates_match_scan() {
1162        let ring = polygon_with_segments(256);
1163        let options = PolygonBuildOptions {
1164            enable_rtree: false,
1165            enable_compressed_quad: true,
1166            rtree_min_segments: 64,
1167        };
1168        let index = build_ring_index(&ring, &options).unwrap();
1169
1170        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1171            let mut from_index = Vec::new();
1172            index.search_candidates(y, &mut from_index);
1173            from_index.sort_unstable();
1174
1175            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1176            from_scan.sort_unstable();
1177
1178            assert_eq!(from_index, from_scan);
1179        }
1180    }
1181}