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
14pub type I32Point = Point<i32>;
16
17pub trait CoordStorage: Copy + PartialOrd {
27 fn to_f64(self) -> f64;
30 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
70pub 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(), }
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 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 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 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 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
654fn 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 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
984pub struct Polygon<T: CoordStorage = f64> {
992 exterior: Vec<Point<T>>,
993 holes: Vec<Vec<Point<T>>>,
994 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 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 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 let scaled = Point {
1102 x: p.x * self.scale,
1103 y: p.y * self.scale,
1104 };
1105 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 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 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 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 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 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 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, on: bool, }
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 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 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 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 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 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 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}