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    ///
1077    /// `allow_on_edge` only applies to the exterior ring. Hole rings are
1078    /// always tested with `allow_on_edge = false`, so a point lying on a
1079    /// hole's boundary counts as *not* in the hole, i.e. inside the polygon
1080    /// under both edge rules.
1081    fn contains_point_normal(&self, p: Point, allow_on_edge: bool) -> bool {
1082        if !rings_contains_point(
1083            &self.exterior,
1084            self.exterior_index.as_ref(),
1085            p,
1086            allow_on_edge,
1087        ) {
1088            return false;
1089        }
1090
1091        for (hole, hole_index) in self.holes.iter().zip(self.hole_indexes.iter()) {
1092            if rings_contains_point(hole, hole_index.as_ref(), p, false) {
1093                return false;
1094            }
1095        }
1096
1097        return true;
1098    }
1099
1100    /// Do point-in-polygon search. `p` is in unscaled (degree) coordinates.
1101    ///
1102    /// A point lying exactly on the exterior boundary is reported as
1103    /// **outside**. Use [`Polygon::contains_point_allow_on_edge`] when the
1104    /// boundary should belong to the polygon — for example when adjacent
1105    /// polygons tile a plane and a query on a shared edge must not fall
1106    /// through the crack between them.
1107    pub fn contains_point(&self, p: Point) -> bool {
1108        self.contains_point_with_edge_rule(p, false)
1109    }
1110
1111    /// Like [`Polygon::contains_point`], but a point lying exactly on the
1112    /// exterior boundary is reported as **inside**.
1113    ///
1114    /// For a set of polygons sharing borders this makes the boundary belong
1115    /// to every polygon that touches it, so no query can land in a gap. The
1116    /// flip side is that a shared edge matches more than one polygon; callers
1117    /// that need a single answer must break the tie themselves (e.g. by
1118    /// taking the first match in a stable iteration order).
1119    pub fn contains_point_allow_on_edge(&self, p: Point) -> bool {
1120        self.contains_point_with_edge_rule(p, true)
1121    }
1122
1123    fn contains_point_with_edge_rule(&self, p: Point, allow_on_edge: bool) -> bool {
1124        match self.raycast_mode {
1125            I32RaycastMode::Float => self.contains_point_float(p, allow_on_edge),
1126            I32RaycastMode::Integer => self.contains_point_integer(p, allow_on_edge),
1127        }
1128    }
1129
1130    fn contains_point_float(&self, p: Point, allow_on_edge: bool) -> bool {
1131        // `x * 1.0` is an exact identity under IEEE 754, so f64 polygons
1132        // (scale == 1.0) behave bit-identically to the pre-generic code.
1133        let scaled = Point {
1134            x: p.x * self.scale,
1135            y: p.y * self.scale,
1136        };
1137        // Same truth table as Rect::contains_point; the storage-type bounds
1138        // are converted in registers.
1139        if !(scaled.x >= self.min.x.to_f64()
1140            && scaled.x <= self.max.x.to_f64()
1141            && scaled.y >= self.min.y.to_f64()
1142            && scaled.y <= self.max.y.to_f64())
1143        {
1144            return false;
1145        }
1146
1147        return self.contains_point_normal(scaled, allow_on_edge);
1148    }
1149
1150    // Opt-in integer cross-product raycast; snaps the query to the storage
1151    // grid and ignores acceleration indexes.
1152    fn contains_point_integer(&self, p: Point, allow_on_edge: bool) -> bool {
1153        if !p.x.is_finite() || !p.y.is_finite() {
1154            return false;
1155        }
1156        let scaled = Point::<i64> {
1157            x: (p.x * self.scale).round() as i64,
1158            y: (p.y * self.scale).round() as i64,
1159        };
1160        if scaled.x < self.min.x.to_i64()
1161            || scaled.x > self.max.x.to_i64()
1162            || scaled.y < self.min.y.to_i64()
1163            || scaled.y > self.max.y.to_i64()
1164            || !ring_contains_point_integer(&self.exterior, scaled, allow_on_edge)
1165        {
1166            return false;
1167        }
1168        !self
1169            .holes
1170            .iter()
1171            .any(|ring| ring_contains_point_integer(ring, scaled, false))
1172    }
1173    pub fn exterior(&self) -> &[Point<T>] {
1174        &self.exterior
1175    }
1176
1177    pub fn holes(&self) -> &[Vec<Point<T>>] {
1178        &self.holes
1179    }
1180
1181    /// The polygon's bounding box in storage space (degrees for `f64`
1182    /// polygons, scaled integers for [`I32Polygon`]).
1183    pub fn rect(&self) -> Rect {
1184        Rect {
1185            min: Point {
1186                x: self.min.x.to_f64(),
1187                y: self.min.y.to_f64(),
1188            },
1189            max: Point {
1190                x: self.max.x.to_f64(),
1191                y: self.max.y.to_f64(),
1192            },
1193        }
1194    }
1195
1196    /// The factor a degree coordinate is multiplied by to reach storage
1197    /// space: `1.0` for `f64` polygons, e.g. `1e5` for [`I32Polygon`].
1198    pub fn scale(&self) -> f64 {
1199        self.scale
1200    }
1201
1202    pub fn options(&self) -> PolygonBuildOptions {
1203        self.options
1204    }
1205
1206    pub fn index_stats(&self) -> &PolygonIndexStats {
1207        &self.index_stats
1208    }
1209
1210    pub fn set_exterior(&mut self, exterior: Vec<Point<T>>) {
1211        self.exterior = exterior;
1212        self.rebuild_cache();
1213    }
1214
1215    pub fn set_holes(&mut self, holes: Vec<Vec<Point<T>>>) {
1216        self.holes = holes;
1217        self.rebuild_cache();
1218    }
1219
1220    pub fn set_options(&mut self, options: PolygonBuildOptions) {
1221        self.options = options;
1222        self.rebuild_cache();
1223    }
1224}
1225
1226impl Polygon {
1227    /// Create a new Polygon instance from exterior and holes.
1228    ///
1229    /// Example:
1230    ///
1231    /// ```rust
1232    /// use std::vec;
1233    /// use geometry_rs;
1234    /// let poly = geometry_rs::Polygon::new(
1235    ///     vec![
1236    ///         geometry_rs::Point {
1237    ///             x: 90.48826291293898,
1238    ///             y: 45.951129815858565,
1239    ///         },
1240    ///         geometry_rs::Point {
1241    ///             x: 90.48826291293898,
1242    ///             y: 27.99437617512571,
1243    ///         },
1244    ///         geometry_rs::Point {
1245    ///             x: 122.83201291294,
1246    ///             y: 27.99437617512571,
1247    ///         },
1248    ///         geometry_rs::Point {
1249    ///             x: 122.83201291294,
1250    ///             y: 45.951129815858565,
1251    ///         },
1252    ///         geometry_rs::Point {
1253    ///             x: 90.48826291293898,
1254    ///             y: 45.951129815858565,
1255    ///         },
1256    ///     ],
1257    ///     vec![],
1258    ///     None,
1259    /// );
1260    ///
1261    /// let p_out = geometry_rs::Point {
1262    ///     x: 130.74216916294148,
1263    ///     y: 37.649011392900306,
1264    /// };
1265    ///
1266    /// print!("{:?}\n", poly.contains_point(p_out));
1267    ///
1268    /// let p_in = geometry_rs::Point {
1269    ///     x: 99.9804504129416,
1270    ///     y: 39.70716466970461,
1271    /// };
1272    /// print!("{:?}\n", poly.contains_point(p_in));
1273    /// ```
1274    pub fn new(
1275        exterior: Vec<Point>,
1276        holes: Vec<Vec<Point>>,
1277        options: Option<PolygonBuildOptions>,
1278    ) -> Polygon {
1279        Self::build(exterior, holes, 1.0, I32RaycastMode::Float, options)
1280    }
1281}
1282
1283impl I32Polygon {
1284    /// Create a polygon from scaled integer coordinates with no acceleration
1285    /// index.
1286    ///
1287    /// (There is intentionally no `I32Polygon::new`: a second inherent `new`
1288    /// would make plain `Polygon::new(...)` calls ambiguous.)
1289    pub fn new_with_mode(
1290        exterior: Vec<I32Point>,
1291        holes: Vec<Vec<I32Point>>,
1292        scale: f64,
1293        raycast_mode: I32RaycastMode,
1294    ) -> Self {
1295        Self::new_with_options(exterior, holes, scale, raycast_mode, None)
1296    }
1297
1298    /// Create a polygon from scaled integer coordinates with explicit build
1299    /// options; the acceleration indexes operate directly in the scaled
1300    /// integer storage space. The indexes only serve the default (float)
1301    /// raycast; [`I32RaycastMode::Integer`] always scans segments linearly.
1302    pub fn new_with_options(
1303        exterior: Vec<I32Point>,
1304        holes: Vec<Vec<I32Point>>,
1305        scale: f64,
1306        raycast_mode: I32RaycastMode,
1307        options: Option<PolygonBuildOptions>,
1308    ) -> Self {
1309        assert!(!exterior.is_empty(), "polygon exterior must not be empty");
1310        assert!(
1311            scale.is_finite() && scale > 0.0,
1312            "scale must be positive and finite"
1313        );
1314        Self::build(exterior, holes, scale, raycast_mode, options)
1315    }
1316}
1317
1318impl<T: CoordStorage> ContainsPoint<Point> for Polygon<T> {
1319    fn contains_point(&self, point: Point) -> bool {
1320        Polygon::contains_point(self, point)
1321    }
1322}
1323
1324#[derive(Copy, Clone, Debug)]
1325pub struct Segment {
1326    pub a: Point,
1327    pub b: Point,
1328}
1329
1330impl Segment {
1331    pub fn rect(&self) -> Rect {
1332        let mut min_x: f64 = self.a.x;
1333        let mut min_y: f64 = self.a.y;
1334        let mut max_x: f64 = self.b.x;
1335        let mut max_y: f64 = self.b.y;
1336
1337        if min_x > max_x {
1338            let actual_min_x = max_x;
1339            let actual_max_x = min_x;
1340            min_x = actual_min_x;
1341            max_x = actual_max_x;
1342        }
1343
1344        if min_y > max_y {
1345            let actual_min_y = max_y;
1346            let actual_max_y = min_y;
1347            min_y = actual_min_y;
1348            max_y = actual_max_y;
1349        }
1350
1351        return Rect {
1352            min: Point { x: min_x, y: min_y },
1353            max: Point { x: max_x, y: max_y },
1354        };
1355    }
1356}
1357
1358pub struct RaycastResult {
1359    inside: bool, // point on the left
1360    on: bool,     // point is directly on top of
1361}
1362
1363pub fn raycast(seg: &Segment, point: Point) -> RaycastResult {
1364    let mut p = point;
1365    let a = seg.a;
1366    let b = seg.b;
1367
1368    // make sure that the point is inside the segment bounds
1369    if a.y < b.y && (p.y < a.y || p.y > b.y) {
1370        return RaycastResult {
1371            inside: false,
1372            on: false,
1373        };
1374    } else if a.y > b.y && (p.y < b.y || p.y > a.y) {
1375        return RaycastResult {
1376            inside: false,
1377            on: false,
1378        };
1379    }
1380
1381    // test if point is in on the segment
1382    if a.y == b.y {
1383        if a.x == b.x {
1384            if p.x == a.x && p.y == a.y {
1385                return RaycastResult {
1386                    inside: false,
1387                    on: true,
1388                };
1389            }
1390            return RaycastResult {
1391                inside: false,
1392                on: false,
1393            };
1394        }
1395        if p.y == b.y {
1396            // horizontal segment
1397            // check if the point in on the line
1398            if a.x < b.x {
1399                if p.x >= a.x && p.x <= b.x {
1400                    return RaycastResult {
1401                        inside: false,
1402                        on: true,
1403                    };
1404                }
1405            } else if p.x >= b.x && p.x <= a.x {
1406                return RaycastResult {
1407                    inside: false,
1408                    on: true,
1409                };
1410            }
1411        }
1412    }
1413    if a.x == b.x && p.x == b.x {
1414        // vertical segment
1415        // check if the point in on the line
1416        if a.y < b.y {
1417            if p.y >= a.y && p.y <= b.y {
1418                return RaycastResult {
1419                    inside: false,
1420                    on: true,
1421                };
1422            }
1423        } else if p.y >= b.y && p.y <= a.y {
1424            return RaycastResult {
1425                inside: false,
1426                on: true,
1427            };
1428        }
1429    }
1430    if (p.x - a.x) / (b.x - a.x) == (p.y - a.y) / (b.y - a.y) {
1431        return RaycastResult {
1432            inside: false,
1433            on: true,
1434        };
1435    }
1436
1437    // do the actual raycast here.
1438    while p.y == a.y || p.y == b.y {
1439        p.y = p.y.next_after(std::f64::INFINITY);
1440    }
1441
1442    if a.y < b.y {
1443        if p.y < a.y || p.y > b.y {
1444            return RaycastResult {
1445                inside: false,
1446                on: false,
1447            };
1448        }
1449    } else if p.y < b.y || p.y > a.y {
1450        return RaycastResult {
1451            inside: false,
1452            on: false,
1453        };
1454    }
1455    if a.x > b.x {
1456        if p.x >= a.x {
1457            return RaycastResult {
1458                inside: false,
1459                on: false,
1460            };
1461        }
1462        if p.x <= b.x {
1463            return RaycastResult {
1464                inside: true,
1465                on: false,
1466            };
1467        }
1468    } else {
1469        if p.x >= b.x {
1470            return RaycastResult {
1471                inside: false,
1472                on: false,
1473            };
1474        }
1475        if p.x <= a.x {
1476            return RaycastResult {
1477                inside: true,
1478                on: false,
1479            };
1480        }
1481    }
1482    if a.y < b.y {
1483        if (p.y - a.y) / (p.x - a.x) >= (b.y - a.y) / (b.x - a.x) {
1484            return RaycastResult {
1485                inside: true,
1486                on: false,
1487            };
1488        }
1489    } else if (p.y - b.y) / (p.x - b.x) >= (a.y - b.y) / (a.x - b.x) {
1490        return RaycastResult {
1491            inside: true,
1492            on: false,
1493        };
1494    }
1495    return RaycastResult {
1496        inside: false,
1497        on: false,
1498    };
1499}
1500
1501#[cfg(test)]
1502mod tests {
1503    use super::*;
1504
1505    fn square(min: f64, max: f64) -> Vec<Point> {
1506        vec![
1507            Point { x: min, y: min },
1508            Point { x: min, y: max },
1509            Point { x: max, y: max },
1510            Point { x: max, y: min },
1511            Point { x: min, y: min },
1512        ]
1513    }
1514
1515    fn polygon_with_segments(segments: usize) -> Vec<Point> {
1516        let mut ring = Vec::with_capacity(segments + 1);
1517        for i in 0..segments {
1518            let theta = (i as f64) / (segments as f64) * std::f64::consts::TAU;
1519            ring.push(Point {
1520                x: theta.cos(),
1521                y: theta.sin(),
1522            });
1523        }
1524        ring.push(ring[0]);
1525        ring
1526    }
1527
1528    fn scan_candidates(ring: &[Point], y: f64, x_min: f64, x_max: f64) -> Vec<usize> {
1529        let mut out = Vec::new();
1530        let query = Rect {
1531            min: Point { x: x_min, y },
1532            max: Point { x: x_max, y },
1533        };
1534        for (i, pair) in ring.windows(2).enumerate() {
1535            let seg = Segment {
1536                a: pair[0],
1537                b: pair[1],
1538            };
1539            if seg.rect().intersects_rect(query) {
1540                out.push(i);
1541            }
1542        }
1543        out
1544    }
1545
1546    #[test]
1547    fn rings_contains_point_allow_on_edge() {
1548        let ring = square(0.0, 10.0);
1549        let on_edge = Point { x: 0.0, y: 5.0 };
1550        assert!(rings_contains_point(&ring, None, on_edge, true));
1551        assert!(!rings_contains_point(&ring, None, on_edge, false));
1552    }
1553
1554    #[test]
1555    fn polygon_contains_basic_in_and_out() {
1556        let poly = Polygon::new(square(0.0, 10.0), vec![], None);
1557        assert!(poly.contains_point(Point { x: 5.0, y: 5.0 }));
1558        assert!(!poly.contains_point(Point { x: 20.0, y: 5.0 }));
1559    }
1560
1561    #[test]
1562    fn polygon_contains_with_hole() {
1563        let poly = Polygon::new(square(0.0, 10.0), vec![square(3.0, 7.0)], None);
1564        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1565        assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1566    }
1567
1568    fn shifted_square(min: f64, max: f64, dx: f64) -> Vec<Point> {
1569        square(min, max)
1570            .into_iter()
1571            .map(|p| Point {
1572                x: p.x + dx,
1573                y: p.y,
1574            })
1575            .collect()
1576    }
1577
1578    /// Two squares sharing the x == 10 border: under the default rule a query
1579    /// on that border belongs to neither, which is how a tiled polygon set
1580    /// grows gaps along its internal edges.
1581    #[test]
1582    fn polygon_allow_on_edge_closes_shared_border() {
1583        for options in [
1584            None,
1585            // Force the acceleration indexes on so the indexed candidate
1586            // scan takes the same code path as the linear one.
1587            Some(PolygonBuildOptions {
1588                enable_rtree: true,
1589                enable_compressed_quad: true,
1590                enable_y_stripes: false,
1591                rtree_min_segments: 1,
1592            }),
1593            Some(PolygonBuildOptions {
1594                enable_rtree: false,
1595                enable_compressed_quad: false,
1596                enable_y_stripes: true,
1597                rtree_min_segments: 1,
1598            }),
1599        ] {
1600            let left = Polygon::new(square(0.0, 10.0), vec![], options);
1601            let right = Polygon::new(shifted_square(0.0, 10.0, 10.0), vec![], options);
1602
1603            for p in [
1604                Point { x: 10.0, y: 5.0 },  // mid-edge
1605                Point { x: 10.0, y: 10.0 }, // shared vertex
1606                Point { x: 10.0, y: 0.0 },
1607            ] {
1608                assert!(!left.contains_point(p), "{p:?} {options:?}");
1609                assert!(!right.contains_point(p), "{p:?} {options:?}");
1610                assert!(left.contains_point_allow_on_edge(p), "{p:?} {options:?}");
1611                assert!(right.contains_point_allow_on_edge(p), "{p:?} {options:?}");
1612            }
1613
1614            // Points off the border are unaffected by the edge rule.
1615            for p in [
1616                Point { x: 5.0, y: 5.0 },
1617                Point { x: 15.0, y: 5.0 },
1618                Point { x: 25.0, y: 5.0 },
1619                Point { x: 10.0, y: 25.0 },
1620            ] {
1621                assert_eq!(
1622                    left.contains_point(p),
1623                    left.contains_point_allow_on_edge(p),
1624                    "{p:?} {options:?}"
1625                );
1626                assert_eq!(
1627                    right.contains_point(p),
1628                    right.contains_point_allow_on_edge(p),
1629                    "{p:?} {options:?}"
1630                );
1631            }
1632        }
1633    }
1634
1635    /// The edge rule applies to the exterior only: a hole's boundary counts as
1636    /// polygon interior either way, so the ring filling the hole and the ring
1637    /// around it both claim it rather than both dropping it.
1638    #[test]
1639    fn polygon_allow_on_edge_leaves_hole_boundary_alone() {
1640        let poly = Polygon::new(square(0.0, 10.0), vec![square(3.0, 7.0)], None);
1641        let fill = Polygon::new(square(3.0, 7.0), vec![], None);
1642
1643        for p in [Point { x: 3.0, y: 5.0 }, Point { x: 3.0, y: 3.0 }] {
1644            assert!(poly.contains_point(p), "{p:?}");
1645            assert!(poly.contains_point_allow_on_edge(p), "{p:?}");
1646            assert!(!fill.contains_point(p), "{p:?}");
1647            assert!(fill.contains_point_allow_on_edge(p), "{p:?}");
1648        }
1649
1650        // Inside the hole proper stays outside under both rules.
1651        let inner = Point { x: 5.0, y: 5.0 };
1652        assert!(!poly.contains_point(inner));
1653        assert!(!poly.contains_point_allow_on_edge(inner));
1654    }
1655
1656    #[test]
1657    fn i32_polygon_allow_on_edge_closes_shared_border() {
1658        let rect_i32 = |x_min, x_max| {
1659            vec![
1660                I32Point { x: x_min, y: 0 },
1661                I32Point {
1662                    x: x_min,
1663                    y: 1_000_000,
1664                },
1665                I32Point {
1666                    x: x_max,
1667                    y: 1_000_000,
1668                },
1669                I32Point { x: x_max, y: 0 },
1670                I32Point { x: x_min, y: 0 },
1671            ]
1672        };
1673        for mode in [I32RaycastMode::Float, I32RaycastMode::Integer] {
1674            let left = I32Polygon::new_with_mode(rect_i32(0, 750_000), vec![], 1e5, mode);
1675            let right = I32Polygon::new_with_mode(rect_i32(750_000, 1_500_000), vec![], 1e5, mode);
1676
1677            let on_edge = Point { x: 7.5, y: 5.0 };
1678            assert!(!left.contains_point(on_edge), "{mode:?}");
1679            assert!(!right.contains_point(on_edge), "{mode:?}");
1680            assert!(left.contains_point_allow_on_edge(on_edge), "{mode:?}");
1681            assert!(right.contains_point_allow_on_edge(on_edge), "{mode:?}");
1682
1683            let outside = Point { x: 20.0, y: 5.0 };
1684            assert!(!left.contains_point_allow_on_edge(outside), "{mode:?}");
1685            assert!(!right.contains_point_allow_on_edge(outside), "{mode:?}");
1686        }
1687    }
1688
1689    #[test]
1690    fn i32_polygon_contains_with_hole_and_fractional_query() {
1691        let square = |min, max| {
1692            vec![
1693                I32Point { x: min, y: min },
1694                I32Point { x: min, y: max },
1695                I32Point { x: max, y: max },
1696                I32Point { x: max, y: min },
1697                I32Point { x: min, y: min },
1698            ]
1699        };
1700        for mode in [I32RaycastMode::Float, I32RaycastMode::Integer] {
1701            let poly = I32Polygon::new_with_mode(
1702                square(0, 1_000_000),
1703                vec![square(400_000, 600_000)],
1704                1e5,
1705                mode,
1706            );
1707            assert!(poly.contains_point(Point {
1708                x: 1.23456,
1709                y: 2.34567
1710            }));
1711            assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1712            assert!(!poly.contains_point(Point { x: 11.0, y: 5.0 }));
1713        }
1714    }
1715
1716    #[test]
1717    fn indexed_and_non_indexed_results_match() {
1718        let ring = polygon_with_segments(128);
1719        let p_in = Point { x: 0.2, y: 0.1 };
1720        let p_out = Point { x: 2.0, y: 0.0 };
1721
1722        let p1 = Polygon::new(
1723            ring.clone(),
1724            vec![],
1725            Some(PolygonBuildOptions {
1726                enable_rtree: false,
1727                enable_compressed_quad: false,
1728                enable_y_stripes: false,
1729                rtree_min_segments: 64,
1730            }),
1731        );
1732
1733        let p2 = Polygon::new(
1734            ring,
1735            vec![],
1736            Some(PolygonBuildOptions {
1737                enable_rtree: true,
1738                enable_compressed_quad: true,
1739                enable_y_stripes: false,
1740                rtree_min_segments: 64,
1741            }),
1742        );
1743
1744        assert_eq!(p1.contains_point(p_in), p2.contains_point(p_in));
1745        assert_eq!(p1.contains_point(p_out), p2.contains_point(p_out));
1746    }
1747
1748    #[test]
1749    fn rtree_and_compressed_quad_and_both_match_baseline() {
1750        let ring = polygon_with_segments(160);
1751        let points = [
1752            Point { x: 0.2, y: 0.1 },
1753            Point { x: -0.4, y: -0.3 },
1754            Point { x: 1.2, y: 0.0 },
1755        ];
1756
1757        let base = Polygon::new(
1758            ring.clone(),
1759            vec![],
1760            Some(PolygonBuildOptions {
1761                enable_rtree: false,
1762                enable_compressed_quad: false,
1763                enable_y_stripes: false,
1764                rtree_min_segments: 64,
1765            }),
1766        );
1767        let only_rtree = Polygon::new(
1768            ring.clone(),
1769            vec![],
1770            Some(PolygonBuildOptions {
1771                enable_rtree: true,
1772                enable_compressed_quad: false,
1773                enable_y_stripes: false,
1774                rtree_min_segments: 64,
1775            }),
1776        );
1777        let only_compressed = Polygon::new(
1778            ring.clone(),
1779            vec![],
1780            Some(PolygonBuildOptions {
1781                enable_rtree: false,
1782                enable_compressed_quad: true,
1783                enable_y_stripes: false,
1784                rtree_min_segments: 64,
1785            }),
1786        );
1787        let both = Polygon::new(
1788            ring,
1789            vec![],
1790            Some(PolygonBuildOptions {
1791                enable_rtree: true,
1792                enable_compressed_quad: true,
1793                enable_y_stripes: false,
1794                rtree_min_segments: 64,
1795            }),
1796        );
1797
1798        for p in points {
1799            let expected = base.contains_point(p);
1800            assert_eq!(only_rtree.contains_point(p), expected);
1801            assert_eq!(only_compressed.contains_point(p), expected);
1802            assert_eq!(both.contains_point(p), expected);
1803        }
1804    }
1805
1806    #[test]
1807    fn threshold_boundaries_63_64_65_are_consistent() {
1808        let ring = polygon_with_segments(65);
1809        let p_in = Point { x: 0.2, y: 0.0 };
1810        let p_out = Point { x: 1.5, y: 0.0 };
1811
1812        let p63 = Polygon::new(
1813            ring.clone(),
1814            vec![],
1815            Some(PolygonBuildOptions {
1816                enable_rtree: true,
1817                enable_compressed_quad: true,
1818                enable_y_stripes: false,
1819                rtree_min_segments: 63,
1820            }),
1821        );
1822        let p64 = Polygon::new(
1823            ring.clone(),
1824            vec![],
1825            Some(PolygonBuildOptions {
1826                enable_rtree: true,
1827                enable_compressed_quad: true,
1828                enable_y_stripes: false,
1829                rtree_min_segments: 64,
1830            }),
1831        );
1832        let p65 = Polygon::new(
1833            ring,
1834            vec![],
1835            Some(PolygonBuildOptions {
1836                enable_rtree: true,
1837                enable_compressed_quad: true,
1838                enable_y_stripes: false,
1839                rtree_min_segments: 65,
1840            }),
1841        );
1842
1843        assert_eq!(p63.contains_point(p_in), p64.contains_point(p_in));
1844        assert_eq!(p64.contains_point(p_in), p65.contains_point(p_in));
1845
1846        assert_eq!(p63.contains_point(p_out), p64.contains_point(p_out));
1847        assert_eq!(p64.contains_point(p_out), p65.contains_point(p_out));
1848    }
1849
1850    #[test]
1851    fn setters_rebuild_cache_and_keep_correct_results() {
1852        let mut poly = Polygon::new(square(0.0, 10.0), vec![], None);
1853        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1854
1855        poly.set_exterior(square(20.0, 30.0));
1856        assert!(!poly.contains_point(Point { x: 1.0, y: 1.0 }));
1857        assert!(poly.contains_point(Point { x: 21.0, y: 21.0 }));
1858
1859        poly.set_holes(vec![square(22.0, 24.0)]);
1860        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1861
1862        poly.set_options(PolygonBuildOptions {
1863            enable_rtree: false,
1864            enable_compressed_quad: false,
1865            enable_y_stripes: false,
1866            rtree_min_segments: 64,
1867        });
1868        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1869        assert!(poly.contains_point(Point { x: 25.0, y: 25.0 }));
1870    }
1871
1872    #[test]
1873    fn compressed_quad_candidates_match_scan() {
1874        let ring = polygon_with_segments(256);
1875        let options = PolygonBuildOptions {
1876            enable_rtree: false,
1877            enable_compressed_quad: true,
1878            enable_y_stripes: false,
1879            rtree_min_segments: 64,
1880        };
1881        let (index, stats) = build_ring_index(&ring, &options);
1882        let index = index.unwrap();
1883
1884        assert!(stats.used_compressed_quad);
1885        assert!(!stats.below_threshold);
1886
1887        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1888            let mut from_index = Vec::new();
1889            index.search_candidates(&ring, y, &mut from_index);
1890            from_index.sort_unstable();
1891
1892            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1893            from_scan.sort_unstable();
1894
1895            assert_eq!(from_index, from_scan);
1896        }
1897    }
1898
1899    #[test]
1900    fn y_stripes_candidates_match_scan() {
1901        let ring = polygon_with_segments(256);
1902        let options = PolygonBuildOptions {
1903            enable_rtree: false,
1904            enable_compressed_quad: false,
1905            enable_y_stripes: true,
1906            rtree_min_segments: 64,
1907        };
1908        let (index, stats) = build_ring_index(&ring, &options);
1909        let index = index.unwrap();
1910
1911        assert!(stats.used_y_stripes);
1912        assert!(!stats.below_threshold);
1913        let y_stats = stats.y_stripes.as_ref().unwrap();
1914        assert_eq!(y_stats.segment_count, 256);
1915        assert!(y_stats.stripe_count >= 32);
1916        assert!(y_stats.assigned_item_count >= 256);
1917        assert!(y_stats.max_bucket_len > 0);
1918
1919        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1920            let mut from_index = Vec::new();
1921            index.search_candidates(&ring, y, &mut from_index);
1922            from_index.sort_unstable();
1923
1924            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1925            from_scan.sort_unstable();
1926
1927            assert_eq!(from_index, from_scan);
1928        }
1929    }
1930
1931    #[test]
1932    fn polygon_index_stats_capture_threshold_for_compressed_quad() {
1933        let indexed = Polygon::new(
1934            polygon_with_segments(256),
1935            vec![polygon_with_segments(32)],
1936            Some(PolygonBuildOptions {
1937                enable_rtree: false,
1938                enable_compressed_quad: true,
1939                enable_y_stripes: false,
1940                rtree_min_segments: 64,
1941            }),
1942        );
1943        let stats = indexed.index_stats();
1944        assert!(stats.exterior.used_compressed_quad);
1945        assert!(!stats.exterior.below_threshold);
1946        assert_eq!(stats.exterior.segment_count, 256);
1947        assert_eq!(stats.holes.len(), 1);
1948        assert!(stats.holes[0].below_threshold);
1949        assert!(!stats.holes[0].used_compressed_quad);
1950    }
1951
1952    fn i32_polygon_with_segments(segments: usize, scale: f64) -> Vec<I32Point> {
1953        let mut ring = Vec::with_capacity(segments + 1);
1954        for i in 0..segments {
1955            let theta = (i as f64) / (segments as f64) * std::f64::consts::TAU;
1956            ring.push(I32Point {
1957                x: (theta.cos() * scale).round() as i32,
1958                y: (theta.sin() * scale).round() as i32,
1959            });
1960        }
1961        ring.push(ring[0]);
1962        ring
1963    }
1964
1965    fn y_stripes_options() -> PolygonBuildOptions {
1966        PolygonBuildOptions {
1967            enable_rtree: false,
1968            enable_compressed_quad: false,
1969            enable_y_stripes: true,
1970            rtree_min_segments: 64,
1971        }
1972    }
1973
1974    #[test]
1975    fn i32_y_stripes_matches_linear_scan() {
1976        let scale = 1e5;
1977        let ring = i32_polygon_with_segments(256, scale);
1978        let hole = i32_polygon_with_segments(96, scale * 0.4);
1979
1980        let linear = I32Polygon::new_with_mode(
1981            ring.clone(),
1982            vec![hole.clone()],
1983            scale,
1984            I32RaycastMode::Float,
1985        );
1986        let indexed = I32Polygon::new_with_options(
1987            ring,
1988            vec![hole],
1989            scale,
1990            I32RaycastMode::Float,
1991            Some(y_stripes_options()),
1992        );
1993        assert!(indexed.index_stats().exterior.used_y_stripes);
1994        assert!(indexed.index_stats().holes[0].used_y_stripes);
1995
1996        // Sweep radii crossing the hole boundary, the fill ring and the
1997        // exterior boundary, including points snapped to the 1e-5 grid.
1998        for i in 0..64 {
1999            let theta = (i as f64) / 64.0 * std::f64::consts::TAU;
2000            for r in [0.0, 0.2, 0.39999, 0.4, 0.40001, 0.7, 0.99999, 1.0, 1.1] {
2001                let p = Point {
2002                    x: theta.cos() * r,
2003                    y: theta.sin() * r,
2004                };
2005                assert_eq!(
2006                    linear.contains_point(p),
2007                    indexed.contains_point(p),
2008                    "mismatch at {p:?}"
2009                );
2010                let snapped = Point {
2011                    x: (p.x * scale).round() / scale,
2012                    y: (p.y * scale).round() / scale,
2013                };
2014                assert_eq!(
2015                    linear.contains_point(snapped),
2016                    indexed.contains_point(snapped),
2017                    "mismatch at snapped {snapped:?}"
2018                );
2019            }
2020        }
2021    }
2022
2023    #[test]
2024    fn i32_y_stripes_matches_f64_y_stripes_away_from_boundary() {
2025        let scale = 1e5;
2026        let int_ring = i32_polygon_with_segments(256, scale);
2027        let float_ring: Vec<Point> = int_ring
2028            .iter()
2029            .map(|p| Point {
2030                x: f64::from(p.x) / scale,
2031                y: f64::from(p.y) / scale,
2032            })
2033            .collect();
2034
2035        let int_poly = I32Polygon::new_with_options(
2036            int_ring,
2037            vec![],
2038            scale,
2039            I32RaycastMode::Float,
2040            Some(y_stripes_options()),
2041        );
2042        let float_poly = Polygon::new(float_ring, vec![], Some(y_stripes_options()));
2043
2044        for i in 0..256 {
2045            let theta = (i as f64) / 256.0 * std::f64::consts::TAU;
2046            for r in [0.0, 0.3, 0.6, 0.9, 1.1, 1.5] {
2047                let p = Point {
2048                    x: theta.cos() * r,
2049                    y: theta.sin() * r,
2050                };
2051                assert_eq!(
2052                    float_poly.contains_point(p),
2053                    int_poly.contains_point(p),
2054                    "mismatch at {p:?}"
2055                );
2056            }
2057        }
2058    }
2059
2060    #[test]
2061    fn i32_y_stripes_candidates_match_scan() {
2062        let scale = 1e5;
2063        let ring = i32_polygon_with_segments(256, scale);
2064        let (index, stats) = build_ring_index(&ring, &y_stripes_options());
2065        let index = index.unwrap();
2066        assert!(stats.used_y_stripes);
2067
2068        let float_ring: Vec<Point> = ring
2069            .iter()
2070            .map(|p| Point {
2071                x: f64::from(p.x),
2072                y: f64::from(p.y),
2073            })
2074            .collect();
2075
2076        for y in [
2077            -1.2e5, -1e5, -0.75e5, -0.2e5, 0.0, 0.4e5, 0.99e5, 1e5, 1.2e5, 33333.0,
2078        ] {
2079            let mut from_index = Vec::new();
2080            index.search_candidates(&ring, y, &mut from_index);
2081            from_index.sort_unstable();
2082
2083            let mut from_scan = scan_candidates(&float_ring, y, index.x_min, index.x_max);
2084            from_scan.sort_unstable();
2085
2086            assert_eq!(from_index, from_scan, "candidate mismatch at y={y}");
2087        }
2088    }
2089
2090    #[test]
2091    fn polygon_index_stats_capture_y_stripes_metrics() {
2092        let indexed = Polygon::new(
2093            polygon_with_segments(256),
2094            vec![polygon_with_segments(32)],
2095            Some(PolygonBuildOptions {
2096                enable_rtree: false,
2097                enable_compressed_quad: false,
2098                enable_y_stripes: true,
2099                rtree_min_segments: 64,
2100            }),
2101        );
2102        let stats = indexed.index_stats();
2103        assert!(stats.exterior.used_y_stripes);
2104        assert!(!stats.exterior.below_threshold);
2105        assert_eq!(stats.exterior.segment_count, 256);
2106        assert!(stats.exterior.y_stripes.is_some());
2107        assert_eq!(stats.holes.len(), 1);
2108        assert!(stats.holes[0].below_threshold);
2109        assert!(!stats.holes[0].used_y_stripes);
2110        assert!(stats.holes[0].y_stripes.is_none());
2111    }
2112}