1use core::cmp::Ordering;
24use core::ops::Range;
25
26use geo_types::Coord;
27
28use crate::TileError;
29use crate::clip_polygon::{
30 ClipScratch, RingClip, clip_ring, close_ring, fill_box, keep_edge, push_distinct, push_fill_box,
31};
32use crate::clip_polyline::to_local;
33use crate::geom::{crossing_x_ceil, ring_orientation};
34use crate::grid::{Grid, Seg};
35use crate::slicer::Local;
36use crate::tile::TileId;
37use crate::vertex::{PolyVertex, Vertex};
38
39const CHUNK: usize = 32;
41
42const LONG_EDGE: i64 = 4;
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54#[non_exhaustive]
55pub struct FillRun {
56 pub y: i32,
58 pub x: Range<i32>,
60 orient: Ordering,
62}
63
64#[derive(Debug)]
66pub enum PolygonSlice<'a, V, R> {
67 Piece(PolygonPiece<'a, V, R>),
69 Run(FillRun),
71}
72
73#[derive(Debug)]
75pub struct PolygonPiece<'a, V, R> {
76 tile: TileId,
77 rings: PieceRings<'a, V, R>,
78}
79
80#[derive(Debug)]
81enum PieceRings<'a, V, R> {
82 Built {
85 verts: &'a [V],
86 ends: &'a [u32],
87 origin: Coord<i32>,
88 },
89 Whole {
92 rings: &'a [RingInfo],
93 input: &'a [R],
94 copies: &'a [V],
95 origin: Coord<i32>,
96 },
97}
98
99impl<V, R> Clone for PieceRings<'_, V, R> {
101 fn clone(&self) -> Self {
102 *self
103 }
104}
105impl<V, R> Copy for PieceRings<'_, V, R> {}
106impl<V, R> Clone for PolygonPiece<'_, V, R> {
107 fn clone(&self) -> Self {
108 *self
109 }
110}
111impl<V, R> Copy for PolygonPiece<'_, V, R> {}
112
113impl<'a, V: Vertex, R: AsRef<[V]>> PolygonPiece<'a, V, R> {
114 #[must_use]
116 pub fn tile(&self) -> TileId {
117 self.tile
118 }
119
120 pub fn rings(&self) -> impl Iterator<Item = Local<'a, V>> + use<'a, V, R> {
122 let rings = self.rings;
123 let n = match rings {
124 PieceRings::Built { ends, .. } => ends.len(),
125 PieceRings::Whole { rings, .. } => rings.len(),
126 };
127 (0..n).map(move |i| match rings {
128 PieceRings::Built {
129 verts,
130 ends,
131 origin,
132 } => {
133 let start = i.checked_sub(1).map_or(0, |p| ends[p] as usize);
134 Local::ring(&verts[start..ends[i] as usize], origin, true)
135 }
136 PieceRings::Whole {
137 rings,
138 input,
139 copies,
140 origin,
141 } => Local::ring(rings[i].pts(input, copies), origin, false),
142 })
143 }
144}
145
146#[derive(Debug, Clone, Copy)]
148enum Src {
149 Input,
151 Copy(usize),
153}
154
155#[derive(Debug, Clone, Copy)]
159struct RingInfo {
160 input: u32,
161 src: Src,
162 len: u32,
163 edge0: u32,
164}
165
166impl RingInfo {
167 fn pts<'a, V, R: AsRef<[V]>>(&self, input: &'a [R], copies: &'a [V]) -> &'a [V] {
168 let len = self.len as usize;
169 match self.src {
170 Src::Input => &input[self.input as usize].as_ref()[..len],
171 Src::Copy(start) => &copies[start..start + len],
172 }
173 }
174}
175
176#[derive(Debug, Clone, Copy)]
178struct Chunk {
179 ring: u32,
180 first: u32,
181 count: u32,
182 rows: (i64, i64),
183}
184
185#[derive(Debug, Clone, Copy)]
188struct Hit {
189 tx: i32,
190 edge: u32,
191 ring: u32,
192}
193
194#[derive(Debug, Clone, Copy)]
197struct Span {
198 first: i32,
199 last: i32,
200 edge: u32,
201 ring: u32,
202}
203
204#[derive(Debug, Clone, Copy)]
208struct Crossing {
209 edge: u32,
210 ring: u32,
211 up: bool,
212 right_of: i64,
213}
214
215impl Crossing {
216 fn sign(self) -> i64 {
217 if self.up { 1 } else { -1 }
218 }
219}
220
221fn row_center(ty: i64, extent: i64) -> i64 {
225 ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
226}
227
228fn fenwick_build(tree: &mut Vec<i64>, values: impl Iterator<Item = i64>) {
230 tree.clear();
231 tree.extend(values);
232 for i in 0..tree.len() {
233 let parent = i | (i + 1);
234 if parent < tree.len() {
235 tree[parent] += tree[i];
236 }
237 }
238}
239
240fn fenwick_add(tree: &mut [i64], mut i: usize, delta: i64) {
242 while i < tree.len() {
243 tree[i] += delta;
244 i |= i + 1;
245 }
246}
247
248fn fenwick_prefix(tree: &[i64], mut end: usize) -> i64 {
250 let mut sum = 0;
251 while end > 0 {
252 sum += tree[end - 1];
253 end &= end - 1;
254 }
255 sum
256}
257
258fn sort_hits(hits: &mut [Hit], tmp: &mut Vec<Hit>, counts: &mut Vec<usize>) {
262 let Some(head) = hits.first() else {
263 return;
264 };
265 let (lo, hi) = hits.iter().fold((head.tx, head.tx), |(lo, hi), h| {
266 (lo.min(h.tx), hi.max(h.tx))
267 });
268 let columns = hi.abs_diff(lo) as usize + 1;
269 if columns == 1 {
270 } else if hits.len() < 16 || columns > hits.len().saturating_mul(4) {
272 hits.sort_unstable_by_key(|h| (h.tx, h.edge));
273 } else {
274 counts.clear();
275 counts.resize(columns, 0);
276 for h in hits.iter() {
277 counts[h.tx.abs_diff(lo) as usize] += 1;
278 }
279 let mut start = 0;
281 for c in counts.iter_mut() {
282 (*c, start) = (start, start + *c);
283 }
284 tmp.clear();
285 tmp.extend_from_slice(hits);
286 for h in tmp.iter() {
287 let at = &mut counts[h.tx.abs_diff(lo) as usize];
288 hits[*at] = *h;
289 *at += 1;
290 }
291 }
292 debug_assert!(hits.is_sorted_by_key(|h| (h.tx, h.edge)));
293}
294
295fn check_local<V: Vertex>(verts: &[V], origin: Coord<i32>) -> Result<(), TileError> {
297 for &v in verts {
298 to_local(v, origin)?;
299 }
300 Ok(())
301}
302
303fn merge_by_edge<T: Copy>(
307 spans: &mut Vec<T>,
308 kept: usize,
309 tmp: &mut Vec<T>,
310 edge: impl Fn(&T) -> u32,
311) {
312 let added = &mut spans[kept..];
313 if !added.is_sorted_by_key(&edge) {
314 added.sort_unstable_by_key(&edge);
315 }
316 if kept == 0 || kept == spans.len() || edge(&spans[kept - 1]) < edge(&spans[kept]) {
317 return;
318 }
319 tmp.clear();
320 let (old, new) = spans.split_at(kept);
321 let (mut i, mut j) = (0, 0);
322 while i < old.len() && j < new.len() {
323 if edge(&old[i]) < edge(&new[j]) {
324 tmp.push(old[i]);
325 i += 1;
326 } else {
327 tmp.push(new[j]);
328 j += 1;
329 }
330 }
331 tmp.extend_from_slice(&old[i..]);
332 tmp.extend_from_slice(&new[j..]);
333 core::mem::swap(spans, tmp);
334}
335
336fn ring_groups(mut hits: &[Hit]) -> impl Iterator<Item = &[Hit]> {
339 core::iter::from_fn(move || {
340 let ring = hits.first()?.ring;
341 let (group, rest) = hits.split_at(hits.partition_point(|x| x.ring == ring));
342 hits = rest;
343 Some(group)
344 })
345}
346
347fn excursion_winding(
350 cs: &[Crossing],
351 fenwick: &[i64],
352 ring: RingInfo,
353 first: usize,
354 count: usize,
355) -> i64 {
356 let (start, len) = (ring.edge0 as usize, ring.len as usize);
357 let sum = |lo: usize, hi: usize| {
358 let i = cs.partition_point(|c| (c.edge as usize) < lo);
359 let j = cs.partition_point(|c| (c.edge as usize) < hi);
360 fenwick_prefix(fenwick, j) - fenwick_prefix(fenwick, i)
361 };
362 if first + count <= len {
363 sum(start + first, start + first + count)
364 } else {
365 sum(start + first, start + len) + sum(start, start + first + count - len)
366 }
367}
368
369pub(crate) struct PolyScratch<V> {
372 rings: Vec<RingInfo>,
374 copies: Vec<V>,
375 chunks: Vec<Chunk>,
376 order: Vec<u32>,
378 active: Vec<u32>,
379 hits: Vec<Hit>,
384 long: Vec<Span>,
385 long_cover: Vec<Span>,
386 covering: Vec<Hit>,
387 crossings: Vec<Crossing>,
388 by_x: Vec<u32>,
389 hits_tmp: Vec<Hit>,
391 spans_tmp: Vec<Span>,
392 counts: Vec<usize>,
393 fenwick: Vec<i64>,
394 inside: Vec<bool>,
396 verts: Vec<V>,
398 ring_ends: Vec<u32>,
399 arcs: Vec<(usize, usize)>,
400 clip: ClipScratch<V>,
401}
402
403impl<V> PolyScratch<V> {
404 pub(crate) const fn new() -> Self {
405 Self {
406 rings: Vec::new(),
407 copies: Vec::new(),
408 chunks: Vec::new(),
409 order: Vec::new(),
410 active: Vec::new(),
411 hits: Vec::new(),
412 long: Vec::new(),
413 long_cover: Vec::new(),
414 covering: Vec::new(),
415 crossings: Vec::new(),
416 by_x: Vec::new(),
417 hits_tmp: Vec::new(),
418 spans_tmp: Vec::new(),
419 counts: Vec::new(),
420 fenwick: Vec::new(),
421 inside: Vec::new(),
422 verts: Vec::new(),
423 ring_ends: Vec::new(),
424 arcs: Vec::new(),
425 clip: ClipScratch::new(),
426 }
427 }
428}
429
430pub(crate) struct Scratch<V>(Option<Box<PolyState<V>>>);
434
435pub(crate) struct PolyState<V> {
438 pub(crate) scratch: PolyScratch<V>,
439 sweep: Option<Sweep>,
440}
441
442impl<V> Scratch<V> {
443 pub(crate) const fn new() -> Self {
444 Self(None)
445 }
446
447 #[inline]
449 pub(crate) fn get(&mut self) -> &mut PolyState<V> {
450 #[cold]
451 fn alloc<V>() -> Box<PolyState<V>> {
452 Box::new(PolyState {
453 scratch: PolyScratch::new(),
454 sweep: None,
455 })
456 }
457 self.0.get_or_insert_with(alloc)
458 }
459}
460
461impl<V> Clone for Scratch<V> {
462 fn clone(&self) -> Self {
463 Self::new()
464 }
465}
466
467impl<V> PartialEq for Scratch<V> {
468 fn eq(&self, _: &Self) -> bool {
469 true
470 }
471}
472
473impl<V> Eq for Scratch<V> {}
474
475impl<V> core::fmt::Debug for Scratch<V> {
476 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
477 f.debug_struct("PolyScratch").finish_non_exhaustive()
478 }
479}
480
481pub(crate) fn fill_ring<V: PolyVertex>(grid: Grid, run: &FillRun) -> [V; 5] {
485 let (extent, buffer) = (grid.extent_i32(), grid.buffer_i32());
486 let sw = Coord {
487 x: -buffer - 1,
488 y: -buffer - 1,
489 };
490 let ne = Coord {
491 x: extent + buffer,
492 y: extent + buffer,
493 };
494 fill_box(run.orient, sw, ne).map(V::synthetic_at)
495}
496
497enum Event {
499 Whole {
501 tile: TileId,
502 origin: Coord<i32>,
503 },
504 Built {
506 tile: TileId,
507 origin: Coord<i32>,
508 },
509 Run(FillRun),
510}
511
512struct Sweep {
514 row: i64,
516 last_row: i64,
517 next_chunk: usize,
519 in_row: bool,
522 ty: i32,
523 k: usize,
524 h: usize,
525 lh: usize,
526 next: i64,
527 cover: Option<Range<usize>>,
530 long_last: i64,
532 holes_in: u32,
534 pending: Option<(i64, i64)>,
536 ready: Option<FillRun>,
539 tile_ready: Option<i32>,
540 orient: Option<Ordering>,
542}
543
544pub struct PolygonSlices<'s, V, R> {
550 grid: Grid,
551 rings: &'s [R],
552 s: &'s mut PolyScratch<V>,
553 start: Option<Start>,
555 sweep: &'s mut Option<Sweep>,
556 done: bool,
557}
558
559enum Start {
562 Whole { tile: TileId, origin: Coord<i32> },
564 Sweep { row: i64, last_row: i64 },
566}
567
568impl<'s, V: PolyVertex, R: AsRef<[V]>> PolygonSlices<'s, V, R> {
569 #[cfg_attr(feature = "hotpath", hotpath::measure)]
571 #[inline]
572 pub(crate) fn new(
573 grid: Grid,
574 rings: &'s [R],
575 state: &'s mut PolyState<V>,
576 ) -> Result<Self, TileError> {
577 let PolyState { scratch: s, sweep } = state;
578 *sweep = None;
579 let start = prepare(grid, rings, s)?;
580 Ok(Self {
581 grid,
582 rings,
583 s,
584 start,
585 sweep,
586 done: false,
587 })
588 }
589
590 #[expect(
594 clippy::should_implement_trait,
595 reason = "a lending iterator: each item borrows the iterator"
596 )]
597 pub fn next(&mut self) -> Option<Result<PolygonSlice<'_, V, R>, TileError>> {
598 if self.done {
599 return None;
600 }
601 match self.step() {
602 Ok(Some(event)) => Some(Ok(self.view(event))),
603 Ok(None) => {
604 self.done = true;
605 None
606 }
607 Err(e) => {
608 self.done = true;
609 Some(Err(e))
610 }
611 }
612 }
613
614 #[must_use]
620 pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
621 fill_ring(self.grid, run)
622 }
623
624 fn view(&self, event: Event) -> PolygonSlice<'_, V, R> {
625 let (tile, rings) = match event {
626 Event::Run(run) => return PolygonSlice::Run(run),
627 Event::Built { tile, origin } => (
628 tile,
629 PieceRings::Built {
630 verts: &self.s.verts,
631 ends: &self.s.ring_ends,
632 origin,
633 },
634 ),
635 Event::Whole { tile, origin } => (
636 tile,
637 PieceRings::Whole {
638 rings: &self.s.rings,
639 input: self.rings,
640 copies: &self.s.copies,
641 origin,
642 },
643 ),
644 };
645 PolygonSlice::Piece(PolygonPiece { tile, rings })
646 }
647
648 fn step(&mut self) -> Result<Option<Event>, TileError> {
649 match self.start.take() {
650 Some(Start::Whole { tile, origin }) => return Ok(Some(Event::Whole { tile, origin })),
651 Some(Start::Sweep { row, last_row }) => *self.sweep = Some(Sweep::new(row, last_row)),
652 None => {}
653 }
654 let Some(sw) = self.sweep.as_mut() else {
655 return Ok(None);
656 };
657 let mut cx = Cx {
658 grid: self.grid,
659 s: &mut *self.s,
660 rings: self.rings,
661 };
662 sw.step(&mut cx)
663 }
664}
665
666fn prepare<V: PolyVertex, R: AsRef<[V]>>(
670 grid: Grid,
671 rings: &[R],
672 s: &mut PolyScratch<V>,
673) -> Result<Option<Start>, TileError> {
674 let (extent, buffer) = (i64::from(grid.extent()), i64::from(grid.buffer()));
675 s.rings.clear();
676 s.copies.clear();
677 u32::try_from(rings.len()).map_err(|_| TileError::GeometryTooLarge)?;
678 let mut bounds: Option<(Coord<i32>, Coord<i32>)> = None;
679 let mut edges = 0_u32;
680 for (index, ring) in (0_u32..).zip(rings) {
681 let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), edges)? else {
682 if index == 0 {
683 return Ok(None); }
685 continue;
686 };
687 edges = edges
688 .checked_add(info.len)
689 .ok_or(TileError::GeometryTooLarge)?;
690 s.rings.push(info);
691 bounds = Some(bounds.map_or((lo, hi), |(l, h)| {
692 (
693 Coord {
694 x: l.x.min(lo.x),
695 y: l.y.min(lo.y),
696 },
697 Coord {
698 x: h.x.max(hi.x),
699 y: h.y.max(hi.y),
700 },
701 )
702 }));
703 }
704 let Some((lo, hi)) = bounds else {
705 return Ok(None);
706 };
707
708 if let Some((tile, origin)) = grid.inner_tile(lo, hi) {
711 return Ok(Some(Start::Whole { tile, origin }));
712 }
713 grid.check_polygon_bounds(lo, hi)?;
714
715 s.chunks.clear();
716 index_chunks(s, rings, extent, buffer);
717 s.order.clear();
718 #[expect(
719 clippy::cast_possible_truncation,
720 reason = "at most one chunk per edge"
721 )]
722 s.order.extend(0..s.chunks.len() as u32);
723 let chunks = &s.chunks;
724 s.order.sort_unstable_by_key(|&c| chunks[c as usize].rows.0);
725 s.active.clear();
726 s.inside.clear();
727 s.inside.resize(s.rings.len(), false);
728 Ok(Some(Start::Sweep {
729 row: (i64::from(lo.y) - buffer).div_euclid(extent),
730 last_row: (i64::from(hi.y) + buffer).div_euclid(extent),
731 }))
732}
733
734struct Cx<'x, V, R> {
736 grid: Grid,
737 s: &'x mut PolyScratch<V>,
738 rings: &'x [R],
739}
740
741impl Sweep {
742 fn new(row: i64, last_row: i64) -> Self {
744 Self {
745 row,
746 last_row,
747 next_chunk: 0,
748 in_row: false,
749 ty: 0,
750 k: 0,
751 h: 0,
752 next: i64::MIN,
753 holes_in: 0,
754 pending: None,
755 ready: None,
756 tile_ready: None,
757 orient: None,
758 lh: 0,
759 cover: Some(0..0),
760 long_last: i64::MIN,
761 }
762 }
763
764 fn step<V: PolyVertex, R: AsRef<[V]>>(
766 &mut self,
767 cx: &mut Cx<'_, V, R>,
768 ) -> Result<Option<Event>, TileError> {
769 loop {
770 if let Some(run) = self.ready.take() {
771 return Ok(Some(Event::Run(run)));
772 }
773 if let Some(tx) = self.tile_ready.take() {
774 let tile = TileId::new(tx, self.ty);
775 if let Some(origin) = self.build_tile(cx, tile)? {
776 return Ok(Some(Event::Built { tile, origin }));
777 }
778 continue;
779 }
780 if !self.in_row {
781 if self.row > self.last_row {
782 return Ok(None);
783 }
784 self.load_row(cx)?;
785 self.ty = i32::try_from(self.row).map_err(|_| TileError::Overflow)?;
786 (self.in_row, self.k, self.h, self.lh, self.next) = (true, 0, 0, 0, i64::MIN);
787 (self.cover, self.long_last) = (Some(0..0), i64::MIN);
788 continue;
789 }
790 let s = &*cx.s;
792 let bound = s
793 .by_x
794 .get(self.k)
795 .map_or(i64::MAX, |&i| s.crossings[i as usize].right_of);
796 let tile = if s.long.is_empty() {
798 s.hits.get(self.h).map(|c| i64::from(c.tx))
799 } else {
800 let next = self.next;
803 let covered =
805 (self.long_last != i64::MIN && self.long_last >= next).then_some(next);
806 let short = s.hits.get(self.h).map(|c| i64::from(c.tx));
807 let long = s.long.get(self.lh).map(|c| i64::from(c.first).max(next));
808 [covered, short, long].into_iter().flatten().min()
809 };
810 if let Some(tx) = tile
811 && tx <= bound
812 {
813 let tx = i32::try_from(tx).map_err(|_| TileError::Overflow)?;
814 let s = &mut *cx.s;
815 let start = self.h;
817 self.h += s.hits[start..].partition_point(|c| c.tx == tx);
818 self.cover = Some(start..self.h);
819 if !s.long.is_empty() {
820 self.cover_long(s, tx, start);
821 }
822 self.fill(cx, self.next, i64::from(tx))?;
823 self.ready = self.take_pending();
825 self.tile_ready = Some(tx);
826 self.next = i64::from(tx) + 1;
827 continue;
828 }
829 if self.k == s.by_x.len() {
830 debug_assert!(cx.s.inside.iter().all(|&i| !i), "parity ends even");
832 self.ready = self.take_pending();
833 (self.in_row, self.row) = (false, self.row + 1);
834 continue;
835 }
836 self.fill(cx, self.next, bound + 1)?;
838 self.next = bound + 1;
839 let s = &mut *cx.s;
841 while let Some(&i) = s.by_x.get(self.k)
842 && s.crossings[i as usize].right_of == bound
843 {
844 let crossing = s.crossings[i as usize];
845 let r = crossing.ring as usize;
846 s.inside[r] = !s.inside[r];
847 if r != 0 {
848 if s.inside[r] {
849 self.holes_in += 1;
850 } else {
851 self.holes_in = self.holes_in.saturating_sub(1);
854 }
855 }
856 fenwick_add(&mut s.fenwick, i as usize, -crossing.sign());
857 self.k += 1;
858 }
859 if !(s.inside[0] && self.holes_in == 0) {
861 self.ready = self.take_pending();
862 }
863 }
864 }
865
866 fn cover_long<V>(&mut self, s: &mut PolyScratch<V>, tx: i32, start: usize) {
869 if self.long_last < i64::from(tx) {
870 s.long_cover.clear();
871 } else {
872 s.long_cover.retain(|c| c.last >= tx);
873 }
874 let kept = s.long_cover.len();
875 while let Some(&span) = s.long.get(self.lh)
876 && span.first <= tx
877 {
878 s.long_cover.push(span);
879 self.lh += 1;
880 }
881 if s.long_cover.is_empty() {
882 self.long_last = i64::MIN;
883 return;
884 }
885 merge_by_edge(&mut s.long_cover, kept, &mut s.spans_tmp, |c| c.edge);
886 self.long_last = s
887 .long_cover
888 .iter()
889 .map(|c| i64::from(c.last))
890 .max()
891 .unwrap_or(i64::MIN);
892 s.covering.clear();
893 s.covering.extend(s.long_cover.iter().map(|c| Hit {
894 tx,
895 edge: c.edge,
896 ring: c.ring,
897 }));
898 let kept = s.covering.len();
899 s.covering.extend_from_slice(&s.hits[start..self.h]);
900 merge_by_edge(&mut s.covering, kept, &mut s.hits_tmp, |c| c.edge);
901 self.cover = None;
902 }
903
904 fn take_pending(&mut self) -> Option<FillRun> {
906 let (start, end) = self.pending.take()?;
907 #[expect(
908 clippy::cast_possible_truncation,
909 reason = "both ends were checked to fit i32 when the span was recorded"
910 )]
911 Some(FillRun {
912 y: self.ty,
913 x: start as i32..end as i32,
914 orient: self.orient.unwrap_or(Ordering::Equal),
915 })
916 }
917
918 fn fill<V: PolyVertex, R: AsRef<[V]>>(
921 &mut self,
922 cx: &mut Cx<'_, V, R>,
923 start: i64,
924 end: i64,
925 ) -> Result<(), TileError> {
926 let covered = cx.s.inside.first().copied().unwrap_or(false) && self.holes_in == 0;
927 if start >= end || !covered {
928 return Ok(());
929 }
930 i32::try_from(start).map_err(|_| TileError::Overflow)?;
931 i32::try_from(end).map_err(|_| TileError::Overflow)?;
932 if self.orient.is_none() {
933 let ext = cx.s.rings[0].pts(cx.rings, &cx.s.copies);
934 self.orient = Some(ring_orientation(ext));
935 }
936 match &mut self.pending {
937 Some((_, e)) if *e == start => *e = end,
938 pending => {
939 debug_assert!(
940 pending.is_none(),
941 "an edge tile or row end yields a span first"
942 );
943 *pending = Some((start, end));
944 }
945 }
946 Ok(())
947 }
948
949 #[cfg_attr(feature = "hotpath", hotpath::measure)]
951 #[cfg_attr(
953 not(feature = "hotpath"),
954 expect(
955 clippy::many_single_char_names,
956 reason = "edge endpoints and the scratch"
957 )
958 )]
959 fn load_row<V: PolyVertex, R: AsRef<[V]>>(
960 &mut self,
961 cx: &mut Cx<'_, V, R>,
962 ) -> Result<(), TileError> {
963 let ty = self.row;
964 let (extent, buffer) = (i64::from(cx.grid.extent()), i64::from(cx.grid.buffer()));
965 let s = &mut *cx.s;
966 let rings = cx.rings;
967 while let Some(&c) = s.order.get(self.next_chunk)
968 && s.chunks[c as usize].rows.0 <= ty
969 {
970 let at = s.active.partition_point(|&a| a < c);
971 s.active.insert(at, c);
972 self.next_chunk += 1;
973 }
974 let chunks = &s.chunks;
975 s.active.retain(|&c| chunks[c as usize].rows.1 >= ty);
976
977 s.hits.clear();
978 s.long.clear();
979 s.long_cover.clear();
980 s.covering.clear();
981 s.crossings.clear();
982 let (slab_lo, slab_hi) = (ty * extent - buffer, ty * extent + extent - 1 + buffer);
983 let cy = row_center(ty, extent);
984 for &c in &s.active {
985 let chunk = s.chunks[c as usize];
986 let info = s.rings[chunk.ring as usize];
987 let pts = info.pts(rings, &s.copies);
988 let m = pts.len();
989 for i in chunk.first as usize..(chunk.first + chunk.count) as usize {
990 let (a, b) = (pts[i].position(), pts[(i + 1) % m].position());
991 let (lo, hi) = if a.y <= b.y { (a, b) } else { (b, a) };
992 if i64::from(hi.y) < slab_lo || i64::from(lo.y) > slab_hi {
993 continue;
994 }
995 #[expect(clippy::cast_possible_truncation, reason = "edge ids fit u32")]
996 let edge = info.edge0 + i as u32;
997 let wide = |p: Coord<i32>| Coord {
998 x: i64::from(p.x),
999 y: i64::from(p.y),
1000 };
1001 let (first, last) = Seg::new(wide(lo), wide(hi)).row_span(ty, extent, buffer);
1002 let fits = |v: i64| i32::try_from(v).map_err(|_| TileError::Overflow);
1003 if last - first + 1 >= LONG_EDGE {
1004 s.long.push(Span {
1005 first: fits(first)?,
1006 last: fits(last)?,
1007 edge,
1008 ring: chunk.ring,
1009 });
1010 } else {
1011 for tx in first..=last {
1012 s.hits.push(Hit {
1013 tx: fits(tx)?,
1014 edge,
1015 ring: chunk.ring,
1016 });
1017 }
1018 }
1019 if i64::from(lo.y) <= cy && cy < i64::from(hi.y) {
1021 let x =
1022 crossing_x_ceil(a, b, i32::try_from(cy).map_err(|_| TileError::Overflow)?);
1023 s.crossings.push(Crossing {
1024 edge,
1025 ring: chunk.ring,
1026 up: b.y > a.y,
1027 right_of: (x - extent - buffer).div_euclid(extent),
1029 });
1030 }
1031 }
1032 }
1033 sort_hits(&mut s.hits, &mut s.hits_tmp, &mut s.counts);
1034 s.long.sort_unstable_by_key(|c| (c.first, c.edge));
1035 debug_assert!(s.crossings.is_sorted_by_key(|c| c.edge));
1036 s.by_x.clear();
1037 #[expect(clippy::cast_possible_truncation, reason = "one crossing per edge")]
1038 s.by_x.extend(0..s.crossings.len() as u32);
1039 let crossings = &s.crossings;
1040 s.by_x
1041 .sort_unstable_by_key(|&i| crossings[i as usize].right_of);
1042 fenwick_build(&mut s.fenwick, s.crossings.iter().map(|c| c.sign()));
1043 Ok(())
1044 }
1045
1046 #[cfg_attr(feature = "hotpath", hotpath::measure)]
1051 fn build_tile<V: PolyVertex, R: AsRef<[V]>>(
1052 &mut self,
1053 cx: &mut Cx<'_, V, R>,
1054 tile: TileId,
1055 ) -> Result<Option<Coord<i32>>, TileError> {
1056 let (min, max) = cx.grid.tile_buffered_bounds(tile)?;
1057 let origin = cx.grid.origin_of(min);
1058 let s = &mut *cx.s;
1059 let rings = cx.rings;
1060 let hits = match &self.cover {
1061 Some(range) => &s.hits[range.clone()],
1062 None => &s.covering[..],
1063 };
1064 s.verts.clear();
1065 s.ring_ends.clear();
1066 let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1068 if !exterior_touched && !s.inside[0] {
1069 return Ok(None); }
1071 let holes_in = self.holes_in as usize;
1074 if holes_in > 0
1075 && holes_in
1076 > ring_groups(hits)
1077 .filter(|g| g[0].ring != 0 && s.inside[g[0].ring as usize])
1078 .count()
1079 {
1080 return Ok(None);
1081 }
1082 if !exterior_touched {
1083 let orient = *self
1084 .orient
1085 .get_or_insert_with(|| ring_orientation(s.rings[0].pts(rings, &s.copies)));
1086 push_fill_box(&mut s.verts, orient, min, max)?;
1087 s.ring_ends.push(offset(s.verts.len())?);
1088 }
1089 for ring_hits in ring_groups(hits) {
1090 let info = s.rings[ring_hits[0].ring as usize];
1091 s.arcs.clear();
1092 for x in ring_hits {
1093 keep_edge(&mut s.arcs, (x.edge - info.edge0) as usize);
1094 }
1095 let (cs, fenwick) = (&s.crossings, &s.fenwick);
1096 let winding =
1097 |first: usize, count: usize| excursion_winding(cs, fenwick, info, first, count);
1098 close_ring(
1099 info.pts(rings, &s.copies),
1100 &mut s.arcs,
1101 min,
1102 max,
1103 winding,
1104 &mut s.verts,
1105 )?;
1106 s.ring_ends.push(offset(s.verts.len())?);
1107 }
1108 Ok(Some(origin))
1109 }
1110}
1111
1112type LoadedRing = (RingInfo, Coord<i32>, Coord<i32>);
1114
1115fn load_ring<V: Vertex>(
1120 s: &mut PolyScratch<V>,
1121 index: u32,
1122 ring: &[V],
1123 edge0: u32,
1124) -> Result<Option<LoadedRing>, TileError> {
1125 let Some(first) = ring.first().map(Vertex::position) else {
1126 return Ok(None);
1127 };
1128 let mut n = ring.len();
1129 while n >= 2 && ring[n - 1].position() == first {
1130 n -= 1;
1131 }
1132 let (mut lo, mut hi, mut prev, mut distinct) = (first, first, first, true);
1134 for v in &ring[1..n] {
1135 let p = v.position();
1136 (lo.x, lo.y, hi.x, hi.y) = (lo.x.min(p.x), lo.y.min(p.y), hi.x.max(p.x), hi.y.max(p.y));
1137 distinct &= p != prev;
1138 prev = p;
1139 }
1140 let (src, len) = if distinct {
1141 (Src::Input, n)
1142 } else {
1143 let start = s.copies.len();
1144 (Src::Copy(start), push_distinct(ring, &mut s.copies))
1145 };
1146 if len < 3 {
1147 if let Src::Copy(start) = src {
1148 s.copies.truncate(start);
1149 }
1150 return Ok(None);
1151 }
1152 let info = RingInfo {
1153 input: index,
1154 src,
1155 len: u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)?,
1156 edge0,
1157 };
1158 Ok(Some((info, lo, hi)))
1159}
1160
1161fn index_chunks<V: Vertex, R: AsRef<[V]>>(
1164 s: &mut PolyScratch<V>,
1165 rings: &[R],
1166 extent: i64,
1167 buffer: i64,
1168) {
1169 for (ring_id, info) in (0_u32..).zip(&s.rings) {
1170 let pts = info.pts(rings, &s.copies);
1171 for first in (0..pts.len()).step_by(CHUNK) {
1172 let count = CHUNK.min(pts.len() - first);
1173 let (mut y0, mut y1) = (i32::MAX, i32::MIN);
1174 for v in pts[first..first + count]
1175 .iter()
1176 .chain(core::iter::once(&pts[(first + count) % pts.len()]))
1177 {
1178 let y = v.position().y;
1179 (y0, y1) = (y0.min(y), y1.max(y));
1180 }
1181 #[expect(
1182 clippy::cast_possible_truncation,
1183 reason = "within the ring's u32 length"
1184 )]
1185 s.chunks.push(Chunk {
1186 ring: ring_id,
1187 first: first as u32,
1188 count: count as u32,
1189 rows: (
1190 (i64::from(y0) - buffer).div_euclid(extent),
1191 (i64::from(y1) + buffer).div_euclid(extent),
1192 ),
1193 });
1194 }
1195 }
1196}
1197
1198fn offset(len: usize) -> Result<u32, TileError> {
1200 u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)
1201}
1202
1203#[cfg_attr(feature = "hotpath", hotpath::measure)]
1206pub(crate) fn clip_polygon_to_tile<'s, V: PolyVertex, R: AsRef<[V]>>(
1207 grid: Grid,
1208 tile: TileId,
1209 rings: &[R],
1210 s: &'s mut PolyScratch<V>,
1211) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
1212 let (min, max) = grid.tile_buffered_bounds(tile)?;
1213 let origin = grid.origin_of(min);
1214 s.verts.clear();
1215 s.ring_ends.clear();
1216 let Some((exterior, holes)) = rings.split_first() else {
1217 return Ok(None);
1218 };
1219 if clip_ring(exterior.as_ref(), min, max, &mut s.clip, &mut s.verts)? == RingClip::Outside {
1221 return Ok(None);
1222 }
1223 check_local(&s.verts, origin)?;
1224 s.ring_ends.push(offset(s.verts.len())?);
1225 for hole in holes {
1226 let start = s.verts.len();
1227 match clip_ring(hole.as_ref(), min, max, &mut s.clip, &mut s.verts)? {
1228 RingClip::Covers => return Ok(None),
1230 RingClip::Clipped => {
1231 check_local(&s.verts[start..], origin)?;
1232 s.ring_ends.push(offset(s.verts.len())?);
1233 }
1234 RingClip::Outside => {}
1235 }
1236 }
1237 Ok(Some(PolygonPiece {
1238 tile,
1239 rings: PieceRings::Built {
1240 verts: &s.verts,
1241 ends: &s.ring_ends,
1242 origin,
1243 },
1244 }))
1245}