1use core::cmp::Ordering;
27use core::ops::Range;
28
29use geo_types::Coord;
30
31use crate::TileError;
32use crate::clip_polygon::{
33 ClipScratch, RingClip, clip_ring, close_ring, fill_box, keep_edge, push_distinct,
34 push_fill_box, ring_meets_box,
35};
36use crate::clip_polyline::to_local;
37use crate::geom::{crossing_x_ceil, ring_orientation};
38use crate::grid::{Grid, Seg};
39use crate::slicer::{ClipMode, Local};
40use crate::tile::TileId;
41use crate::vertex::{PolyVertex, Vertex};
42
43const CHUNK: usize = 32;
45
46const LONG_EDGE: i64 = 4;
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58#[non_exhaustive]
59pub struct FillRun {
60 pub y: i32,
62 pub x: Range<i32>,
64 orient: Ordering,
66}
67
68#[derive(Debug)]
70pub enum PolygonSlice<'a, V, R> {
71 Piece(PolygonPiece<'a, V, R>),
73 Run(FillRun),
75}
76
77#[derive(Debug)]
80pub struct PolygonPiece<'a, V, R> {
81 tile: TileId,
82 rings: PieceRings<'a, V, R>,
83}
84
85#[derive(Debug)]
86enum PieceRings<'a, V, R> {
87 Built {
90 verts: &'a [V],
91 ends: &'a [u32],
92 origin: Coord<i32>,
93 },
94 Whole {
97 rings: &'a [RingInfo],
98 input: &'a [R],
99 copies: &'a [V],
100 origin: Coord<i32>,
101 },
102}
103
104impl<V, R> Clone for PieceRings<'_, V, R> {
106 fn clone(&self) -> Self {
107 *self
108 }
109}
110impl<V, R> Copy for PieceRings<'_, V, R> {}
111impl<V, R> Clone for PolygonPiece<'_, V, R> {
112 fn clone(&self) -> Self {
113 *self
114 }
115}
116impl<V, R> Copy for PolygonPiece<'_, V, R> {}
117
118impl<'a, V: Vertex, R: AsRef<[V]>> PolygonPiece<'a, V, R> {
119 #[must_use]
121 pub fn tile(&self) -> TileId {
122 self.tile
123 }
124
125 pub fn rings(&self) -> impl Iterator<Item = Local<'a, V>> + use<'a, V, R> {
127 let rings = self.rings;
128 let n = match rings {
129 PieceRings::Built { ends, .. } => ends.len(),
130 PieceRings::Whole { rings, .. } => rings.len(),
131 };
132 (0..n).map(move |i| match rings {
133 PieceRings::Built {
134 verts,
135 ends,
136 origin,
137 } => {
138 let start = i.checked_sub(1).map_or(0, |p| ends[p] as usize);
139 Local::ring(&verts[start..ends[i] as usize], origin, true)
140 }
141 PieceRings::Whole {
142 rings,
143 input,
144 copies,
145 origin,
146 } => Local::ring(rings[i].pts(input, copies), origin, false),
147 })
148 }
149}
150
151#[derive(Debug, Clone, Copy)]
153enum Src {
154 Input,
156 Copy(usize),
158}
159
160#[derive(Debug, Clone, Copy)]
164struct RingInfo {
165 input: u32,
166 src: Src,
167 len: u32,
168 edge0: u32,
169}
170
171impl RingInfo {
172 fn pts<'a, V, R: AsRef<[V]>>(&self, input: &'a [R], copies: &'a [V]) -> &'a [V] {
173 let len = self.len as usize;
174 match self.src {
175 Src::Input => &input[self.input as usize].as_ref()[..len],
176 Src::Copy(start) => &copies[start..start + len],
177 }
178 }
179}
180
181#[derive(Debug, Clone, Copy)]
183struct Chunk {
184 ring: u32,
185 first: u32,
186 count: u32,
187 rows: (i64, i64),
188}
189
190#[derive(Debug, Clone, Copy)]
193struct Hit {
194 tx: i32,
195 edge: u32,
196 ring: u32,
197}
198
199#[derive(Debug, Clone, Copy)]
202struct Span {
203 first: i32,
204 last: i32,
205 edge: u32,
206 ring: u32,
207}
208
209#[derive(Debug, Clone, Copy)]
213struct Crossing {
214 edge: u32,
215 ring: u32,
216 up: bool,
217 right_of: i64,
218}
219
220impl Crossing {
221 fn sign(self) -> i64 {
222 if self.up { 1 } else { -1 }
223 }
224}
225
226fn row_center(ty: i64, extent: i64) -> i64 {
230 ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
231}
232
233fn fenwick_build(tree: &mut Vec<i64>, values: impl Iterator<Item = i64>) {
235 tree.clear();
236 tree.extend(values);
237 for i in 0..tree.len() {
238 let parent = i | (i + 1);
239 if parent < tree.len() {
240 tree[parent] += tree[i];
241 }
242 }
243}
244
245fn fenwick_add(tree: &mut [i64], mut i: usize, delta: i64) {
247 while i < tree.len() {
248 tree[i] += delta;
249 i |= i + 1;
250 }
251}
252
253fn fenwick_prefix(tree: &[i64], mut end: usize) -> i64 {
255 let mut sum = 0;
256 while end > 0 {
257 sum += tree[end - 1];
258 end &= end - 1;
259 }
260 sum
261}
262
263fn sort_hits(hits: &mut [Hit], tmp: &mut Vec<Hit>, counts: &mut Vec<usize>) {
267 let Some(head) = hits.first() else {
268 return;
269 };
270 let (lo, hi) = hits.iter().fold((head.tx, head.tx), |(lo, hi), h| {
271 (lo.min(h.tx), hi.max(h.tx))
272 });
273 let columns = hi.abs_diff(lo) as usize + 1;
274 if columns == 1 {
275 } else if hits.len() < 16 || columns > hits.len().saturating_mul(4) {
277 hits.sort_unstable_by_key(|h| (h.tx, h.edge));
278 } else {
279 counts.clear();
280 counts.resize(columns, 0);
281 for h in hits.iter() {
282 counts[h.tx.abs_diff(lo) as usize] += 1;
283 }
284 let mut start = 0;
286 for c in counts.iter_mut() {
287 (*c, start) = (start, start + *c);
288 }
289 tmp.clear();
290 tmp.extend_from_slice(hits);
291 for h in tmp.iter() {
292 let at = &mut counts[h.tx.abs_diff(lo) as usize];
293 hits[*at] = *h;
294 *at += 1;
295 }
296 }
297 debug_assert!(hits.is_sorted_by_key(|h| (h.tx, h.edge)));
298}
299
300fn check_local<V: Vertex>(verts: &[V], origin: Coord<i32>) -> Result<(), TileError> {
302 for &v in verts {
303 to_local(v, origin)?;
304 }
305 Ok(())
306}
307
308fn merge_by_edge<T: Copy>(
312 spans: &mut Vec<T>,
313 kept: usize,
314 tmp: &mut Vec<T>,
315 edge: impl Fn(&T) -> u32,
316) {
317 let added = &mut spans[kept..];
318 if !added.is_sorted_by_key(&edge) {
319 added.sort_unstable_by_key(&edge);
320 }
321 if kept == 0 || kept == spans.len() || edge(&spans[kept - 1]) < edge(&spans[kept]) {
322 return;
323 }
324 tmp.clear();
325 let (old, new) = spans.split_at(kept);
326 let (mut i, mut j) = (0, 0);
327 while i < old.len() && j < new.len() {
328 if edge(&old[i]) < edge(&new[j]) {
329 tmp.push(old[i]);
330 i += 1;
331 } else {
332 tmp.push(new[j]);
333 j += 1;
334 }
335 }
336 tmp.extend_from_slice(&old[i..]);
337 tmp.extend_from_slice(&new[j..]);
338 core::mem::swap(spans, tmp);
339}
340
341fn ring_groups(mut hits: &[Hit]) -> impl Iterator<Item = &[Hit]> {
344 core::iter::from_fn(move || {
345 let ring = hits.first()?.ring;
346 let (group, rest) = hits.split_at(hits.partition_point(|x| x.ring == ring));
347 hits = rest;
348 Some(group)
349 })
350}
351
352fn excursion_winding(
355 cs: &[Crossing],
356 fenwick: &[i64],
357 ring: RingInfo,
358 first: usize,
359 count: usize,
360) -> i64 {
361 let (start, len) = (ring.edge0 as usize, ring.len as usize);
362 let sum = |lo: usize, hi: usize| {
363 let i = cs.partition_point(|c| (c.edge as usize) < lo);
364 let j = cs.partition_point(|c| (c.edge as usize) < hi);
365 fenwick_prefix(fenwick, j) - fenwick_prefix(fenwick, i)
366 };
367 if first + count <= len {
368 sum(start + first, start + first + count)
369 } else {
370 sum(start + first, start + len) + sum(start, start + first + count - len)
371 }
372}
373
374pub(crate) struct PolyScratch<V> {
377 rings: Vec<RingInfo>,
379 copies: Vec<V>,
380 chunks: Vec<Chunk>,
381 order: Vec<u32>,
383 active: Vec<u32>,
384 hits: Vec<Hit>,
389 long: Vec<Span>,
390 long_cover: Vec<Span>,
391 covering: Vec<Hit>,
392 crossings: Vec<Crossing>,
393 by_x: Vec<u32>,
394 hits_tmp: Vec<Hit>,
396 spans_tmp: Vec<Span>,
397 counts: Vec<usize>,
398 fenwick: Vec<i64>,
399 inside: Vec<bool>,
401 verts: Vec<V>,
403 ring_ends: Vec<u32>,
404 arcs: Vec<(usize, usize)>,
405 clip: ClipScratch<V>,
406}
407
408impl<V> PolyScratch<V> {
409 pub(crate) const fn new() -> Self {
410 Self {
411 rings: Vec::new(),
412 copies: Vec::new(),
413 chunks: Vec::new(),
414 order: Vec::new(),
415 active: Vec::new(),
416 hits: Vec::new(),
417 long: Vec::new(),
418 long_cover: Vec::new(),
419 covering: Vec::new(),
420 crossings: Vec::new(),
421 by_x: Vec::new(),
422 hits_tmp: Vec::new(),
423 spans_tmp: Vec::new(),
424 counts: Vec::new(),
425 fenwick: Vec::new(),
426 inside: Vec::new(),
427 verts: Vec::new(),
428 ring_ends: Vec::new(),
429 arcs: Vec::new(),
430 clip: ClipScratch::new(),
431 }
432 }
433}
434
435pub(crate) struct Scratch<V>(Option<Box<PolyState<V>>>);
439
440pub(crate) struct PolyState<V> {
443 pub(crate) scratch: PolyScratch<V>,
444 sweep: Option<Sweep>,
445}
446
447impl<V> Scratch<V> {
448 pub(crate) const fn new() -> Self {
449 Self(None)
450 }
451
452 #[inline]
454 pub(crate) fn get(&mut self) -> &mut PolyState<V> {
455 #[cold]
456 fn alloc<V>() -> Box<PolyState<V>> {
457 Box::new(PolyState {
458 scratch: PolyScratch::new(),
459 sweep: None,
460 })
461 }
462 self.0.get_or_insert_with(alloc)
463 }
464}
465
466impl<V> Clone for Scratch<V> {
467 fn clone(&self) -> Self {
468 Self::new()
469 }
470}
471
472impl<V> PartialEq for Scratch<V> {
473 fn eq(&self, _: &Self) -> bool {
474 true
475 }
476}
477
478impl<V> Eq for Scratch<V> {}
479
480impl<V> core::fmt::Debug for Scratch<V> {
481 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
482 f.debug_struct("PolyScratch").finish_non_exhaustive()
483 }
484}
485
486pub(crate) fn fill_ring<V: PolyVertex>(grid: Grid, run: &FillRun) -> [V; 5] {
490 let (extent, buffer) = (grid.extent_i32(), grid.buffer_i32());
491 let sw = Coord {
492 x: -buffer - 1,
493 y: -buffer - 1,
494 };
495 let ne = Coord {
496 x: extent + buffer,
497 y: extent + buffer,
498 };
499 fill_box(run.orient, sw, ne).map(V::synthetic_at)
500}
501
502enum Event {
504 Whole {
506 tile: TileId,
507 origin: Coord<i32>,
508 },
509 Built {
511 tile: TileId,
512 origin: Coord<i32>,
513 },
514 Run(FillRun),
515}
516
517struct Sweep {
519 row: i64,
521 last_row: i64,
522 next_chunk: usize,
524 in_row: bool,
527 ty: i32,
528 k: usize,
529 h: usize,
530 lh: usize,
531 next: i64,
532 cover: Option<Range<usize>>,
535 long_last: i64,
537 holes_in: u32,
539 pending: Option<(i64, i64)>,
541 ready: Option<FillRun>,
544 tile_ready: Option<i32>,
545 orient: Option<Ordering>,
547}
548
549pub struct PolygonSlices<'s, V, R> {
555 grid: Grid,
556 whole: bool,
558 rings: &'s [R],
559 s: &'s mut PolyScratch<V>,
560 start: Option<Start>,
562 sweep: &'s mut Option<Sweep>,
563 done: bool,
564}
565
566enum Start {
569 Whole { tile: TileId, origin: Coord<i32> },
571 Sweep { row: i64, last_row: i64 },
573}
574
575impl<'s, V: PolyVertex, R: AsRef<[V]>> PolygonSlices<'s, V, R> {
576 #[cfg_attr(feature = "hotpath", hotpath::measure)]
578 #[inline]
579 pub(crate) fn new(
580 grid: Grid,
581 mode: ClipMode,
582 rings: &'s [R],
583 state: &'s mut PolyState<V>,
584 ) -> Result<Self, TileError> {
585 let PolyState { scratch: s, sweep } = state;
586 *sweep = None;
587 let start = prepare(grid, rings, s)?;
588 Ok(Self {
589 grid,
590 whole: mode == ClipMode::Unclipped,
591 rings,
592 s,
593 start,
594 sweep,
595 done: false,
596 })
597 }
598
599 #[expect(
603 clippy::should_implement_trait,
604 reason = "a lending iterator: each item borrows the iterator"
605 )]
606 pub fn next(&mut self) -> Option<Result<PolygonSlice<'_, V, R>, TileError>> {
607 if self.done {
608 return None;
609 }
610 match self.step() {
611 Ok(Some(event)) => Some(Ok(self.view(event))),
612 Ok(None) => {
613 self.done = true;
614 None
615 }
616 Err(e) => {
617 self.done = true;
618 Some(Err(e))
619 }
620 }
621 }
622
623 #[must_use]
629 pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
630 fill_ring(self.grid, run)
631 }
632
633 fn view(&self, event: Event) -> PolygonSlice<'_, V, R> {
634 let (tile, rings) = match event {
635 Event::Run(run) => return PolygonSlice::Run(run),
636 Event::Built { tile, origin } => (
637 tile,
638 PieceRings::Built {
639 verts: &self.s.verts,
640 ends: &self.s.ring_ends,
641 origin,
642 },
643 ),
644 Event::Whole { tile, origin } => (
645 tile,
646 PieceRings::Whole {
647 rings: &self.s.rings,
648 input: self.rings,
649 copies: &self.s.copies,
650 origin,
651 },
652 ),
653 };
654 PolygonSlice::Piece(PolygonPiece { tile, rings })
655 }
656
657 fn step(&mut self) -> Result<Option<Event>, TileError> {
658 match self.start.take() {
659 Some(Start::Whole { tile, origin }) => return Ok(Some(Event::Whole { tile, origin })),
660 Some(Start::Sweep { row, last_row }) => *self.sweep = Some(Sweep::new(row, last_row)),
661 None => {}
662 }
663 let Some(sw) = self.sweep.as_mut() else {
664 return Ok(None);
665 };
666 let mut cx = Cx {
667 grid: self.grid,
668 s: &mut *self.s,
669 rings: self.rings,
670 };
671 if self.whole {
672 sw.step_whole(&mut cx)
673 } else {
674 sw.step::<false, V, R>(&mut cx)
675 }
676 }
677}
678
679#[inline(never)]
684fn prepare<V: PolyVertex, R: AsRef<[V]>>(
685 grid: Grid,
686 rings: &[R],
687 s: &mut PolyScratch<V>,
688) -> Result<Option<Start>, TileError> {
689 let (extent, buffer) = (i64::from(grid.extent()), i64::from(grid.buffer()));
690 s.rings.clear();
691 s.copies.clear();
692 u32::try_from(rings.len()).map_err(|_| TileError::GeometryTooLarge)?;
693 let mut bounds: Option<(Coord<i32>, Coord<i32>)> = None;
694 let mut edges = 0_u32;
695 for (index, ring) in (0_u32..).zip(rings) {
696 let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), edges)? else {
697 if index == 0 {
698 return Ok(None); }
700 continue;
701 };
702 edges = edges
703 .checked_add(info.len)
704 .ok_or(TileError::GeometryTooLarge)?;
705 s.rings.push(info);
706 bounds = Some(bounds.map_or((lo, hi), |(l, h)| {
707 (
708 Coord {
709 x: l.x.min(lo.x),
710 y: l.y.min(lo.y),
711 },
712 Coord {
713 x: h.x.max(hi.x),
714 y: h.y.max(hi.y),
715 },
716 )
717 }));
718 }
719 let Some((lo, hi)) = bounds else {
720 return Ok(None);
721 };
722
723 if let Some((tile, origin)) = grid.inner_tile(lo, hi) {
726 return Ok(Some(Start::Whole { tile, origin }));
727 }
728 grid.check_polygon_bounds(lo, hi)?;
729
730 s.chunks.clear();
731 index_chunks(s, rings, extent, buffer);
732 s.order.clear();
733 #[expect(
734 clippy::cast_possible_truncation,
735 reason = "at most one chunk per edge"
736 )]
737 s.order.extend(0..s.chunks.len() as u32);
738 let chunks = &s.chunks;
739 s.order.sort_unstable_by_key(|&c| chunks[c as usize].rows.0);
740 s.active.clear();
741 s.inside.clear();
742 s.inside.resize(s.rings.len(), false);
743 Ok(Some(Start::Sweep {
744 row: (i64::from(lo.y) - buffer).div_euclid(extent),
745 last_row: (i64::from(hi.y) + buffer).div_euclid(extent),
746 }))
747}
748
749struct Cx<'x, V, R> {
751 grid: Grid,
752 s: &'x mut PolyScratch<V>,
753 rings: &'x [R],
754}
755
756impl Sweep {
757 fn new(row: i64, last_row: i64) -> Self {
759 Self {
760 row,
761 last_row,
762 next_chunk: 0,
763 in_row: false,
764 ty: 0,
765 k: 0,
766 h: 0,
767 next: i64::MIN,
768 holes_in: 0,
769 pending: None,
770 ready: None,
771 tile_ready: None,
772 orient: None,
773 lh: 0,
774 cover: Some(0..0),
775 long_last: i64::MIN,
776 }
777 }
778
779 #[inline(never)]
783 fn step_whole<V: PolyVertex, R: AsRef<[V]>>(
784 &mut self,
785 cx: &mut Cx<'_, V, R>,
786 ) -> Result<Option<Event>, TileError> {
787 self.step::<true, V, R>(cx)
788 }
789
790 fn step<const WHOLE: bool, V: PolyVertex, R: AsRef<[V]>>(
793 &mut self,
794 cx: &mut Cx<'_, V, R>,
795 ) -> Result<Option<Event>, TileError> {
796 loop {
797 if let Some(run) = self.ready.take() {
798 return Ok(Some(if WHOLE {
799 self.whole_run_tile(cx.grid, run)
800 } else {
801 Event::Run(run)
802 }));
803 }
804 if let Some(tx) = self.tile_ready.take() {
805 let tile = TileId::new(tx, self.ty);
806 if WHOLE {
807 if let Some(event) = self.whole_tile(cx.grid, cx.s, tile) {
808 return Ok(Some(event));
809 }
810 } else if let Some(origin) = self.build_tile(cx, tile)? {
811 return Ok(Some(Event::Built { tile, origin }));
812 }
813 continue;
814 }
815 if !self.in_row {
816 if self.row > self.last_row {
817 return Ok(None);
818 }
819 self.load_row(cx)?;
820 self.ty = i32::try_from(self.row).map_err(|_| TileError::Overflow)?;
821 (self.in_row, self.k, self.h, self.lh, self.next) = (true, 0, 0, 0, i64::MIN);
822 (self.cover, self.long_last) = (Some(0..0), i64::MIN);
823 continue;
824 }
825 let s = &*cx.s;
827 let bound = s
828 .by_x
829 .get(self.k)
830 .map_or(i64::MAX, |&i| s.crossings[i as usize].right_of);
831 let tile = if s.long.is_empty() {
833 s.hits.get(self.h).map(|c| i64::from(c.tx))
834 } else {
835 let next = self.next;
838 let covered =
840 (self.long_last != i64::MIN && self.long_last >= next).then_some(next);
841 let short = s.hits.get(self.h).map(|c| i64::from(c.tx));
842 let long = s.long.get(self.lh).map(|c| i64::from(c.first).max(next));
843 [covered, short, long].into_iter().flatten().min()
844 };
845 if let Some(tx) = tile
846 && tx <= bound
847 {
848 let tx = i32::try_from(tx).map_err(|_| TileError::Overflow)?;
849 let s = &mut *cx.s;
850 let start = self.h;
852 self.h += s.hits[start..].partition_point(|c| c.tx == tx);
853 self.cover = Some(start..self.h);
854 if !s.long.is_empty() {
855 self.cover_long(s, tx, start);
856 }
857 self.fill(cx, self.next, i64::from(tx))?;
858 self.ready = self.take_pending();
860 self.tile_ready = Some(tx);
861 self.next = i64::from(tx) + 1;
862 continue;
863 }
864 if self.k == s.by_x.len() {
865 debug_assert!(cx.s.inside.iter().all(|&i| !i), "parity ends even");
867 self.ready = self.take_pending();
868 (self.in_row, self.row) = (false, self.row + 1);
869 continue;
870 }
871 self.fill(cx, self.next, bound + 1)?;
873 self.next = bound + 1;
874 let s = &mut *cx.s;
876 while let Some(&i) = s.by_x.get(self.k)
877 && s.crossings[i as usize].right_of == bound
878 {
879 let crossing = s.crossings[i as usize];
880 let r = crossing.ring as usize;
881 s.inside[r] = !s.inside[r];
882 if r != 0 {
883 if s.inside[r] {
884 self.holes_in += 1;
885 } else {
886 self.holes_in = self.holes_in.saturating_sub(1);
889 }
890 }
891 fenwick_add(&mut s.fenwick, i as usize, -crossing.sign());
892 self.k += 1;
893 }
894 if !(s.inside[0] && self.holes_in == 0) {
896 self.ready = self.take_pending();
897 }
898 }
899 }
900
901 fn whole_run_tile(&mut self, grid: Grid, mut run: FillRun) -> Event {
904 let tile = TileId::new(run.x.start, run.y);
905 run.x.start += 1;
906 if !run.x.is_empty() {
907 self.ready = Some(run);
908 }
909 Event::Whole {
910 tile,
911 origin: grid.tile_origin(tile),
912 }
913 }
914
915 fn cover_long<V>(&mut self, s: &mut PolyScratch<V>, tx: i32, start: usize) {
918 if self.long_last < i64::from(tx) {
919 s.long_cover.clear();
920 } else {
921 s.long_cover.retain(|c| c.last >= tx);
922 }
923 let kept = s.long_cover.len();
924 while let Some(&span) = s.long.get(self.lh)
925 && span.first <= tx
926 {
927 s.long_cover.push(span);
928 self.lh += 1;
929 }
930 if s.long_cover.is_empty() {
931 self.long_last = i64::MIN;
932 return;
933 }
934 merge_by_edge(&mut s.long_cover, kept, &mut s.spans_tmp, |c| c.edge);
935 self.long_last = s
936 .long_cover
937 .iter()
938 .map(|c| i64::from(c.last))
939 .max()
940 .unwrap_or(i64::MIN);
941 s.covering.clear();
942 s.covering.extend(s.long_cover.iter().map(|c| Hit {
943 tx,
944 edge: c.edge,
945 ring: c.ring,
946 }));
947 let kept = s.covering.len();
948 s.covering.extend_from_slice(&s.hits[start..self.h]);
949 merge_by_edge(&mut s.covering, kept, &mut s.hits_tmp, |c| c.edge);
950 self.cover = None;
951 }
952
953 fn take_pending(&mut self) -> Option<FillRun> {
955 let (start, end) = self.pending.take()?;
956 #[expect(
957 clippy::cast_possible_truncation,
958 reason = "both ends were checked to fit i32 when the span was recorded"
959 )]
960 Some(FillRun {
961 y: self.ty,
962 x: start as i32..end as i32,
963 orient: self.orient.unwrap_or(Ordering::Equal),
964 })
965 }
966
967 fn fill<V: PolyVertex, R: AsRef<[V]>>(
970 &mut self,
971 cx: &mut Cx<'_, V, R>,
972 start: i64,
973 end: i64,
974 ) -> Result<(), TileError> {
975 let covered = cx.s.inside.first().copied().unwrap_or(false) && self.holes_in == 0;
976 if start >= end || !covered {
977 return Ok(());
978 }
979 i32::try_from(start).map_err(|_| TileError::Overflow)?;
980 i32::try_from(end).map_err(|_| TileError::Overflow)?;
981 if self.orient.is_none() {
982 let ext = cx.s.rings[0].pts(cx.rings, &cx.s.copies);
983 self.orient = Some(ring_orientation(ext));
984 }
985 match &mut self.pending {
986 Some((_, e)) if *e == start => *e = end,
987 pending => {
988 debug_assert!(
989 pending.is_none(),
990 "an edge tile or row end yields a span first"
991 );
992 *pending = Some((start, end));
993 }
994 }
995 Ok(())
996 }
997
998 #[cfg_attr(feature = "hotpath", hotpath::measure)]
1000 #[cfg_attr(
1002 not(feature = "hotpath"),
1003 expect(
1004 clippy::many_single_char_names,
1005 reason = "edge endpoints and the scratch"
1006 )
1007 )]
1008 fn load_row<V: PolyVertex, R: AsRef<[V]>>(
1009 &mut self,
1010 cx: &mut Cx<'_, V, R>,
1011 ) -> Result<(), TileError> {
1012 let ty = self.row;
1013 let (extent, buffer) = (i64::from(cx.grid.extent()), i64::from(cx.grid.buffer()));
1014 let s = &mut *cx.s;
1015 let rings = cx.rings;
1016 while let Some(&c) = s.order.get(self.next_chunk)
1017 && s.chunks[c as usize].rows.0 <= ty
1018 {
1019 let at = s.active.partition_point(|&a| a < c);
1020 s.active.insert(at, c);
1021 self.next_chunk += 1;
1022 }
1023 let chunks = &s.chunks;
1024 s.active.retain(|&c| chunks[c as usize].rows.1 >= ty);
1025
1026 s.hits.clear();
1027 s.long.clear();
1028 s.long_cover.clear();
1029 s.covering.clear();
1030 s.crossings.clear();
1031 let (slab_lo, slab_hi) = (ty * extent - buffer, ty * extent + extent - 1 + buffer);
1032 let cy = row_center(ty, extent);
1033 for &c in &s.active {
1034 let chunk = s.chunks[c as usize];
1035 let info = s.rings[chunk.ring as usize];
1036 let pts = info.pts(rings, &s.copies);
1037 let m = pts.len();
1038 for i in chunk.first as usize..(chunk.first + chunk.count) as usize {
1039 let (a, b) = (pts[i].position(), pts[(i + 1) % m].position());
1040 let (lo, hi) = if a.y <= b.y { (a, b) } else { (b, a) };
1041 if i64::from(hi.y) < slab_lo || i64::from(lo.y) > slab_hi {
1042 continue;
1043 }
1044 #[expect(clippy::cast_possible_truncation, reason = "edge ids fit u32")]
1045 let edge = info.edge0 + i as u32;
1046 let wide = |p: Coord<i32>| Coord {
1047 x: i64::from(p.x),
1048 y: i64::from(p.y),
1049 };
1050 let (first, last) = Seg::new(wide(lo), wide(hi)).row_span(ty, extent, buffer);
1051 let fits = |v: i64| i32::try_from(v).map_err(|_| TileError::Overflow);
1052 if last - first + 1 >= LONG_EDGE {
1053 s.long.push(Span {
1054 first: fits(first)?,
1055 last: fits(last)?,
1056 edge,
1057 ring: chunk.ring,
1058 });
1059 } else {
1060 for tx in first..=last {
1061 s.hits.push(Hit {
1062 tx: fits(tx)?,
1063 edge,
1064 ring: chunk.ring,
1065 });
1066 }
1067 }
1068 if i64::from(lo.y) <= cy && cy < i64::from(hi.y) {
1070 let x =
1071 crossing_x_ceil(a, b, i32::try_from(cy).map_err(|_| TileError::Overflow)?);
1072 s.crossings.push(Crossing {
1073 edge,
1074 ring: chunk.ring,
1075 up: b.y > a.y,
1076 right_of: (x - extent - buffer).div_euclid(extent),
1078 });
1079 }
1080 }
1081 }
1082 sort_hits(&mut s.hits, &mut s.hits_tmp, &mut s.counts);
1083 s.long.sort_unstable_by_key(|c| (c.first, c.edge));
1084 debug_assert!(s.crossings.is_sorted_by_key(|c| c.edge));
1085 s.by_x.clear();
1086 #[expect(clippy::cast_possible_truncation, reason = "one crossing per edge")]
1087 s.by_x.extend(0..s.crossings.len() as u32);
1088 let crossings = &s.crossings;
1089 s.by_x
1090 .sort_unstable_by_key(|&i| crossings[i as usize].right_of);
1091 fenwick_build(&mut s.fenwick, s.crossings.iter().map(|c| c.sign()));
1092 Ok(())
1093 }
1094
1095 #[expect(
1100 clippy::inline_always,
1101 reason = "run per edge tile; out of line it costs the polygon benchmarks 2%"
1102 )]
1103 #[inline(always)]
1104 fn tile_hits<'x>(
1105 &self,
1106 hits: &'x [Hit],
1107 covering: &'x [Hit],
1108 inside: &[bool],
1109 ) -> Option<&'x [Hit]> {
1110 let hits = match &self.cover {
1111 Some(range) => &hits[range.clone()],
1112 None => covering,
1113 };
1114 let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1116 if !exterior_touched && !inside[0] {
1117 return None; }
1119 let holes_in = self.holes_in as usize;
1122 if holes_in > 0
1123 && holes_in
1124 > ring_groups(hits)
1125 .filter(|g| g[0].ring != 0 && inside[g[0].ring as usize])
1126 .count()
1127 {
1128 return None;
1129 }
1130 Some(hits)
1131 }
1132
1133 fn whole_tile<V>(&self, grid: Grid, s: &PolyScratch<V>, tile: TileId) -> Option<Event> {
1135 self.tile_hits(&s.hits, &s.covering, &s.inside)?;
1136 let origin = grid.tile_origin(tile);
1137 Some(Event::Whole { tile, origin })
1138 }
1139
1140 #[cfg_attr(feature = "hotpath", hotpath::measure)]
1144 fn build_tile<V: PolyVertex, R: AsRef<[V]>>(
1145 &mut self,
1146 cx: &mut Cx<'_, V, R>,
1147 tile: TileId,
1148 ) -> Result<Option<Coord<i32>>, TileError> {
1149 let (min, max) = cx.grid.tile_buffered_bounds(tile)?;
1150 let origin = cx.grid.origin_of(min);
1151 let s = &mut *cx.s;
1152 let rings = cx.rings;
1153 s.verts.clear();
1154 s.ring_ends.clear();
1155 let Some(hits) = self.tile_hits(&s.hits, &s.covering, &s.inside) else {
1156 return Ok(None);
1157 };
1158 let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1159 if !exterior_touched {
1160 let orient = *self
1161 .orient
1162 .get_or_insert_with(|| ring_orientation(s.rings[0].pts(rings, &s.copies)));
1163 push_fill_box(&mut s.verts, orient, min, max)?;
1164 s.ring_ends.push(offset(s.verts.len())?);
1165 }
1166 for ring_hits in ring_groups(hits) {
1167 let info = s.rings[ring_hits[0].ring as usize];
1168 s.arcs.clear();
1169 for x in ring_hits {
1170 keep_edge(&mut s.arcs, (x.edge - info.edge0) as usize);
1171 }
1172 let (cs, fenwick) = (&s.crossings, &s.fenwick);
1173 let winding =
1174 |first: usize, count: usize| excursion_winding(cs, fenwick, info, first, count);
1175 close_ring(
1176 info.pts(rings, &s.copies),
1177 &mut s.arcs,
1178 min,
1179 max,
1180 winding,
1181 &mut s.verts,
1182 )?;
1183 s.ring_ends.push(offset(s.verts.len())?);
1184 }
1185 Ok(Some(origin))
1186 }
1187}
1188
1189type LoadedRing = (RingInfo, Coord<i32>, Coord<i32>);
1191
1192#[expect(
1197 clippy::inline_always,
1198 reason = "run per ring of every polygon; out of line it costs the small-polygon benchmarks 3-5%"
1199)]
1200#[inline(always)]
1201fn load_ring<V: Vertex>(
1202 s: &mut PolyScratch<V>,
1203 index: u32,
1204 ring: &[V],
1205 edge0: u32,
1206) -> Result<Option<LoadedRing>, TileError> {
1207 let Some(first) = ring.first().map(Vertex::position) else {
1208 return Ok(None);
1209 };
1210 let mut n = ring.len();
1211 while n >= 2 && ring[n - 1].position() == first {
1212 n -= 1;
1213 }
1214 let (mut lo, mut hi, mut prev, mut distinct) = (first, first, first, true);
1216 for v in &ring[1..n] {
1217 let p = v.position();
1218 (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));
1219 distinct &= p != prev;
1220 prev = p;
1221 }
1222 let (src, len) = if distinct {
1223 (Src::Input, n)
1224 } else {
1225 let start = s.copies.len();
1226 (Src::Copy(start), push_distinct(ring, &mut s.copies))
1227 };
1228 if len < 3 {
1229 if let Src::Copy(start) = src {
1230 s.copies.truncate(start);
1231 }
1232 return Ok(None);
1233 }
1234 let info = RingInfo {
1235 input: index,
1236 src,
1237 len: u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)?,
1238 edge0,
1239 };
1240 Ok(Some((info, lo, hi)))
1241}
1242
1243fn index_chunks<V: Vertex, R: AsRef<[V]>>(
1246 s: &mut PolyScratch<V>,
1247 rings: &[R],
1248 extent: i64,
1249 buffer: i64,
1250) {
1251 for (ring_id, info) in (0_u32..).zip(&s.rings) {
1252 let pts = info.pts(rings, &s.copies);
1253 for first in (0..pts.len()).step_by(CHUNK) {
1254 let count = CHUNK.min(pts.len() - first);
1255 let (mut y0, mut y1) = (i32::MAX, i32::MIN);
1256 for v in pts[first..first + count]
1257 .iter()
1258 .chain(core::iter::once(&pts[(first + count) % pts.len()]))
1259 {
1260 let y = v.position().y;
1261 (y0, y1) = (y0.min(y), y1.max(y));
1262 }
1263 #[expect(
1264 clippy::cast_possible_truncation,
1265 reason = "within the ring's u32 length"
1266 )]
1267 s.chunks.push(Chunk {
1268 ring: ring_id,
1269 first: first as u32,
1270 count: count as u32,
1271 rows: (
1272 (i64::from(y0) - buffer).div_euclid(extent),
1273 (i64::from(y1) + buffer).div_euclid(extent),
1274 ),
1275 });
1276 }
1277 }
1278}
1279
1280fn offset(len: usize) -> Result<u32, TileError> {
1282 u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)
1283}
1284
1285#[cfg_attr(feature = "hotpath", hotpath::measure)]
1288pub(crate) fn clip_polygon_to_tile<'s, V: PolyVertex, R: AsRef<[V]>>(
1289 grid: Grid,
1290 tile: TileId,
1291 rings: &[R],
1292 s: &'s mut PolyScratch<V>,
1293) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
1294 let (min, max) = grid.tile_buffered_bounds(tile)?;
1295 let origin = grid.origin_of(min);
1296 s.verts.clear();
1297 s.ring_ends.clear();
1298 let Some((exterior, holes)) = rings.split_first() else {
1299 return Ok(None);
1300 };
1301 if clip_ring(exterior.as_ref(), min, max, &mut s.clip, &mut s.verts)? == RingClip::Outside {
1303 return Ok(None);
1304 }
1305 check_local(&s.verts, origin)?;
1306 s.ring_ends.push(offset(s.verts.len())?);
1307 for hole in holes {
1308 let start = s.verts.len();
1309 match clip_ring(hole.as_ref(), min, max, &mut s.clip, &mut s.verts)? {
1310 RingClip::Covers => return Ok(None),
1312 RingClip::Clipped => {
1313 check_local(&s.verts[start..], origin)?;
1314 s.ring_ends.push(offset(s.verts.len())?);
1315 }
1316 RingClip::Outside => {}
1317 }
1318 }
1319 Ok(Some(PolygonPiece {
1320 tile,
1321 rings: PieceRings::Built {
1322 verts: &s.verts,
1323 ends: &s.ring_ends,
1324 origin,
1325 },
1326 }))
1327}
1328
1329pub(crate) fn whole_polygon_in_tile<'s, V: PolyVertex, R: AsRef<[V]>>(
1333 grid: Grid,
1334 tile: TileId,
1335 rings: &'s [R],
1336 s: &'s mut PolyScratch<V>,
1337) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
1338 let (min, max) = grid.tile_buffered_bounds(tile)?;
1339 let origin = grid.origin_of(min);
1340 s.rings.clear();
1341 s.copies.clear();
1342 for (index, ring) in (0_u32..).zip(rings) {
1343 let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), 0)? else {
1345 if index == 0 {
1346 return Ok(None); }
1348 continue;
1349 };
1350 match ring_meets_box(info.pts(rings, &s.copies), min, max) {
1351 RingClip::Outside if index == 0 => return Ok(None),
1353 RingClip::Covers if index > 0 => return Ok(None),
1354 _ => {}
1355 }
1356 check_local(&[lo, hi], origin)?;
1357 s.rings.push(info);
1358 }
1359 if s.rings.is_empty() {
1360 return Ok(None); }
1362 Ok(Some(PolygonPiece {
1363 tile,
1364 rings: PieceRings::Whole {
1365 rings: &s.rings,
1366 input: rings,
1367 copies: &s.copies,
1368 origin,
1369 },
1370 }))
1371}