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, PartialEq)]
9pub struct Point<T = f64> {
10    pub x: T,
11    pub y: T,
12}
13
14/// A coordinate stored as an integer with an application-defined scale.
15pub type I32Point = Point<i32>;
16
17/// Coordinate storage for [`Polygon`].
18///
19/// The polygon's bounding box, acceleration indexes and raycast all operate in
20/// "storage space": the query point is multiplied by the polygon's scale once
21/// per containment check, and segment endpoints are converted with
22/// [`CoordStorage::to_f64`] in registers. `f64::from(i32)` is exact (`i32`
23/// fits within `f64`'s 53-bit mantissa), so integer storage loses no
24/// precision, and `x * 1.0` is an exact identity, so `f64` storage behaves
25/// bit-identically to a non-generic implementation.
26pub trait CoordStorage: Copy + PartialOrd {
27    /// Convert a stored coordinate to `f64`.
28    /// Identity for `f64`; exact `f64::from` for `i32`.
29    fn to_f64(self) -> f64;
30    /// Convert a stored coordinate to `i64` for the opt-in integer raycast.
31    /// Only meaningful for integer storage.
32    fn to_i64(self) -> i64;
33}
34
35impl CoordStorage for f64 {
36    #[inline(always)]
37    fn to_f64(self) -> f64 {
38        self
39    }
40
41    #[inline(always)]
42    fn to_i64(self) -> i64 {
43        self as i64
44    }
45}
46
47impl CoordStorage for i32 {
48    #[inline(always)]
49    fn to_f64(self) -> f64 {
50        f64::from(self)
51    }
52
53    #[inline(always)]
54    fn to_i64(self) -> i64 {
55        i64::from(self)
56    }
57}
58
59pub trait ContainsPoint<Q> {
60    fn contains_point(&self, point: Q) -> bool;
61}
62
63#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
64pub enum I32RaycastMode {
65    #[default]
66    Float,
67    Integer,
68}
69
70/// A compact polygon whose coordinates are scaled integers.
71///
72/// This is [`Polygon`] with `i32` storage: eight bytes per stored point, with
73/// full support for the acceleration indexes in [`PolygonBuildOptions`].
74pub type I32Polygon = Polygon<i32>;
75
76#[inline]
77fn ring_contains_point_integer<T: CoordStorage>(
78    ring: &[Point<T>],
79    point: Point<i64>,
80    allow_on_edge: bool,
81) -> bool {
82    let mut inside = false;
83    for pair in ring.windows(2) {
84        let ax = pair[0].x.to_i64();
85        let ay = pair[0].y.to_i64();
86        let bx = pair[1].x.to_i64();
87        let by = pair[1].y.to_i64();
88        let cross = (bx - ax) * (point.y - ay) - (by - ay) * (point.x - ax);
89        if cross == 0
90            && point.x >= ax.min(bx)
91            && point.x <= ax.max(bx)
92            && point.y >= ay.min(by)
93            && point.y <= ay.max(by)
94        {
95            return allow_on_edge;
96        }
97        if (ay > point.y) != (by > point.y) && (cross > 0) == (by > ay) {
98            inside = !inside;
99        }
100    }
101    inside
102}
103
104#[derive(Copy, Clone, Debug)]
105pub struct Rect {
106    pub min: Point,
107    pub max: Point,
108}
109
110impl Rect {
111    pub fn contains_point(&self, p: Point) -> bool {
112        return p.x >= self.min.x && p.x <= self.max.x && p.y >= self.min.y && p.y <= self.max.y;
113    }
114
115    pub fn intersects_rect(&self, other: Rect) -> bool {
116        if self.min.y > other.max.y || self.max.y < other.min.y {
117            return false;
118        }
119        if self.min.x > other.max.x || self.max.x < other.min.x {
120            return false;
121        }
122        return true;
123    }
124
125    pub fn nw(&self) -> Point {
126        Point {
127            x: self.min.x,
128            y: self.max.y,
129        }
130    }
131
132    pub fn sw(&self) -> Point {
133        Point {
134            x: self.min.x,
135            y: self.min.y,
136        }
137    }
138
139    pub fn se(&self) -> Point {
140        Point {
141            x: self.max.x,
142            y: self.min.y,
143        }
144    }
145
146    pub fn ne(&self) -> Point {
147        Point {
148            x: self.max.x,
149            y: self.max.y,
150        }
151    }
152
153    pub fn south(&self) -> Segment {
154        Segment {
155            a: self.sw(),
156            b: self.se(),
157        }
158    }
159
160    pub fn east(&self) -> Segment {
161        Segment {
162            a: self.se(),
163            b: self.ne(),
164        }
165    }
166
167    pub fn north(&self) -> Segment {
168        Segment {
169            a: self.ne(),
170            b: self.nw(),
171        }
172    }
173
174    pub fn west(&self) -> Segment {
175        Segment {
176            a: self.nw(),
177            b: self.sw(),
178        }
179    }
180
181    pub fn segment_at(&self, index: i64) -> Segment {
182        match index {
183            0 => self.south(),
184            1 => self.east(),
185            2 => self.north(),
186            3 => self.west(),
187            _ => self.south(), // TODO(ringsaturn): raise err
188        }
189    }
190}
191
192#[derive(Copy, Clone, Debug)]
193pub struct PolygonBuildOptions {
194    pub enable_rtree: bool,
195    pub enable_compressed_quad: bool,
196    pub enable_y_stripes: bool,
197    pub rtree_min_segments: usize,
198}
199
200impl Default for PolygonBuildOptions {
201    fn default() -> Self {
202        Self {
203            enable_rtree: false,
204            enable_compressed_quad: true,
205            enable_y_stripes: false,
206            rtree_min_segments: 64,
207        }
208    }
209}
210
211#[derive(Clone, Debug, Default, PartialEq, Eq)]
212pub struct YStripesBuildStats {
213    pub segment_count: usize,
214    pub stripe_count: usize,
215    pub assigned_item_count: usize,
216    pub max_bucket_len: usize,
217}
218
219#[derive(Clone, Debug, Default, PartialEq, Eq)]
220pub struct RingBuildStats {
221    pub segment_count: usize,
222    pub below_threshold: bool,
223    pub used_rtree: bool,
224    pub used_compressed_quad: bool,
225    pub used_y_stripes: bool,
226    pub y_stripes: Option<YStripesBuildStats>,
227}
228
229#[derive(Clone, Debug, Default, PartialEq, Eq)]
230pub struct PolygonIndexStats {
231    pub exterior: RingBuildStats,
232    pub holes: Vec<RingBuildStats>,
233}
234
235struct RingIndex {
236    x_min: f64,
237    x_max: f64,
238    seg_count: usize,
239    rtree: Option<RTree<2, f64, usize>>,
240    compressed_quad: Option<CompressedQuadIndex>,
241    y_stripes: Option<YStripesIndex>,
242}
243
244impl RingIndex {
245    fn search_candidates<T: CoordStorage>(
246        &self,
247        ring: &[Point<T>],
248        point_y: f64,
249        out: &mut Vec<usize>,
250    ) {
251        // The common configurations enable one index. Avoid allocating the
252        // deduplication bitmap and temporary vectors when no merge is needed.
253        match (
254            self.rtree.as_ref(),
255            self.y_stripes.as_ref(),
256            self.compressed_quad.as_ref(),
257        ) {
258            (None, Some(index), None) => {
259                index.search(ring, point_y, out);
260                return;
261            }
262            (Some(tree), None, None) => {
263                let query = RTreeRect::new([self.x_min, point_y], [self.x_max, point_y]);
264                out.extend(
265                    tree.search(query)
266                        .map(|item| *item.data)
267                        .filter(|&idx| idx < self.seg_count),
268                );
269                return;
270            }
271            (None, None, Some(index)) => {
272                let query_rect = Rect {
273                    min: Point {
274                        x: self.x_min,
275                        y: point_y,
276                    },
277                    max: Point {
278                        x: self.x_max,
279                        y: point_y,
280                    },
281                };
282                index.search_intersects(query_rect, out);
283                return;
284            }
285            _ => {}
286        }
287
288        let query_rect = Rect {
289            min: Point {
290                x: self.x_min,
291                y: point_y,
292            },
293            max: Point {
294                x: self.x_max,
295                y: point_y,
296            },
297        };
298
299        let mut seen = vec![false; self.seg_count];
300
301        if let Some(tree) = self.rtree.as_ref() {
302            let query = RTreeRect::new([self.x_min, point_y], [self.x_max, point_y]);
303            for item in tree.search(query) {
304                let idx = *item.data;
305                if idx < self.seg_count && !seen[idx] {
306                    seen[idx] = true;
307                    out.push(idx);
308                }
309            }
310        }
311
312        if let Some(index) = self.y_stripes.as_ref() {
313            let mut tmp = Vec::new();
314            index.search(ring, point_y, &mut tmp);
315            for idx in tmp {
316                if idx < self.seg_count && !seen[idx] {
317                    seen[idx] = true;
318                    out.push(idx);
319                }
320            }
321        }
322
323        if let Some(index) = self.compressed_quad.as_ref() {
324            let mut tmp = Vec::new();
325            index.search_intersects(query_rect, &mut tmp);
326            for idx in tmp {
327                if idx < self.seg_count && !seen[idx] {
328                    seen[idx] = true;
329                    out.push(idx);
330                }
331            }
332        }
333    }
334}
335
336#[derive(Clone, Copy, Default)]
337struct YStripe {
338    start: u32,
339    count: u32,
340}
341
342struct YStripesIndex {
343    min_y: f64,
344    height: f64,
345    stripes: Vec<YStripe>,
346    indexes: Vec<u32>,
347}
348
349impl YStripesIndex {
350    fn build<T: CoordStorage>(
351        ring: &[Point<T>],
352        seg_rects: &[Rect],
353    ) -> Option<(Self, YStripesBuildStats)> {
354        if seg_rects.is_empty() {
355            return None;
356        }
357
358        let mut min_y = seg_rects[0].min.y;
359        let mut max_y = seg_rects[0].max.y;
360        for rect in seg_rects.iter().copied() {
361            min_y = min_y.min(rect.min.y);
362            max_y = max_y.max(rect.max.y);
363        }
364
365        let mut stripe_count = calc_y_stripe_count(ring, seg_rects.len());
366        if stripe_count == 0 {
367            stripe_count = 1;
368        }
369
370        let height = max_y - min_y;
371        let mut counts = vec![0usize; stripe_count];
372        for rect in seg_rects.iter().copied() {
373            let (start, end) = stripe_bounds_for_rect(rect, min_y, height, stripe_count);
374            for stripe in start..=end {
375                counts[stripe] += 1;
376            }
377        }
378
379        let mut stripes = vec![YStripe::default(); stripe_count];
380        let mut offset = 0usize;
381        for (stripe, count) in stripes.iter_mut().zip(&counts) {
382            stripe.start = u32::try_from(offset).ok()?;
383            stripe.count = 0;
384            offset += *count;
385        }
386        // Segment indices and stripe offsets are stored as u32.
387        if u32::try_from(offset).is_err() || u32::try_from(seg_rects.len()).is_err() {
388            return None;
389        }
390
391        let mut indexes = vec![0u32; offset];
392        for (idx, rect) in seg_rects.iter().copied().enumerate() {
393            let (start, end) = stripe_bounds_for_rect(rect, min_y, height, stripe_count);
394            for stripe_index in start..=end {
395                let stripe = &mut stripes[stripe_index];
396                indexes[(stripe.start + stripe.count) as usize] = idx as u32;
397                stripe.count += 1;
398            }
399        }
400
401        let mut stats = YStripesBuildStats {
402            segment_count: seg_rects.len(),
403            stripe_count,
404            assigned_item_count: indexes.len(),
405            max_bucket_len: counts.into_iter().max().unwrap_or(0),
406        };
407        if stats.stripe_count == 0 {
408            stats.stripe_count = 1;
409        }
410
411        Some((
412            Self {
413                min_y,
414                height,
415                stripes,
416                indexes,
417            },
418            stats,
419        ))
420    }
421
422    fn search<T: CoordStorage>(&self, ring: &[Point<T>], y: f64, out: &mut Vec<usize>) {
423        if self.height == 0.0 {
424            if y != self.min_y {
425                return;
426            }
427        } else if y < self.min_y || y > self.min_y + self.height {
428            return;
429        }
430
431        let stripe_index = if self.height == 0.0 {
432            0
433        } else {
434            let raw = ((y - self.min_y) / self.height * self.stripes.len() as f64).floor() as isize;
435            raw.clamp(0, self.stripes.len() as isize - 1) as usize
436        };
437        let stripe = self.stripes[stripe_index];
438        let start = stripe.start as usize;
439        let end = start + stripe.count as usize;
440        for idx in &self.indexes[start..end] {
441            let idx = *idx as usize;
442            // Recompute the segment's y range from its endpoints instead of
443            // storing it; the endpoints are about to be fetched for the
444            // raycast anyway.
445            let a_y = ring[idx].y.to_f64();
446            let b_y = ring[idx + 1].y.to_f64();
447            let (seg_min_y, seg_max_y) = if a_y <= b_y { (a_y, b_y) } else { (b_y, a_y) };
448            if y >= seg_min_y && y <= seg_max_y {
449                out.push(idx);
450            }
451        }
452    }
453}
454
455fn stripe_bounds_for_rect(
456    rect: Rect,
457    min_y: f64,
458    height: f64,
459    stripe_count: usize,
460) -> (usize, usize) {
461    if stripe_count <= 1 || height == 0.0 {
462        return (0, 0);
463    }
464
465    let last = stripe_count - 1;
466    let start = (((rect.min.y - min_y) / height) * stripe_count as f64).floor() as isize;
467    let end = (((rect.max.y - min_y) / height) * stripe_count as f64).floor() as isize;
468    (
469        start.clamp(0, last as isize) as usize,
470        end.clamp(0, last as isize) as usize,
471    )
472}
473
474fn calc_ring_area_and_perimeter<T: CoordStorage>(ring: &[Point<T>]) -> (f64, f64) {
475    let seg_count = ring_segment_count(ring);
476    if seg_count == 0 {
477        return (0.0, 0.0);
478    }
479
480    // Computed in storage space; the stripe-count score below is the
481    // isoperimetric quotient, which is invariant under uniform scaling.
482    let mut signed_area = 0.0;
483    let mut perimeter = 0.0;
484    for i in 0..seg_count {
485        let a = Point {
486            x: ring[i].x.to_f64(),
487            y: ring[i].y.to_f64(),
488        };
489        let b = Point {
490            x: ring[i + 1].x.to_f64(),
491            y: ring[i + 1].y.to_f64(),
492        };
493        signed_area += a.x * b.y - b.x * a.y;
494        perimeter += ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt();
495    }
496    (signed_area.abs() * 0.5, perimeter)
497}
498
499fn calc_y_stripe_count<T: CoordStorage>(ring: &[Point<T>], seg_count: usize) -> usize {
500    let (area, perimeter) = calc_ring_area_and_perimeter(ring);
501    let mut score = 0.0;
502    if perimeter > 0.0 {
503        score = (area * std::f64::consts::PI * 4.0) / (perimeter * perimeter);
504    }
505    ((seg_count as f64 * score).floor() as usize).max(32)
506}
507
508const Q_MAX_ITEMS: usize = 12;
509const Q_MAX_DEPTH: usize = 64;
510
511#[derive(Default)]
512struct QuadNode {
513    split: bool,
514    items: Vec<usize>,
515    quads: [Option<Box<QuadNode>>; 4],
516}
517
518impl QuadNode {
519    fn new() -> Self {
520        Self {
521            split: false,
522            items: Vec::new(),
523            quads: [None, None, None, None],
524        }
525    }
526}
527
528struct CompressedQuadIndex {
529    bounds: Rect,
530    seg_rects: Vec<Rect>,
531    data: Vec<u8>,
532}
533
534impl CompressedQuadIndex {
535    fn build<T: CoordStorage>(ring: &[Point<T>]) -> Option<Self> {
536        let seg_count = ring_segment_count(ring);
537        if seg_count == 0 {
538            return None;
539        }
540
541        let mut min_x = ring[0].x.to_f64();
542        let mut min_y = ring[0].y.to_f64();
543        let mut max_x = min_x;
544        let mut max_y = min_y;
545
546        for p in ring.iter().take(seg_count) {
547            let x = p.x.to_f64();
548            let y = p.y.to_f64();
549            if x < min_x {
550                min_x = x;
551            }
552            if y < min_y {
553                min_y = y;
554            }
555            if x > max_x {
556                max_x = x;
557            }
558            if y > max_y {
559                max_y = y;
560            }
561        }
562
563        let bounds = Rect {
564            min: Point { x: min_x, y: min_y },
565            max: Point { x: max_x, y: max_y },
566        };
567
568        let mut seg_rects = Vec::with_capacity(seg_count);
569        for i in 0..seg_count {
570            seg_rects.push(segment_at_for_slice(ring, i).rect());
571        }
572
573        let mut root = QuadNode::new();
574        for i in 0..seg_rects.len() {
575            insert_quad_node(&mut root, bounds, &seg_rects, i, 0);
576        }
577
578        let mut data = Vec::with_capacity(seg_rects.len() * 2);
579        compress_quad_node(&root, &mut data);
580
581        Some(Self {
582            bounds,
583            seg_rects,
584            data,
585        })
586    }
587
588    fn search_intersects(&self, query: Rect, out: &mut Vec<usize>) {
589        if !self.bounds.intersects_rect(query) {
590            return;
591        }
592        let _ = self.search_intersects_from(0, self.bounds, query, out);
593    }
594
595    fn search_intersects_from(
596        &self,
597        mut addr: usize,
598        bounds: Rect,
599        query: Rect,
600        out: &mut Vec<usize>,
601    ) -> Option<usize> {
602        let (nitems, next_addr) = read_uvarint(&self.data, addr)?;
603        addr = next_addr;
604
605        let mut last: usize = 0;
606        for _ in 0..nitems {
607            let (delta, next_addr) = read_uvarint(&self.data, addr)?;
608            addr = next_addr;
609            last = last.checked_add(delta as usize)?;
610            let seg_rect = self.seg_rects.get(last)?;
611            if seg_rect.intersects_rect(query) {
612                out.push(last);
613            }
614        }
615
616        let split = *self.data.get(addr)?;
617        addr += 1;
618        if split == 0 {
619            return Some(addr);
620        }
621        if split != 1 {
622            return None;
623        }
624
625        for q in 0..4 {
626            let (qsize, next_addr) = read_uvarint(&self.data, addr)?;
627            addr = next_addr;
628            if qsize == 0 {
629                continue;
630            }
631
632            let qsize = usize::try_from(qsize).ok()?;
633            let qbounds = quad_bounds(bounds, q);
634            let child_start = addr;
635            let child_end = child_start.checked_add(qsize)?;
636            if child_end > self.data.len() {
637                return None;
638            }
639
640            if qbounds.intersects_rect(query) {
641                let _ = self.search_intersects_from(child_start, qbounds, query, out)?;
642            }
643            addr = child_end;
644        }
645
646        Some(addr)
647    }
648}
649
650fn ring_segment_count<T>(ring: &[Point<T>]) -> usize {
651    ring.len().saturating_sub(1)
652}
653
654// Rings are stored closed (first point repeated at the end), so segment `i`
655// is `ring[i]..ring[i+1]` and the segment count is `len - 1`. The YStripes
656// build and its query-time y-range recomputation both rely on this numbering.
657fn segment_at_for_slice<T: CoordStorage>(ring: &[Point<T>], index: usize) -> Segment {
658    Segment {
659        a: Point {
660            x: ring[index].x.to_f64(),
661            y: ring[index].y.to_f64(),
662        },
663        b: Point {
664            x: ring[index + 1].x.to_f64(),
665            y: ring[index + 1].y.to_f64(),
666        },
667    }
668}
669
670fn build_ring_index<T: CoordStorage>(
671    ring: &[Point<T>],
672    options: &PolygonBuildOptions,
673) -> (Option<RingIndex>, RingBuildStats) {
674    let seg_count = ring_segment_count(ring);
675    let index_requested =
676        options.enable_rtree || options.enable_compressed_quad || options.enable_y_stripes;
677    let mut stats = RingBuildStats {
678        segment_count: seg_count,
679        below_threshold: index_requested && seg_count < options.rtree_min_segments,
680        ..RingBuildStats::default()
681    };
682
683    if !index_requested || ring.is_empty() || seg_count < options.rtree_min_segments {
684        return (None, stats);
685    }
686
687    let mut x_min = ring[0].x.to_f64();
688    let mut x_max = x_min;
689    for p in ring.iter().take(seg_count) {
690        let x = p.x.to_f64();
691        if x < x_min {
692            x_min = x;
693        }
694        if x > x_max {
695            x_max = x;
696        }
697    }
698
699    let rtree = if options.enable_rtree {
700        stats.used_rtree = true;
701        let mut tree: RTree<2, f64, usize> = RTree::new();
702        for i in 0..seg_count {
703            let seg_rect = segment_at_for_slice(ring, i).rect();
704            tree.insert(
705                RTreeRect::new(
706                    [seg_rect.min.x, seg_rect.min.y],
707                    [seg_rect.max.x, seg_rect.max.y],
708                ),
709                i,
710            );
711        }
712        Some(tree)
713    } else {
714        None
715    };
716
717    let compressed_quad = if options.enable_compressed_quad {
718        match CompressedQuadIndex::build(ring) {
719            Some(index) => {
720                stats.used_compressed_quad = true;
721                Some(index)
722            }
723            None => None,
724        }
725    } else {
726        None
727    };
728
729    let (y_stripes, y_stripes_stats) = if options.enable_y_stripes {
730        let mut seg_rects = Vec::with_capacity(seg_count);
731        for i in 0..seg_count {
732            seg_rects.push(segment_at_for_slice(ring, i).rect());
733        }
734        match YStripesIndex::build(ring, &seg_rects) {
735            Some((index, stripe_stats)) => {
736                stats.used_y_stripes = true;
737                (Some(index), Some(stripe_stats))
738            }
739            None => (None, None),
740        }
741    } else {
742        (None, None)
743    };
744    stats.y_stripes = y_stripes_stats;
745
746    if rtree.is_none() && compressed_quad.is_none() && y_stripes.is_none() {
747        return (None, stats);
748    }
749
750    (
751        Some(RingIndex {
752            x_min,
753            x_max,
754            seg_count,
755            rtree,
756            compressed_quad,
757            y_stripes,
758        }),
759        stats,
760    )
761}
762
763fn rings_contains_point<T: CoordStorage>(
764    ring: &[Point<T>],
765    ring_index: Option<&RingIndex>,
766    point: Point,
767    allow_on_edge: bool,
768) -> bool {
769    let mut inside: bool = false;
770
771    if let Some(index) = ring_index {
772        let mut candidates = Vec::new();
773        index.search_candidates(ring, point.y, &mut candidates);
774        for i in candidates {
775            let seg = segment_at_for_slice(ring, i);
776            let res: RaycastResult = raycast(&seg, point);
777            if res.on {
778                inside = allow_on_edge;
779                break;
780            }
781            if res.inside {
782                inside = !inside;
783            }
784        }
785        return inside;
786    }
787
788    for pair in ring.windows(2) {
789        let seg = Segment {
790            a: Point {
791                x: pair[0].x.to_f64(),
792                y: pair[0].y.to_f64(),
793            },
794            b: Point {
795                x: pair[1].x.to_f64(),
796                y: pair[1].y.to_f64(),
797            },
798        };
799
800        // The ray is horizontal and unbounded in x, so intersecting the
801        // segment's rect reduces to a y-range check; f64::min/max keep it
802        // branchless.
803        let min_y = seg.a.y.min(seg.b.y);
804        let max_y = seg.a.y.max(seg.b.y);
805        if point.y < min_y || point.y > max_y {
806            continue;
807        }
808
809        let res: RaycastResult = raycast(&seg, point);
810        if res.on {
811            inside = allow_on_edge;
812            break;
813        }
814        if res.inside {
815            inside = !inside;
816        }
817    }
818
819    return inside;
820}
821
822fn choose_quad(bounds: Rect, rect: Rect) -> Option<usize> {
823    let mid_x = (bounds.min.x + bounds.max.x) / 2.0;
824    let mid_y = (bounds.min.y + bounds.max.y) / 2.0;
825
826    if rect.max.x < mid_x {
827        if rect.max.y < mid_y {
828            return Some(2);
829        }
830        if rect.min.y < mid_y {
831            return None;
832        }
833        return Some(0);
834    }
835
836    if rect.min.x < mid_x {
837        return None;
838    }
839
840    if rect.max.y < mid_y {
841        return Some(3);
842    }
843    if rect.min.y < mid_y {
844        return None;
845    }
846    Some(1)
847}
848
849fn quad_bounds(mut bounds: Rect, q: usize) -> Rect {
850    let center_x = (bounds.min.x + bounds.max.x) / 2.0;
851    let center_y = (bounds.min.y + bounds.max.y) / 2.0;
852
853    match q {
854        0 => {
855            bounds.min.y = center_y;
856            bounds.max.x = center_x;
857        }
858        1 => {
859            bounds.min.x = center_x;
860            bounds.min.y = center_y;
861        }
862        2 => {
863            bounds.max.x = center_x;
864            bounds.max.y = center_y;
865        }
866        3 => {
867            bounds.min.x = center_x;
868            bounds.max.y = center_y;
869        }
870        _ => {}
871    }
872    bounds
873}
874
875fn insert_quad_node(
876    node: &mut QuadNode,
877    bounds: Rect,
878    seg_rects: &[Rect],
879    item: usize,
880    depth: usize,
881) {
882    if depth == Q_MAX_DEPTH {
883        node.items.push(item);
884        return;
885    }
886
887    let item_rect = seg_rects[item];
888    if node.split {
889        if let Some(q) = choose_quad(bounds, item_rect) {
890            let qbounds = quad_bounds(bounds, q);
891            if node.quads[q].is_none() {
892                node.quads[q] = Some(Box::new(QuadNode::new()));
893            }
894            if let Some(quad) = node.quads[q].as_deref_mut() {
895                insert_quad_node(quad, qbounds, seg_rects, item, depth + 1);
896            }
897        } else {
898            node.items.push(item);
899        }
900        return;
901    }
902
903    if node.items.len() == Q_MAX_ITEMS {
904        let existing = std::mem::take(&mut node.items);
905        node.split = true;
906        for i in existing {
907            let rect = seg_rects[i];
908            if let Some(q) = choose_quad(bounds, rect) {
909                let qbounds = quad_bounds(bounds, q);
910                if node.quads[q].is_none() {
911                    node.quads[q] = Some(Box::new(QuadNode::new()));
912                }
913                if let Some(quad) = node.quads[q].as_deref_mut() {
914                    insert_quad_node(quad, qbounds, seg_rects, i, depth + 1);
915                }
916            } else {
917                node.items.push(i);
918            }
919        }
920        insert_quad_node(node, bounds, seg_rects, item, depth);
921        return;
922    }
923
924    node.items.push(item);
925}
926
927fn append_uvarint(dst: &mut Vec<u8>, mut x: u64) {
928    while x >= 0x80 {
929        dst.push((x as u8 & 0x7f) | 0x80);
930        x >>= 7;
931    }
932    dst.push(x as u8);
933}
934
935fn read_uvarint(data: &[u8], mut addr: usize) -> Option<(u64, usize)> {
936    let mut x: u64 = 0;
937    let mut shift = 0;
938
939    loop {
940        let b = *data.get(addr)?;
941        addr += 1;
942
943        if shift == 70 {
944            return None;
945        }
946
947        x |= ((b & 0x7f) as u64) << shift;
948        if b < 0x80 {
949            return Some((x, addr));
950        }
951        shift += 7;
952    }
953}
954
955fn compress_quad_node(node: &QuadNode, dst: &mut Vec<u8>) {
956    let mut items = node.items.clone();
957    items.sort_unstable();
958
959    append_uvarint(dst, items.len() as u64);
960    let mut last = 0usize;
961    for item in items {
962        append_uvarint(dst, (item - last) as u64);
963        last = item;
964    }
965
966    if !node.split {
967        dst.push(0);
968        return;
969    }
970
971    dst.push(1);
972    for q in 0..4 {
973        if let Some(child) = node.quads[q].as_deref() {
974            let mut child_bytes = Vec::new();
975            compress_quad_node(child, &mut child_bytes);
976            append_uvarint(dst, child_bytes.len() as u64);
977            dst.extend_from_slice(&child_bytes);
978        } else {
979            append_uvarint(dst, 0);
980        }
981    }
982}
983
984/// A polygon whose coordinates are stored as `T` (`f64` degrees by default,
985/// or scaled `i32`, see [`I32Polygon`]).
986///
987/// The bounding box, the acceleration indexes and the raycast all operate in
988/// storage space: [`Polygon::contains_point`] multiplies the query point by
989/// the polygon's scale exactly once, and segment endpoints are converted to
990/// `f64` in registers during the raycast.
991pub struct Polygon<T: CoordStorage = f64> {
992    exterior: Vec<Point<T>>,
993    holes: Vec<Vec<Point<T>>>,
994    // Storage-space bounding box, kept in storage type so the bbox reject
995    // scan touches half the memory for i32 polygons.
996    min: Point<T>,
997    max: Point<T>,
998    scale: f64,
999    raycast_mode: I32RaycastMode,
1000    options: PolygonBuildOptions,
1001    exterior_index: Option<RingIndex>,
1002    hole_indexes: Vec<Option<RingIndex>>,
1003    index_stats: PolygonIndexStats,
1004}
1005
1006impl<T: CoordStorage> Polygon<T> {
1007    fn compute_bounds(exterior: &[Point<T>]) -> (Point<T>, Point<T>) {
1008        let mut min = exterior[0];
1009        let mut max = exterior[0];
1010
1011        for p in exterior.iter() {
1012            if p.x < min.x {
1013                min.x = p.x;
1014            }
1015            if p.y < min.y {
1016                min.y = p.y;
1017            }
1018            if p.x > max.x {
1019                max.x = p.x;
1020            }
1021            if p.y > max.y {
1022                max.y = p.y;
1023            }
1024        }
1025
1026        (min, max)
1027    }
1028
1029    fn build(
1030        exterior: Vec<Point<T>>,
1031        holes: Vec<Vec<Point<T>>>,
1032        scale: f64,
1033        raycast_mode: I32RaycastMode,
1034        options: Option<PolygonBuildOptions>,
1035    ) -> Self {
1036        let (min, max) = Self::compute_bounds(&exterior);
1037        let mut poly = Self {
1038            exterior,
1039            holes,
1040            min,
1041            max,
1042            scale,
1043            raycast_mode,
1044            options: options.unwrap_or_default(),
1045            exterior_index: None,
1046            hole_indexes: Vec::new(),
1047            index_stats: PolygonIndexStats::default(),
1048        };
1049        poly.rebuild_cache();
1050        poly
1051    }
1052
1053    fn rebuild_cache(&mut self) {
1054        let (min, max) = Self::compute_bounds(&self.exterior);
1055        self.min = min;
1056        self.max = max;
1057        let (exterior_index, exterior_stats) = build_ring_index(&self.exterior, &self.options);
1058        self.exterior_index = exterior_index;
1059        self.index_stats.exterior = exterior_stats;
1060
1061        let hole_indexes_and_stats: Vec<(Option<RingIndex>, RingBuildStats)> = self
1062            .holes
1063            .iter()
1064            .map(|hole| build_ring_index(hole, &self.options))
1065            .collect();
1066        let (hole_indexes, hole_stats): (Vec<Option<RingIndex>>, Vec<RingBuildStats>) =
1067            hole_indexes_and_stats.into_iter().unzip();
1068        self.hole_indexes = hole_indexes;
1069        self.index_stats.holes = hole_stats;
1070    }
1071
1072    /// Point-In-Polygon check, the normal way.
1073    /// It's most used algorithm implementation, port from Go's [geojson]
1074    ///
1075    /// [geojson]: https://github.com/tidwall/geojson
1076    fn contains_point_normal(&self, p: Point) -> bool {
1077        if !rings_contains_point(&self.exterior, self.exterior_index.as_ref(), p, false) {
1078            return false;
1079        }
1080
1081        for (hole, hole_index) in self.holes.iter().zip(self.hole_indexes.iter()) {
1082            if rings_contains_point(hole, hole_index.as_ref(), p, false) {
1083                return false;
1084            }
1085        }
1086
1087        return true;
1088    }
1089
1090    /// Do point-in-polygon search. `p` is in unscaled (degree) coordinates.
1091    pub fn contains_point(&self, p: Point) -> bool {
1092        match self.raycast_mode {
1093            I32RaycastMode::Float => self.contains_point_float(p),
1094            I32RaycastMode::Integer => self.contains_point_integer(p),
1095        }
1096    }
1097
1098    fn contains_point_float(&self, p: Point) -> bool {
1099        // `x * 1.0` is an exact identity under IEEE 754, so f64 polygons
1100        // (scale == 1.0) behave bit-identically to the pre-generic code.
1101        let scaled = Point {
1102            x: p.x * self.scale,
1103            y: p.y * self.scale,
1104        };
1105        // Same truth table as Rect::contains_point; the storage-type bounds
1106        // are converted in registers.
1107        if !(scaled.x >= self.min.x.to_f64()
1108            && scaled.x <= self.max.x.to_f64()
1109            && scaled.y >= self.min.y.to_f64()
1110            && scaled.y <= self.max.y.to_f64())
1111        {
1112            return false;
1113        }
1114
1115        return self.contains_point_normal(scaled);
1116    }
1117
1118    // Opt-in integer cross-product raycast; snaps the query to the storage
1119    // grid and ignores acceleration indexes.
1120    fn contains_point_integer(&self, p: Point) -> bool {
1121        if !p.x.is_finite() || !p.y.is_finite() {
1122            return false;
1123        }
1124        let scaled = Point::<i64> {
1125            x: (p.x * self.scale).round() as i64,
1126            y: (p.y * self.scale).round() as i64,
1127        };
1128        if scaled.x < self.min.x.to_i64()
1129            || scaled.x > self.max.x.to_i64()
1130            || scaled.y < self.min.y.to_i64()
1131            || scaled.y > self.max.y.to_i64()
1132            || !ring_contains_point_integer(&self.exterior, scaled, false)
1133        {
1134            return false;
1135        }
1136        !self
1137            .holes
1138            .iter()
1139            .any(|ring| ring_contains_point_integer(ring, scaled, false))
1140    }
1141    pub fn exterior(&self) -> &[Point<T>] {
1142        &self.exterior
1143    }
1144
1145    pub fn holes(&self) -> &[Vec<Point<T>>] {
1146        &self.holes
1147    }
1148
1149    /// The polygon's bounding box in storage space (degrees for `f64`
1150    /// polygons, scaled integers for [`I32Polygon`]).
1151    pub fn rect(&self) -> Rect {
1152        Rect {
1153            min: Point {
1154                x: self.min.x.to_f64(),
1155                y: self.min.y.to_f64(),
1156            },
1157            max: Point {
1158                x: self.max.x.to_f64(),
1159                y: self.max.y.to_f64(),
1160            },
1161        }
1162    }
1163
1164    /// The factor a degree coordinate is multiplied by to reach storage
1165    /// space: `1.0` for `f64` polygons, e.g. `1e5` for [`I32Polygon`].
1166    pub fn scale(&self) -> f64 {
1167        self.scale
1168    }
1169
1170    pub fn options(&self) -> PolygonBuildOptions {
1171        self.options
1172    }
1173
1174    pub fn index_stats(&self) -> &PolygonIndexStats {
1175        &self.index_stats
1176    }
1177
1178    pub fn set_exterior(&mut self, exterior: Vec<Point<T>>) {
1179        self.exterior = exterior;
1180        self.rebuild_cache();
1181    }
1182
1183    pub fn set_holes(&mut self, holes: Vec<Vec<Point<T>>>) {
1184        self.holes = holes;
1185        self.rebuild_cache();
1186    }
1187
1188    pub fn set_options(&mut self, options: PolygonBuildOptions) {
1189        self.options = options;
1190        self.rebuild_cache();
1191    }
1192}
1193
1194impl Polygon {
1195    /// Create a new Polygon instance from exterior and holes.
1196    ///
1197    /// Example:
1198    ///
1199    /// ```rust
1200    /// use std::vec;
1201    /// use geometry_rs;
1202    /// let poly = geometry_rs::Polygon::new(
1203    ///     vec![
1204    ///         geometry_rs::Point {
1205    ///             x: 90.48826291293898,
1206    ///             y: 45.951129815858565,
1207    ///         },
1208    ///         geometry_rs::Point {
1209    ///             x: 90.48826291293898,
1210    ///             y: 27.99437617512571,
1211    ///         },
1212    ///         geometry_rs::Point {
1213    ///             x: 122.83201291294,
1214    ///             y: 27.99437617512571,
1215    ///         },
1216    ///         geometry_rs::Point {
1217    ///             x: 122.83201291294,
1218    ///             y: 45.951129815858565,
1219    ///         },
1220    ///         geometry_rs::Point {
1221    ///             x: 90.48826291293898,
1222    ///             y: 45.951129815858565,
1223    ///         },
1224    ///     ],
1225    ///     vec![],
1226    ///     None,
1227    /// );
1228    ///
1229    /// let p_out = geometry_rs::Point {
1230    ///     x: 130.74216916294148,
1231    ///     y: 37.649011392900306,
1232    /// };
1233    ///
1234    /// print!("{:?}\n", poly.contains_point(p_out));
1235    ///
1236    /// let p_in = geometry_rs::Point {
1237    ///     x: 99.9804504129416,
1238    ///     y: 39.70716466970461,
1239    /// };
1240    /// print!("{:?}\n", poly.contains_point(p_in));
1241    /// ```
1242    pub fn new(
1243        exterior: Vec<Point>,
1244        holes: Vec<Vec<Point>>,
1245        options: Option<PolygonBuildOptions>,
1246    ) -> Polygon {
1247        Self::build(exterior, holes, 1.0, I32RaycastMode::Float, options)
1248    }
1249}
1250
1251impl I32Polygon {
1252    /// Create a polygon from scaled integer coordinates with no acceleration
1253    /// index.
1254    ///
1255    /// (There is intentionally no `I32Polygon::new`: a second inherent `new`
1256    /// would make plain `Polygon::new(...)` calls ambiguous.)
1257    pub fn new_with_mode(
1258        exterior: Vec<I32Point>,
1259        holes: Vec<Vec<I32Point>>,
1260        scale: f64,
1261        raycast_mode: I32RaycastMode,
1262    ) -> Self {
1263        Self::new_with_options(exterior, holes, scale, raycast_mode, None)
1264    }
1265
1266    /// Create a polygon from scaled integer coordinates with explicit build
1267    /// options; the acceleration indexes operate directly in the scaled
1268    /// integer storage space. The indexes only serve the default (float)
1269    /// raycast; [`I32RaycastMode::Integer`] always scans segments linearly.
1270    pub fn new_with_options(
1271        exterior: Vec<I32Point>,
1272        holes: Vec<Vec<I32Point>>,
1273        scale: f64,
1274        raycast_mode: I32RaycastMode,
1275        options: Option<PolygonBuildOptions>,
1276    ) -> Self {
1277        assert!(!exterior.is_empty(), "polygon exterior must not be empty");
1278        assert!(
1279            scale.is_finite() && scale > 0.0,
1280            "scale must be positive and finite"
1281        );
1282        Self::build(exterior, holes, scale, raycast_mode, options)
1283    }
1284}
1285
1286impl<T: CoordStorage> ContainsPoint<Point> for Polygon<T> {
1287    fn contains_point(&self, point: Point) -> bool {
1288        Polygon::contains_point(self, point)
1289    }
1290}
1291
1292#[derive(Copy, Clone, Debug)]
1293pub struct Segment {
1294    pub a: Point,
1295    pub b: Point,
1296}
1297
1298impl Segment {
1299    pub fn rect(&self) -> Rect {
1300        let mut min_x: f64 = self.a.x;
1301        let mut min_y: f64 = self.a.y;
1302        let mut max_x: f64 = self.b.x;
1303        let mut max_y: f64 = self.b.y;
1304
1305        if min_x > max_x {
1306            let actual_min_x = max_x;
1307            let actual_max_x = min_x;
1308            min_x = actual_min_x;
1309            max_x = actual_max_x;
1310        }
1311
1312        if min_y > max_y {
1313            let actual_min_y = max_y;
1314            let actual_max_y = min_y;
1315            min_y = actual_min_y;
1316            max_y = actual_max_y;
1317        }
1318
1319        return Rect {
1320            min: Point { x: min_x, y: min_y },
1321            max: Point { x: max_x, y: max_y },
1322        };
1323    }
1324}
1325
1326pub struct RaycastResult {
1327    inside: bool, // point on the left
1328    on: bool,     // point is directly on top of
1329}
1330
1331pub fn raycast(seg: &Segment, point: Point) -> RaycastResult {
1332    let mut p = point;
1333    let a = seg.a;
1334    let b = seg.b;
1335
1336    // make sure that the point is inside the segment bounds
1337    if a.y < b.y && (p.y < a.y || p.y > b.y) {
1338        return RaycastResult {
1339            inside: false,
1340            on: false,
1341        };
1342    } else if a.y > b.y && (p.y < b.y || p.y > a.y) {
1343        return RaycastResult {
1344            inside: false,
1345            on: false,
1346        };
1347    }
1348
1349    // test if point is in on the segment
1350    if a.y == b.y {
1351        if a.x == b.x {
1352            if p.x == a.x && p.y == a.y {
1353                return RaycastResult {
1354                    inside: false,
1355                    on: true,
1356                };
1357            }
1358            return RaycastResult {
1359                inside: false,
1360                on: false,
1361            };
1362        }
1363        if p.y == b.y {
1364            // horizontal segment
1365            // check if the point in on the line
1366            if a.x < b.x {
1367                if p.x >= a.x && p.x <= b.x {
1368                    return RaycastResult {
1369                        inside: false,
1370                        on: true,
1371                    };
1372                }
1373            } else if p.x >= b.x && p.x <= a.x {
1374                return RaycastResult {
1375                    inside: false,
1376                    on: true,
1377                };
1378            }
1379        }
1380    }
1381    if a.x == b.x && p.x == b.x {
1382        // vertical segment
1383        // check if the point in on the line
1384        if a.y < b.y {
1385            if p.y >= a.y && p.y <= b.y {
1386                return RaycastResult {
1387                    inside: false,
1388                    on: true,
1389                };
1390            }
1391        } else if p.y >= b.y && p.y <= a.y {
1392            return RaycastResult {
1393                inside: false,
1394                on: true,
1395            };
1396        }
1397    }
1398    if (p.x - a.x) / (b.x - a.x) == (p.y - a.y) / (b.y - a.y) {
1399        return RaycastResult {
1400            inside: false,
1401            on: true,
1402        };
1403    }
1404
1405    // do the actual raycast here.
1406    while p.y == a.y || p.y == b.y {
1407        p.y = p.y.next_after(std::f64::INFINITY);
1408    }
1409
1410    if a.y < b.y {
1411        if p.y < a.y || p.y > b.y {
1412            return RaycastResult {
1413                inside: false,
1414                on: false,
1415            };
1416        }
1417    } else if p.y < b.y || p.y > a.y {
1418        return RaycastResult {
1419            inside: false,
1420            on: false,
1421        };
1422    }
1423    if a.x > b.x {
1424        if p.x >= a.x {
1425            return RaycastResult {
1426                inside: false,
1427                on: false,
1428            };
1429        }
1430        if p.x <= b.x {
1431            return RaycastResult {
1432                inside: true,
1433                on: false,
1434            };
1435        }
1436    } else {
1437        if p.x >= b.x {
1438            return RaycastResult {
1439                inside: false,
1440                on: false,
1441            };
1442        }
1443        if p.x <= a.x {
1444            return RaycastResult {
1445                inside: true,
1446                on: false,
1447            };
1448        }
1449    }
1450    if a.y < b.y {
1451        if (p.y - a.y) / (p.x - a.x) >= (b.y - a.y) / (b.x - a.x) {
1452            return RaycastResult {
1453                inside: true,
1454                on: false,
1455            };
1456        }
1457    } else if (p.y - b.y) / (p.x - b.x) >= (a.y - b.y) / (a.x - b.x) {
1458        return RaycastResult {
1459            inside: true,
1460            on: false,
1461        };
1462    }
1463    return RaycastResult {
1464        inside: false,
1465        on: false,
1466    };
1467}
1468
1469#[cfg(test)]
1470mod tests {
1471    use super::*;
1472
1473    fn square(min: f64, max: f64) -> Vec<Point> {
1474        vec![
1475            Point { x: min, y: min },
1476            Point { x: min, y: max },
1477            Point { x: max, y: max },
1478            Point { x: max, y: min },
1479            Point { x: min, y: min },
1480        ]
1481    }
1482
1483    fn polygon_with_segments(segments: usize) -> Vec<Point> {
1484        let mut ring = Vec::with_capacity(segments + 1);
1485        for i in 0..segments {
1486            let theta = (i as f64) / (segments as f64) * std::f64::consts::TAU;
1487            ring.push(Point {
1488                x: theta.cos(),
1489                y: theta.sin(),
1490            });
1491        }
1492        ring.push(ring[0]);
1493        ring
1494    }
1495
1496    fn scan_candidates(ring: &[Point], y: f64, x_min: f64, x_max: f64) -> Vec<usize> {
1497        let mut out = Vec::new();
1498        let query = Rect {
1499            min: Point { x: x_min, y },
1500            max: Point { x: x_max, y },
1501        };
1502        for (i, pair) in ring.windows(2).enumerate() {
1503            let seg = Segment {
1504                a: pair[0],
1505                b: pair[1],
1506            };
1507            if seg.rect().intersects_rect(query) {
1508                out.push(i);
1509            }
1510        }
1511        out
1512    }
1513
1514    #[test]
1515    fn rings_contains_point_allow_on_edge() {
1516        let ring = square(0.0, 10.0);
1517        let on_edge = Point { x: 0.0, y: 5.0 };
1518        assert!(rings_contains_point(&ring, None, on_edge, true));
1519        assert!(!rings_contains_point(&ring, None, on_edge, false));
1520    }
1521
1522    #[test]
1523    fn polygon_contains_basic_in_and_out() {
1524        let poly = Polygon::new(square(0.0, 10.0), vec![], None);
1525        assert!(poly.contains_point(Point { x: 5.0, y: 5.0 }));
1526        assert!(!poly.contains_point(Point { x: 20.0, y: 5.0 }));
1527    }
1528
1529    #[test]
1530    fn polygon_contains_with_hole() {
1531        let poly = Polygon::new(square(0.0, 10.0), vec![square(3.0, 7.0)], None);
1532        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1533        assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1534    }
1535
1536    #[test]
1537    fn i32_polygon_contains_with_hole_and_fractional_query() {
1538        let square = |min, max| {
1539            vec![
1540                I32Point { x: min, y: min },
1541                I32Point { x: min, y: max },
1542                I32Point { x: max, y: max },
1543                I32Point { x: max, y: min },
1544                I32Point { x: min, y: min },
1545            ]
1546        };
1547        for mode in [I32RaycastMode::Float, I32RaycastMode::Integer] {
1548            let poly = I32Polygon::new_with_mode(
1549                square(0, 1_000_000),
1550                vec![square(400_000, 600_000)],
1551                1e5,
1552                mode,
1553            );
1554            assert!(poly.contains_point(Point {
1555                x: 1.23456,
1556                y: 2.34567
1557            }));
1558            assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1559            assert!(!poly.contains_point(Point { x: 11.0, y: 5.0 }));
1560        }
1561    }
1562
1563    #[test]
1564    fn indexed_and_non_indexed_results_match() {
1565        let ring = polygon_with_segments(128);
1566        let p_in = Point { x: 0.2, y: 0.1 };
1567        let p_out = Point { x: 2.0, y: 0.0 };
1568
1569        let p1 = Polygon::new(
1570            ring.clone(),
1571            vec![],
1572            Some(PolygonBuildOptions {
1573                enable_rtree: false,
1574                enable_compressed_quad: false,
1575                enable_y_stripes: false,
1576                rtree_min_segments: 64,
1577            }),
1578        );
1579
1580        let p2 = Polygon::new(
1581            ring,
1582            vec![],
1583            Some(PolygonBuildOptions {
1584                enable_rtree: true,
1585                enable_compressed_quad: true,
1586                enable_y_stripes: false,
1587                rtree_min_segments: 64,
1588            }),
1589        );
1590
1591        assert_eq!(p1.contains_point(p_in), p2.contains_point(p_in));
1592        assert_eq!(p1.contains_point(p_out), p2.contains_point(p_out));
1593    }
1594
1595    #[test]
1596    fn rtree_and_compressed_quad_and_both_match_baseline() {
1597        let ring = polygon_with_segments(160);
1598        let points = [
1599            Point { x: 0.2, y: 0.1 },
1600            Point { x: -0.4, y: -0.3 },
1601            Point { x: 1.2, y: 0.0 },
1602        ];
1603
1604        let base = Polygon::new(
1605            ring.clone(),
1606            vec![],
1607            Some(PolygonBuildOptions {
1608                enable_rtree: false,
1609                enable_compressed_quad: false,
1610                enable_y_stripes: false,
1611                rtree_min_segments: 64,
1612            }),
1613        );
1614        let only_rtree = Polygon::new(
1615            ring.clone(),
1616            vec![],
1617            Some(PolygonBuildOptions {
1618                enable_rtree: true,
1619                enable_compressed_quad: false,
1620                enable_y_stripes: false,
1621                rtree_min_segments: 64,
1622            }),
1623        );
1624        let only_compressed = Polygon::new(
1625            ring.clone(),
1626            vec![],
1627            Some(PolygonBuildOptions {
1628                enable_rtree: false,
1629                enable_compressed_quad: true,
1630                enable_y_stripes: false,
1631                rtree_min_segments: 64,
1632            }),
1633        );
1634        let both = Polygon::new(
1635            ring,
1636            vec![],
1637            Some(PolygonBuildOptions {
1638                enable_rtree: true,
1639                enable_compressed_quad: true,
1640                enable_y_stripes: false,
1641                rtree_min_segments: 64,
1642            }),
1643        );
1644
1645        for p in points {
1646            let expected = base.contains_point(p);
1647            assert_eq!(only_rtree.contains_point(p), expected);
1648            assert_eq!(only_compressed.contains_point(p), expected);
1649            assert_eq!(both.contains_point(p), expected);
1650        }
1651    }
1652
1653    #[test]
1654    fn threshold_boundaries_63_64_65_are_consistent() {
1655        let ring = polygon_with_segments(65);
1656        let p_in = Point { x: 0.2, y: 0.0 };
1657        let p_out = Point { x: 1.5, y: 0.0 };
1658
1659        let p63 = Polygon::new(
1660            ring.clone(),
1661            vec![],
1662            Some(PolygonBuildOptions {
1663                enable_rtree: true,
1664                enable_compressed_quad: true,
1665                enable_y_stripes: false,
1666                rtree_min_segments: 63,
1667            }),
1668        );
1669        let p64 = Polygon::new(
1670            ring.clone(),
1671            vec![],
1672            Some(PolygonBuildOptions {
1673                enable_rtree: true,
1674                enable_compressed_quad: true,
1675                enable_y_stripes: false,
1676                rtree_min_segments: 64,
1677            }),
1678        );
1679        let p65 = Polygon::new(
1680            ring,
1681            vec![],
1682            Some(PolygonBuildOptions {
1683                enable_rtree: true,
1684                enable_compressed_quad: true,
1685                enable_y_stripes: false,
1686                rtree_min_segments: 65,
1687            }),
1688        );
1689
1690        assert_eq!(p63.contains_point(p_in), p64.contains_point(p_in));
1691        assert_eq!(p64.contains_point(p_in), p65.contains_point(p_in));
1692
1693        assert_eq!(p63.contains_point(p_out), p64.contains_point(p_out));
1694        assert_eq!(p64.contains_point(p_out), p65.contains_point(p_out));
1695    }
1696
1697    #[test]
1698    fn setters_rebuild_cache_and_keep_correct_results() {
1699        let mut poly = Polygon::new(square(0.0, 10.0), vec![], None);
1700        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1701
1702        poly.set_exterior(square(20.0, 30.0));
1703        assert!(!poly.contains_point(Point { x: 1.0, y: 1.0 }));
1704        assert!(poly.contains_point(Point { x: 21.0, y: 21.0 }));
1705
1706        poly.set_holes(vec![square(22.0, 24.0)]);
1707        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1708
1709        poly.set_options(PolygonBuildOptions {
1710            enable_rtree: false,
1711            enable_compressed_quad: false,
1712            enable_y_stripes: false,
1713            rtree_min_segments: 64,
1714        });
1715        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1716        assert!(poly.contains_point(Point { x: 25.0, y: 25.0 }));
1717    }
1718
1719    #[test]
1720    fn compressed_quad_candidates_match_scan() {
1721        let ring = polygon_with_segments(256);
1722        let options = PolygonBuildOptions {
1723            enable_rtree: false,
1724            enable_compressed_quad: true,
1725            enable_y_stripes: false,
1726            rtree_min_segments: 64,
1727        };
1728        let (index, stats) = build_ring_index(&ring, &options);
1729        let index = index.unwrap();
1730
1731        assert!(stats.used_compressed_quad);
1732        assert!(!stats.below_threshold);
1733
1734        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1735            let mut from_index = Vec::new();
1736            index.search_candidates(&ring, y, &mut from_index);
1737            from_index.sort_unstable();
1738
1739            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1740            from_scan.sort_unstable();
1741
1742            assert_eq!(from_index, from_scan);
1743        }
1744    }
1745
1746    #[test]
1747    fn y_stripes_candidates_match_scan() {
1748        let ring = polygon_with_segments(256);
1749        let options = PolygonBuildOptions {
1750            enable_rtree: false,
1751            enable_compressed_quad: false,
1752            enable_y_stripes: true,
1753            rtree_min_segments: 64,
1754        };
1755        let (index, stats) = build_ring_index(&ring, &options);
1756        let index = index.unwrap();
1757
1758        assert!(stats.used_y_stripes);
1759        assert!(!stats.below_threshold);
1760        let y_stats = stats.y_stripes.as_ref().unwrap();
1761        assert_eq!(y_stats.segment_count, 256);
1762        assert!(y_stats.stripe_count >= 32);
1763        assert!(y_stats.assigned_item_count >= 256);
1764        assert!(y_stats.max_bucket_len > 0);
1765
1766        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1767            let mut from_index = Vec::new();
1768            index.search_candidates(&ring, y, &mut from_index);
1769            from_index.sort_unstable();
1770
1771            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1772            from_scan.sort_unstable();
1773
1774            assert_eq!(from_index, from_scan);
1775        }
1776    }
1777
1778    #[test]
1779    fn polygon_index_stats_capture_threshold_for_compressed_quad() {
1780        let indexed = Polygon::new(
1781            polygon_with_segments(256),
1782            vec![polygon_with_segments(32)],
1783            Some(PolygonBuildOptions {
1784                enable_rtree: false,
1785                enable_compressed_quad: true,
1786                enable_y_stripes: false,
1787                rtree_min_segments: 64,
1788            }),
1789        );
1790        let stats = indexed.index_stats();
1791        assert!(stats.exterior.used_compressed_quad);
1792        assert!(!stats.exterior.below_threshold);
1793        assert_eq!(stats.exterior.segment_count, 256);
1794        assert_eq!(stats.holes.len(), 1);
1795        assert!(stats.holes[0].below_threshold);
1796        assert!(!stats.holes[0].used_compressed_quad);
1797    }
1798
1799    fn i32_polygon_with_segments(segments: usize, scale: f64) -> Vec<I32Point> {
1800        let mut ring = Vec::with_capacity(segments + 1);
1801        for i in 0..segments {
1802            let theta = (i as f64) / (segments as f64) * std::f64::consts::TAU;
1803            ring.push(I32Point {
1804                x: (theta.cos() * scale).round() as i32,
1805                y: (theta.sin() * scale).round() as i32,
1806            });
1807        }
1808        ring.push(ring[0]);
1809        ring
1810    }
1811
1812    fn y_stripes_options() -> PolygonBuildOptions {
1813        PolygonBuildOptions {
1814            enable_rtree: false,
1815            enable_compressed_quad: false,
1816            enable_y_stripes: true,
1817            rtree_min_segments: 64,
1818        }
1819    }
1820
1821    #[test]
1822    fn i32_y_stripes_matches_linear_scan() {
1823        let scale = 1e5;
1824        let ring = i32_polygon_with_segments(256, scale);
1825        let hole = i32_polygon_with_segments(96, scale * 0.4);
1826
1827        let linear = I32Polygon::new_with_mode(
1828            ring.clone(),
1829            vec![hole.clone()],
1830            scale,
1831            I32RaycastMode::Float,
1832        );
1833        let indexed = I32Polygon::new_with_options(
1834            ring,
1835            vec![hole],
1836            scale,
1837            I32RaycastMode::Float,
1838            Some(y_stripes_options()),
1839        );
1840        assert!(indexed.index_stats().exterior.used_y_stripes);
1841        assert!(indexed.index_stats().holes[0].used_y_stripes);
1842
1843        // Sweep radii crossing the hole boundary, the fill ring and the
1844        // exterior boundary, including points snapped to the 1e-5 grid.
1845        for i in 0..64 {
1846            let theta = (i as f64) / 64.0 * std::f64::consts::TAU;
1847            for r in [0.0, 0.2, 0.39999, 0.4, 0.40001, 0.7, 0.99999, 1.0, 1.1] {
1848                let p = Point {
1849                    x: theta.cos() * r,
1850                    y: theta.sin() * r,
1851                };
1852                assert_eq!(
1853                    linear.contains_point(p),
1854                    indexed.contains_point(p),
1855                    "mismatch at {p:?}"
1856                );
1857                let snapped = Point {
1858                    x: (p.x * scale).round() / scale,
1859                    y: (p.y * scale).round() / scale,
1860                };
1861                assert_eq!(
1862                    linear.contains_point(snapped),
1863                    indexed.contains_point(snapped),
1864                    "mismatch at snapped {snapped:?}"
1865                );
1866            }
1867        }
1868    }
1869
1870    #[test]
1871    fn i32_y_stripes_matches_f64_y_stripes_away_from_boundary() {
1872        let scale = 1e5;
1873        let int_ring = i32_polygon_with_segments(256, scale);
1874        let float_ring: Vec<Point> = int_ring
1875            .iter()
1876            .map(|p| Point {
1877                x: f64::from(p.x) / scale,
1878                y: f64::from(p.y) / scale,
1879            })
1880            .collect();
1881
1882        let int_poly = I32Polygon::new_with_options(
1883            int_ring,
1884            vec![],
1885            scale,
1886            I32RaycastMode::Float,
1887            Some(y_stripes_options()),
1888        );
1889        let float_poly = Polygon::new(float_ring, vec![], Some(y_stripes_options()));
1890
1891        for i in 0..256 {
1892            let theta = (i as f64) / 256.0 * std::f64::consts::TAU;
1893            for r in [0.0, 0.3, 0.6, 0.9, 1.1, 1.5] {
1894                let p = Point {
1895                    x: theta.cos() * r,
1896                    y: theta.sin() * r,
1897                };
1898                assert_eq!(
1899                    float_poly.contains_point(p),
1900                    int_poly.contains_point(p),
1901                    "mismatch at {p:?}"
1902                );
1903            }
1904        }
1905    }
1906
1907    #[test]
1908    fn i32_y_stripes_candidates_match_scan() {
1909        let scale = 1e5;
1910        let ring = i32_polygon_with_segments(256, scale);
1911        let (index, stats) = build_ring_index(&ring, &y_stripes_options());
1912        let index = index.unwrap();
1913        assert!(stats.used_y_stripes);
1914
1915        let float_ring: Vec<Point> = ring
1916            .iter()
1917            .map(|p| Point {
1918                x: f64::from(p.x),
1919                y: f64::from(p.y),
1920            })
1921            .collect();
1922
1923        for y in [
1924            -1.2e5, -1e5, -0.75e5, -0.2e5, 0.0, 0.4e5, 0.99e5, 1e5, 1.2e5, 33333.0,
1925        ] {
1926            let mut from_index = Vec::new();
1927            index.search_candidates(&ring, y, &mut from_index);
1928            from_index.sort_unstable();
1929
1930            let mut from_scan = scan_candidates(&float_ring, y, index.x_min, index.x_max);
1931            from_scan.sort_unstable();
1932
1933            assert_eq!(from_index, from_scan, "candidate mismatch at y={y}");
1934        }
1935    }
1936
1937    #[test]
1938    fn polygon_index_stats_capture_y_stripes_metrics() {
1939        let indexed = Polygon::new(
1940            polygon_with_segments(256),
1941            vec![polygon_with_segments(32)],
1942            Some(PolygonBuildOptions {
1943                enable_rtree: false,
1944                enable_compressed_quad: false,
1945                enable_y_stripes: true,
1946                rtree_min_segments: 64,
1947            }),
1948        );
1949        let stats = indexed.index_stats();
1950        assert!(stats.exterior.used_y_stripes);
1951        assert!(!stats.exterior.below_threshold);
1952        assert_eq!(stats.exterior.segment_count, 256);
1953        assert!(stats.exterior.y_stripes.is_some());
1954        assert_eq!(stats.holes.len(), 1);
1955        assert!(stats.holes[0].below_threshold);
1956        assert!(!stats.holes[0].used_y_stripes);
1957        assert!(stats.holes[0].y_stripes.is_none());
1958    }
1959}