1use core::cmp::Ordering;
37use core::iter;
38use core::ops::{Range, RangeInclusive};
39
40use geo_types::Coord;
41
42use crate::TileError;
43use crate::clip_polygon::{
44 ClipScratch, Closed, PieceBuf, Probe, RingClip, RingPlan, arcs_points, clip_ring,
45 clip_ring_planned, convex_hull, fill_box, keep_edge, lane_bounds, push_distinct,
46 ring_meets_box, scan_arcs,
47};
48use crate::clip_polyline::to_local;
49use crate::geom::{crossing_x_ceil, ring_orientation};
50use crate::grid::{Grid, Seg, ring_within};
51use crate::planar::{PAIR_BUDGET, PlanarScratch, Verdict, is_planar};
52use crate::slicer::{ClipMode, Local};
53use crate::tile::TileId;
54use crate::vertex::{PolyVertex, Vertex};
55
56const CHUNK: usize = 32;
58
59const LONG_EDGE: i64 = 4;
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71#[non_exhaustive]
72pub struct FillRun {
73 pub y: i32,
75 pub x: Range<i32>,
77 orient: Ordering,
79}
80
81#[derive(Debug)]
83pub enum PolygonSlice<'a, V, R> {
84 Piece(PolygonPiece<'a, V, R>),
86 Run(FillRun),
88}
89
90#[derive(Debug)]
93pub struct PolygonPiece<'a, V, R> {
94 tile: TileId,
95 rings: PieceRings<'a, V, R>,
96}
97
98#[derive(Debug)]
99enum PieceRings<'a, V, R> {
100 Built {
103 verts: &'a [V],
104 ends: &'a [u32],
105 origin: Coord<i32>,
106 },
107 Whole {
110 rings: &'a [RingInfo],
111 input: &'a [R],
112 copies: &'a [V],
113 origin: Coord<i32>,
114 },
115}
116
117impl<V, R> Clone for PieceRings<'_, V, R> {
119 fn clone(&self) -> Self {
120 *self
121 }
122}
123impl<V, R> Copy for PieceRings<'_, V, R> {}
124impl<V, R> Clone for PolygonPiece<'_, V, R> {
125 fn clone(&self) -> Self {
126 *self
127 }
128}
129impl<V, R> Copy for PolygonPiece<'_, V, R> {}
130
131impl<'a, V: Vertex, R: AsRef<[V]>> PolygonPiece<'a, V, R> {
132 #[must_use]
134 pub fn tile(&self) -> TileId {
135 self.tile
136 }
137
138 pub fn rings(&self) -> impl Iterator<Item = Local<'a, V>> + use<'a, V, R> {
140 let rings = self.rings;
141 let n = match rings {
142 PieceRings::Built { ends, .. } => ends.len(),
143 PieceRings::Whole { rings, .. } => rings.len(),
144 };
145 (0..n).map(move |i| match rings {
146 PieceRings::Built {
147 verts,
148 ends,
149 origin,
150 } => {
151 let start = i.checked_sub(1).map_or(0, |p| ends[p] as usize);
152 Local::ring(&verts[start..ends[i] as usize], origin, true)
153 }
154 PieceRings::Whole {
155 rings,
156 input,
157 copies,
158 origin,
159 } => Local::ring(rings[i].pts(input, copies), origin, false),
160 })
161 }
162}
163
164#[derive(Debug, Clone, Copy)]
166enum Src {
167 Input,
169 Copy(usize),
171}
172
173#[derive(Debug, Clone, Copy)]
177struct RingInfo {
178 input: u32,
179 src: Src,
180 len: u32,
181 edge0: u32,
182}
183
184impl RingInfo {
185 fn pts<'a, V, R: AsRef<[V]>>(&self, input: &'a [R], copies: &'a [V]) -> &'a [V] {
186 let len = self.len as usize;
187 match self.src {
188 Src::Input => &input[self.input as usize].as_ref()[..len],
189 Src::Copy(start) => &copies[start..start + len],
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy)]
196struct Chunk {
197 ring: u32,
198 first: u32,
199 count: u32,
200 rows: (i64, i64),
201}
202
203#[derive(Debug, Clone, Copy)]
206struct Hit {
207 tx: i32,
208 edge: u32,
209 ring: u32,
210}
211
212#[derive(Debug, Clone, Copy)]
215struct Span {
216 first: i32,
217 last: i32,
218 edge: u32,
219 ring: u32,
220}
221
222#[derive(Debug, Clone, Copy)]
226struct Crossing {
227 edge: u32,
228 ring: u32,
229 up: bool,
230 right_of: i64,
231}
232
233impl Crossing {
234 fn sign(self) -> i64 {
235 if self.up { 1 } else { -1 }
236 }
237}
238
239fn row_center(ty: i64, extent: i64) -> i64 {
243 ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
244}
245
246fn fenwick_build(tree: &mut Vec<i64>, values: impl Iterator<Item = i64>) {
248 tree.clear();
249 tree.extend(values);
250 for i in 0..tree.len() {
251 let parent = i | (i + 1);
252 if parent < tree.len() {
253 tree[parent] += tree[i];
254 }
255 }
256}
257
258fn fenwick_add(tree: &mut [i64], mut i: usize, delta: i64) {
260 while i < tree.len() {
261 tree[i] += delta;
262 i |= i + 1;
263 }
264}
265
266fn fenwick_prefix(tree: &[i64], mut end: usize) -> i64 {
268 let mut sum = 0;
269 while end > 0 {
270 sum += tree[end - 1];
271 end &= end - 1;
272 }
273 sum
274}
275
276fn sort_hits(hits: &mut [Hit], tmp: &mut Vec<Hit>, counts: &mut Vec<usize>) {
280 let Some(head) = hits.first() else {
281 return;
282 };
283 let (lo, hi) = hits.iter().fold((head.tx, head.tx), |(lo, hi), h| {
284 (lo.min(h.tx), hi.max(h.tx))
285 });
286 let columns = hi.abs_diff(lo) as usize + 1;
287 if columns == 1 {
288 } else if hits.len() < 16 || columns > hits.len().saturating_mul(4) {
290 hits.sort_unstable_by_key(|h| (h.tx, h.edge));
291 } else {
292 counts.clear();
293 counts.resize(columns, 0);
294 for h in hits.iter() {
295 counts[h.tx.abs_diff(lo) as usize] += 1;
296 }
297 let mut start = 0;
299 for c in counts.iter_mut() {
300 (*c, start) = (start, start + *c);
301 }
302 tmp.clear();
303 tmp.extend_from_slice(hits);
304 for h in tmp.iter() {
305 let at = &mut counts[h.tx.abs_diff(lo) as usize];
306 hits[*at] = *h;
307 *at += 1;
308 }
309 }
310 debug_assert!(hits.is_sorted_by_key(|h| (h.tx, h.edge)));
311}
312
313fn check_local<V: Vertex>(verts: &[V], origin: Coord<i32>) -> Result<(), TileError> {
315 for &v in verts {
316 to_local(v, origin)?;
317 }
318 Ok(())
319}
320
321fn merge_by_edge<T: Copy>(
325 spans: &mut Vec<T>,
326 kept: usize,
327 tmp: &mut Vec<T>,
328 edge: impl Fn(&T) -> u32,
329) {
330 let added = &mut spans[kept..];
331 if !added.is_sorted_by_key(&edge) {
332 added.sort_unstable_by_key(&edge);
333 }
334 if kept == 0 || kept == spans.len() || edge(&spans[kept - 1]) < edge(&spans[kept]) {
335 return;
336 }
337 tmp.clear();
338 let (old, new) = spans.split_at(kept);
339 let (mut i, mut j) = (0, 0);
340 while i < old.len() && j < new.len() {
341 if edge(&old[i]) < edge(&new[j]) {
342 tmp.push(old[i]);
343 i += 1;
344 } else {
345 tmp.push(new[j]);
346 j += 1;
347 }
348 }
349 tmp.extend_from_slice(&old[i..]);
350 tmp.extend_from_slice(&new[j..]);
351 core::mem::swap(spans, tmp);
352}
353
354fn ring_groups(mut hits: &[Hit]) -> impl Iterator<Item = &[Hit]> {
357 iter::from_fn(move || {
358 let ring = hits.first()?.ring;
359 let (group, rest) = hits.split_at(hits.partition_point(|x| x.ring == ring));
360 hits = rest;
361 Some(group)
362 })
363}
364
365fn excursion_winding(
368 cs: &[Crossing],
369 fenwick: &[i64],
370 ring: RingInfo,
371 first: usize,
372 count: usize,
373) -> i64 {
374 let (start, len) = (ring.edge0 as usize, ring.len as usize);
375 let sum = |lo: usize, hi: usize| {
376 let i = cs.partition_point(|c| (c.edge as usize) < lo);
377 let j = cs.partition_point(|c| (c.edge as usize) < hi);
378 fenwick_prefix(fenwick, j) - fenwick_prefix(fenwick, i)
379 };
380 if first + count <= len {
381 sum(start + first, start + first + count)
382 } else {
383 sum(start + first, start + len) + sum(start, start + first + count - len)
384 }
385}
386
387pub(crate) struct PolyScratch<V> {
390 rings: Vec<RingInfo>,
392 copies: Vec<V>,
393 chunks: Vec<Chunk>,
394 order: Vec<u32>,
396 active: Vec<u32>,
397 hits: Vec<Hit>,
402 long: Vec<Span>,
403 long_cover: Vec<Span>,
404 covering: Vec<Hit>,
405 crossings: Vec<Crossing>,
406 by_x: Vec<u32>,
407 hits_tmp: Vec<Hit>,
409 spans_tmp: Vec<Span>,
410 counts: Vec<usize>,
411 fenwick: Vec<i64>,
412 inside: Vec<bool>,
414 piece: PieceBuf<V>,
416 arcs: Vec<(usize, usize)>,
417 clip: ClipScratch<V>,
418 segs: PlanarScratch,
420 probe: Probe,
422 frame_hull: Vec<Coord<i32>>,
423 depths: Vec<(u32, u32)>,
426 ring_bases: Vec<usize>,
427 probe_arcs: Vec<(usize, usize)>,
429 probe_rings: Vec<Option<(usize, Range<usize>)>>,
430}
431
432impl<V> PolyScratch<V> {
433 pub(crate) const fn new() -> Self {
434 Self {
435 rings: Vec::new(),
436 copies: Vec::new(),
437 chunks: Vec::new(),
438 order: Vec::new(),
439 active: Vec::new(),
440 hits: Vec::new(),
441 long: Vec::new(),
442 long_cover: Vec::new(),
443 covering: Vec::new(),
444 crossings: Vec::new(),
445 by_x: Vec::new(),
446 hits_tmp: Vec::new(),
447 spans_tmp: Vec::new(),
448 counts: Vec::new(),
449 fenwick: Vec::new(),
450 inside: Vec::new(),
451 piece: PieceBuf::new(),
452 arcs: Vec::new(),
453 clip: ClipScratch::new(),
454 segs: PlanarScratch::new(),
455 probe: Probe::new(),
456 frame_hull: Vec::new(),
457 depths: Vec::new(),
458 ring_bases: Vec::new(),
459 probe_arcs: Vec::new(),
460 probe_rings: Vec::new(),
461 }
462 }
463}
464
465pub(crate) struct Scratch<V>(Option<Box<PolyState<V>>>);
469
470pub(crate) struct PolyState<V> {
473 pub(crate) scratch: PolyScratch<V>,
474 sweep: Option<Sweep>,
475 small: Option<SmallWalk>,
477}
478
479impl<V> Scratch<V> {
480 pub(crate) const fn new() -> Self {
481 Self(None)
482 }
483
484 #[inline]
486 pub(crate) fn get(&mut self) -> &mut PolyState<V> {
487 #[cold]
488 fn alloc<V>() -> Box<PolyState<V>> {
489 Box::new(PolyState {
490 scratch: PolyScratch::new(),
491 sweep: None,
492 small: None,
493 })
494 }
495 self.0.get_or_insert_with(alloc)
496 }
497}
498
499impl<V> Clone for Scratch<V> {
500 fn clone(&self) -> Self {
501 Self::new()
502 }
503}
504
505impl<V> PartialEq for Scratch<V> {
506 fn eq(&self, _: &Self) -> bool {
507 true
508 }
509}
510
511impl<V> Eq for Scratch<V> {}
512
513impl<V> core::fmt::Debug for Scratch<V> {
514 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
515 f.debug_struct("PolyScratch").finish_non_exhaustive()
516 }
517}
518
519pub(crate) fn fill_ring<V: PolyVertex>(grid: Grid, run: &FillRun) -> [V; 5] {
523 let (extent, buffer) = (grid.extent_i32(), grid.buffer_i32());
524 let sw = Coord {
525 x: -buffer - 1,
526 y: -buffer - 1,
527 };
528 let ne = Coord {
529 x: extent + buffer,
530 y: extent + buffer,
531 };
532 fill_box(run.orient, sw, ne).map(V::synthetic_at)
533}
534
535enum Event {
537 Whole {
539 tile: TileId,
540 origin: Coord<i32>,
541 },
542 Built {
544 tile: TileId,
545 origin: Coord<i32>,
546 },
547 Run(FillRun),
548}
549
550struct Sweep {
552 rows: RangeInclusive<i64>,
554 next_chunk: usize,
556 in_row: bool,
559 ty: i32,
560 k: usize,
561 h: usize,
562 lh: usize,
563 next: i64,
564 cover: Option<Range<usize>>,
567 long_last: i64,
569 holes_in: u32,
571 pending: Option<(i64, i64)>,
573 ready: Option<FillRun>,
576 tile_ready: Option<i32>,
577 orient: Option<Ordering>,
579 cols: RangeInclusive<i32>,
581}
582
583pub struct PolygonSlices<'s, V, R> {
589 grid: Grid,
590 whole: bool,
592 rings: &'s [R],
593 s: &'s mut PolyScratch<V>,
594 start: Option<Start>,
596 sweep: &'s mut Option<Sweep>,
597 small: &'s mut Option<SmallWalk>,
598 done: bool,
599 cols: RangeInclusive<i32>,
601 rows: RangeInclusive<i32>,
602}
603
604enum Start {
607 Whole { tile: TileId, origin: Coord<i32> },
609 Sweep { rows: RangeInclusive<i64> },
611 Small { x0: i32, x1: i32, y0: i32, y1: i32 },
614}
615
616#[derive(Clone, Copy)]
618struct SmallWalk {
619 x: i32,
620 y: i32,
621 x0: i32,
622 x1: i32,
623 y1: i32,
624}
625
626const SMALL_VERTICES: u32 = 128;
628
629impl<'s, V: PolyVertex, R: AsRef<[V]>> PolygonSlices<'s, V, R> {
630 #[cfg_attr(feature = "hotpath", hotpath::measure)]
632 #[inline]
633 pub(crate) fn new(
634 grid: Grid,
635 mode: ClipMode,
636 rings: &'s [R],
637 state: &'s mut PolyState<V>,
638 ) -> Result<Self, TileError> {
639 let PolyState {
640 scratch: s,
641 sweep,
642 small,
643 } = state;
644 *sweep = None;
645 *small = None;
646 let whole = mode == ClipMode::Unclipped;
647 let start = prepare(grid, rings, s, !whole)?;
648 Ok(Self {
649 grid,
650 whole,
651 rings,
652 s,
653 start,
654 sweep,
655 small,
656 done: false,
657 cols: i32::MIN..=i32::MAX,
658 rows: i32::MIN..=i32::MAX,
659 })
660 }
661
662 #[must_use]
665 pub fn within(mut self, min: TileId, max: TileId) -> Self {
666 debug_assert!(self.sweep.is_none(), "within() before the first next()");
667 self.cols = min.x..=max.x;
668 self.rows = min.y..=max.y;
669 self
670 }
671
672 #[expect(
676 clippy::should_implement_trait,
677 reason = "a lending iterator: each item borrows the iterator"
678 )]
679 pub fn next(&mut self) -> Option<Result<PolygonSlice<'_, V, R>, TileError>> {
680 if self.done {
681 return None;
682 }
683 if let Some(&Start::Whole { tile, origin }) = self.start.as_ref() {
685 self.start = None;
686 self.done = true;
687 let inside = self.cols.contains(&tile.x) && self.rows.contains(&tile.y);
688 return inside.then(|| Ok(self.view(Event::Whole { tile, origin })));
689 }
690 match self.step() {
691 Ok(Some(event)) => Some(Ok(self.view(event))),
692 Ok(None) => {
693 self.done = true;
694 None
695 }
696 Err(e) => {
697 self.done = true;
698 Some(Err(e))
699 }
700 }
701 }
702
703 #[must_use]
709 pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
710 fill_ring(self.grid, run)
711 }
712
713 fn view(&self, event: Event) -> PolygonSlice<'_, V, R> {
714 let (tile, rings) = match event {
715 Event::Run(run) => return PolygonSlice::Run(run),
716 Event::Built { tile, origin } => (
717 tile,
718 PieceRings::Built {
719 verts: self.s.piece.verts(),
720 ends: self.s.piece.ring_ends(),
721 origin,
722 },
723 ),
724 Event::Whole { tile, origin } => (
725 tile,
726 PieceRings::Whole {
727 rings: &self.s.rings,
728 input: self.rings,
729 copies: &self.s.copies,
730 origin,
731 },
732 ),
733 };
734 PolygonSlice::Piece(PolygonPiece { tile, rings })
735 }
736
737 #[inline(always)]
739 #[expect(
740 clippy::inline_always,
741 reason = "the walk of most polygons that cross a tile edge; out of line it costs them ~2%"
742 )]
743 fn step_small(&mut self) -> Result<Option<Event>, TileError> {
744 let Some(walk) = self.small.as_mut() else {
745 return Ok(None);
746 };
747 while walk.y <= walk.y1 {
748 let tile = TileId::new(walk.x, walk.y);
749 if walk.x < walk.x1 {
750 walk.x += 1;
751 } else {
752 (walk.x, walk.y) = (walk.x0, walk.y + 1);
753 }
754 let (min, max) = self.grid.tile_box(tile);
756 if let [info] = self.s.rings[..]
759 && let span = max.x.wrapping_sub(min.x).cast_unsigned()
761 && ring_within(info.pts(self.rings, &self.s.copies), min, span).is_some()
762 {
763 let origin = self.grid.origin_of(min);
764 return Ok(Some(Event::Whole { tile, origin }));
765 }
766 let built = build_tile_piece(self.grid, min, max, self.rings, self.s)?;
767 if let Some(origin) = built {
768 return Ok(Some(Event::Built { tile, origin }));
769 }
770 }
771 *self.small = None;
772 Ok(None)
773 }
774
775 fn step(&mut self) -> Result<Option<Event>, TileError> {
776 match self.start.take() {
777 Some(Start::Whole { .. }) | None => {}
779 Some(Start::Sweep { rows }) => {
780 let first = (*rows.start()).max(i64::from(*self.rows.start()));
781 let last = (*rows.end()).min(i64::from(*self.rows.end()));
782 *self.sweep = Some(Sweep::new(first..=last, self.cols.clone()));
783 }
784 Some(Start::Small { x0, x1, y0, y1 }) => {
785 let (x0, x1) = (x0.max(*self.cols.start()), x1.min(*self.cols.end()));
786 let (y0, y1) = (y0.max(*self.rows.start()), y1.min(*self.rows.end()));
787 if x0 <= x1 {
788 *self.small = Some(SmallWalk {
789 x: x0,
790 y: y0,
791 x0,
792 x1,
793 y1,
794 });
795 }
796 }
797 }
798 let Some(sw) = self.sweep.as_mut() else {
800 return self.step_small();
801 };
802 let mut cx = Cx {
803 grid: self.grid,
804 s: &mut *self.s,
805 rings: self.rings,
806 };
807 if self.whole {
808 sw.step_whole(&mut cx)
809 } else {
810 sw.step::<false, V, R>(&mut cx)
811 }
812 }
813}
814
815const LONE_RING: usize = 16;
817
818#[inline(never)]
823fn prepare<V: PolyVertex, R: AsRef<[V]>>(
824 grid: Grid,
825 rings: &[R],
826 s: &mut PolyScratch<V>,
827 allow_small: bool,
828) -> Result<Option<Start>, TileError> {
829 let (extent, buffer) = (i64::from(grid.extent()), i64::from(grid.buffer()));
830 s.rings.clear();
831 s.copies.clear();
832 if let [ring] = rings
835 && let ring = ring.as_ref()
836 && (3..=LONE_RING).contains(&ring.len())
837 && let Some((tile, origin, vertices)) = grid.inner_tile_of_ring(ring)
838 {
839 s.rings.push(RingInfo {
840 input: 0,
841 src: Src::Input,
842 len: u32::try_from(vertices).unwrap_or(0),
843 edge0: 0,
844 });
845 return Ok(Some(Start::Whole { tile, origin }));
846 }
847 u32::try_from(rings.len()).map_err(|_| TileError::GeometryTooLarge)?;
848 let mut lo = Coord {
849 x: i32::MAX,
850 y: i32::MAX,
851 };
852 let mut hi = Coord {
853 x: i32::MIN,
854 y: i32::MIN,
855 };
856 let mut edges = 0_u32;
857 for (index, ring) in (0_u32..).zip(rings) {
858 let Some((info, rlo, rhi)) = load_ring(s, index, ring.as_ref(), edges)? else {
859 if index == 0 {
860 return Ok(None); }
862 continue;
863 };
864 edges = edges
865 .checked_add(info.len)
866 .ok_or(TileError::GeometryTooLarge)?;
867 s.rings.push(info);
868 (lo.x, lo.y) = (lo.x.min(rlo.x), lo.y.min(rlo.y));
869 (hi.x, hi.y) = (hi.x.max(rhi.x), hi.y.max(rhi.y));
870 }
871
872 if let Some((tile, origin)) = grid.inner_tile(lo, hi) {
875 return Ok(Some(Start::Whole { tile, origin }));
876 }
877 grid.check_polygon_bounds(lo, hi)?;
878
879 if allow_small
882 && edges <= SMALL_VERTICES
883 && i64::from(hi.x) - i64::from(lo.x) < extent
884 && i64::from(hi.y) - i64::from(lo.y) < extent
885 {
886 let tile = |c: i32, d: i64| {
887 i32::try_from((i64::from(c) + d).div_euclid(extent)).unwrap_or(0)
889 };
890 return Ok(Some(Start::Small {
891 x0: tile(lo.x, -buffer),
892 x1: tile(hi.x, buffer),
893 y0: tile(lo.y, -buffer),
894 y1: tile(hi.y, buffer),
895 }));
896 }
897
898 s.chunks.clear();
899 index_chunks(s, rings, extent, buffer);
900 s.order.clear();
901 #[expect(
902 clippy::cast_possible_truncation,
903 reason = "at most one chunk per edge"
904 )]
905 s.order.extend(0..s.chunks.len() as u32);
906 let chunks = &s.chunks;
907 s.order.sort_unstable_by_key(|&c| chunks[c as usize].rows.0);
908 s.active.clear();
909 s.inside.clear();
910 s.inside.resize(s.rings.len(), false);
911 let first = (i64::from(lo.y) - buffer).div_euclid(extent);
912 let last = (i64::from(hi.y) + buffer).div_euclid(extent);
913 Ok(Some(Start::Sweep { rows: first..=last }))
914}
915
916struct Cx<'x, V, R> {
918 grid: Grid,
919 s: &'x mut PolyScratch<V>,
920 rings: &'x [R],
921}
922
923impl Sweep {
924 fn new(rows: RangeInclusive<i64>, cols: RangeInclusive<i32>) -> Self {
926 Self {
927 rows,
928 next_chunk: 0,
929 in_row: false,
930 ty: 0,
931 k: 0,
932 h: 0,
933 next: i64::MIN,
934 holes_in: 0,
935 pending: None,
936 ready: None,
937 tile_ready: None,
938 orient: None,
939 lh: 0,
940 cover: Some(0..0),
941 long_last: i64::MIN,
942 cols,
943 }
944 }
945
946 #[inline(never)]
950 fn step_whole<V: PolyVertex, R: AsRef<[V]>>(
951 &mut self,
952 cx: &mut Cx<'_, V, R>,
953 ) -> Result<Option<Event>, TileError> {
954 self.step::<true, V, R>(cx)
955 }
956
957 fn step<const WHOLE: bool, V: PolyVertex, R: AsRef<[V]>>(
960 &mut self,
961 cx: &mut Cx<'_, V, R>,
962 ) -> Result<Option<Event>, TileError> {
963 loop {
964 if let Some(run) = self.ready.take() {
965 return Ok(Some(if WHOLE {
966 self.whole_run_tile(cx.grid, run)
967 } else {
968 Event::Run(run)
969 }));
970 }
971 if let Some(tx) = self.tile_ready.take() {
972 let tile = TileId::new(tx, self.ty);
973 if WHOLE {
974 if let Some(event) = self.whole_tile(cx.grid, cx.s, tile) {
975 return Ok(Some(event));
976 }
977 } else if let Some(origin) = self.build_tile(cx, tile)? {
978 return Ok(Some(Event::Built { tile, origin }));
979 }
980 continue;
981 }
982 if !self.in_row {
983 let Some(row) = self.rows.next() else {
984 return Ok(None);
985 };
986 self.load_row(cx, row)?;
987 self.ty = i32::try_from(row).map_err(|_| TileError::Overflow)?;
988 (self.in_row, self.k, self.h, self.lh, self.next) = (true, 0, 0, 0, i64::MIN);
989 (self.cover, self.long_last) = (Some(0..0), i64::MIN);
990 continue;
991 }
992 if self.next > i64::from(*self.cols.end()) {
993 self.ready = self.take_pending();
995 cx.s.inside.fill(false);
996 self.holes_in = 0;
997 self.in_row = false;
998 continue;
999 }
1000 let s = &*cx.s;
1002 let bound = s
1003 .by_x
1004 .get(self.k)
1005 .map_or(i64::MAX, |&i| s.crossings[i as usize].right_of);
1006 let tile = if s.long.is_empty() {
1008 s.hits.get(self.h).map(|c| i64::from(c.tx))
1009 } else {
1010 let next = self.next;
1013 let covered =
1015 (self.long_last != i64::MIN && self.long_last >= next).then_some(next);
1016 let short = s.hits.get(self.h).map(|c| i64::from(c.tx));
1017 let long = s.long.get(self.lh).map(|c| i64::from(c.first).max(next));
1018 [covered, short, long].into_iter().flatten().min()
1019 };
1020 if let Some(tx) = tile
1021 && tx <= bound
1022 {
1023 let tx = i32::try_from(tx).map_err(|_| TileError::Overflow)?;
1024 let s = &mut *cx.s;
1025 let start = self.h;
1027 self.h += s.hits[start..].partition_point(|c| c.tx == tx);
1028 self.cover = Some(start..self.h);
1029 if !s.long.is_empty() {
1030 self.cover_long(s, tx, start);
1031 }
1032 self.fill(cx, self.next, i64::from(tx))?;
1033 self.ready = self.take_pending();
1035 self.tile_ready = self.cols.contains(&tx).then_some(tx);
1036 self.next = i64::from(tx) + 1;
1037 continue;
1038 }
1039 if self.k == s.by_x.len() {
1040 debug_assert!(cx.s.inside.iter().all(|&i| !i), "parity ends even");
1042 self.ready = self.take_pending();
1043 self.in_row = false;
1044 continue;
1045 }
1046 self.fill(cx, self.next, bound + 1)?;
1048 self.next = bound + 1;
1049 let s = &mut *cx.s;
1051 while let Some(&i) = s.by_x.get(self.k)
1052 && s.crossings[i as usize].right_of == bound
1053 {
1054 let crossing = s.crossings[i as usize];
1055 let r = crossing.ring as usize;
1056 s.inside[r] = !s.inside[r];
1057 if r != 0 {
1058 if s.inside[r] {
1059 self.holes_in += 1;
1060 } else {
1061 self.holes_in = self.holes_in.saturating_sub(1);
1064 }
1065 }
1066 fenwick_add(&mut s.fenwick, i as usize, -crossing.sign());
1067 self.k += 1;
1068 }
1069 if !(s.inside[0] && self.holes_in == 0) {
1071 self.ready = self.take_pending();
1072 }
1073 }
1074 }
1075
1076 fn whole_run_tile(&mut self, grid: Grid, mut run: FillRun) -> Event {
1079 let tile = TileId::new(run.x.start, run.y);
1080 run.x.start += 1;
1081 if !run.x.is_empty() {
1082 self.ready = Some(run);
1083 }
1084 Event::Whole {
1085 tile,
1086 origin: grid.tile_origin(tile),
1087 }
1088 }
1089
1090 fn cover_long<V>(&mut self, s: &mut PolyScratch<V>, tx: i32, start: usize) {
1093 if self.long_last < i64::from(tx) {
1094 s.long_cover.clear();
1095 } else {
1096 s.long_cover.retain(|c| c.last >= tx);
1097 }
1098 let kept = s.long_cover.len();
1099 while let Some(&span) = s.long.get(self.lh)
1100 && span.first <= tx
1101 {
1102 s.long_cover.push(span);
1103 self.lh += 1;
1104 }
1105 if s.long_cover.is_empty() {
1106 self.long_last = i64::MIN;
1107 return;
1108 }
1109 merge_by_edge(&mut s.long_cover, kept, &mut s.spans_tmp, |c| c.edge);
1110 self.long_last = s
1111 .long_cover
1112 .iter()
1113 .map(|c| i64::from(c.last))
1114 .max()
1115 .unwrap_or(i64::MIN);
1116 s.covering.clear();
1117 s.covering.extend(s.long_cover.iter().map(|c| Hit {
1118 tx,
1119 edge: c.edge,
1120 ring: c.ring,
1121 }));
1122 let kept = s.covering.len();
1123 s.covering.extend_from_slice(&s.hits[start..self.h]);
1124 merge_by_edge(&mut s.covering, kept, &mut s.hits_tmp, |c| c.edge);
1125 self.cover = None;
1126 }
1127
1128 fn take_pending(&mut self) -> Option<FillRun> {
1130 let (start, end) = self.pending.take()?;
1131 #[expect(
1132 clippy::cast_possible_truncation,
1133 reason = "both ends were checked to fit i32 when the span was recorded"
1134 )]
1135 Some(FillRun {
1136 y: self.ty,
1137 x: start as i32..end as i32,
1138 orient: self.orient.unwrap_or(Ordering::Equal),
1139 })
1140 }
1141
1142 fn fill<V: PolyVertex, R: AsRef<[V]>>(
1145 &mut self,
1146 cx: &mut Cx<'_, V, R>,
1147 start: i64,
1148 end: i64,
1149 ) -> Result<(), TileError> {
1150 let covered = cx.s.inside.first().copied().unwrap_or(false) && self.holes_in == 0;
1151 if start >= end || !covered {
1152 return Ok(());
1153 }
1154 i32::try_from(start).map_err(|_| TileError::Overflow)?;
1155 i32::try_from(end).map_err(|_| TileError::Overflow)?;
1156 let start = start.max(i64::from(*self.cols.start()));
1157 let end = end.min(i64::from(*self.cols.end()) + 1);
1158 if start >= end {
1159 return Ok(());
1160 }
1161 if self.orient.is_none() {
1162 let ext = cx.s.rings[0].pts(cx.rings, &cx.s.copies);
1163 self.orient = Some(ring_orientation(ext));
1164 }
1165 match &mut self.pending {
1166 Some((_, e)) if *e == start => *e = end,
1167 pending => {
1168 debug_assert!(
1169 pending.is_none(),
1170 "an edge tile or row end yields a span first"
1171 );
1172 *pending = Some((start, end));
1173 }
1174 }
1175 Ok(())
1176 }
1177
1178 #[cfg_attr(feature = "hotpath", hotpath::measure)]
1180 #[cfg_attr(
1182 not(feature = "hotpath"),
1183 expect(
1184 clippy::many_single_char_names,
1185 reason = "edge endpoints and the scratch"
1186 )
1187 )]
1188 fn load_row<V: PolyVertex, R: AsRef<[V]>>(
1189 &mut self,
1190 cx: &mut Cx<'_, V, R>,
1191 ty: i64,
1192 ) -> Result<(), TileError> {
1193 let (extent, buffer) = (i64::from(cx.grid.extent()), i64::from(cx.grid.buffer()));
1194 let s = &mut *cx.s;
1195 let rings = cx.rings;
1196 while let Some(&c) = s.order.get(self.next_chunk)
1197 && s.chunks[c as usize].rows.0 <= ty
1198 {
1199 let at = s.active.partition_point(|&a| a < c);
1200 s.active.insert(at, c);
1201 self.next_chunk += 1;
1202 }
1203 let chunks = &s.chunks;
1204 s.active.retain(|&c| chunks[c as usize].rows.1 >= ty);
1205
1206 s.hits.clear();
1207 s.long.clear();
1208 s.long_cover.clear();
1209 s.covering.clear();
1210 s.crossings.clear();
1211 let (slab_lo, slab_hi) = (ty * extent - buffer, ty * extent + extent - 1 + buffer);
1212 let cy = row_center(ty, extent);
1213 for &c in &s.active {
1214 let chunk = s.chunks[c as usize];
1215 let info = s.rings[chunk.ring as usize];
1216 let pts = info.pts(rings, &s.copies);
1217 let m = pts.len();
1218 for i in chunk.first as usize..(chunk.first + chunk.count) as usize {
1219 let (a, b) = (pts[i].position(), pts[(i + 1) % m].position());
1220 let (lo, hi) = if a.y <= b.y { (a, b) } else { (b, a) };
1221 if i64::from(hi.y) < slab_lo || i64::from(lo.y) > slab_hi {
1222 continue;
1223 }
1224 #[expect(clippy::cast_possible_truncation, reason = "edge ids fit u32")]
1225 let edge = info.edge0 + i as u32;
1226 let wide = |p: Coord<i32>| Coord {
1227 x: i64::from(p.x),
1228 y: i64::from(p.y),
1229 };
1230 let (first, last) = Seg::new(wide(lo), wide(hi)).row_span(ty, extent, buffer);
1231 let fits = |v: i64| i32::try_from(v).map_err(|_| TileError::Overflow);
1232 if last - first + 1 >= LONG_EDGE {
1233 s.long.push(Span {
1234 first: fits(first)?,
1235 last: fits(last)?,
1236 edge,
1237 ring: chunk.ring,
1238 });
1239 } else {
1240 for tx in first..=last {
1241 s.hits.push(Hit {
1242 tx: fits(tx)?,
1243 edge,
1244 ring: chunk.ring,
1245 });
1246 }
1247 }
1248 if i64::from(lo.y) <= cy && cy < i64::from(hi.y) {
1250 let x =
1251 crossing_x_ceil(a, b, i32::try_from(cy).map_err(|_| TileError::Overflow)?);
1252 s.crossings.push(Crossing {
1253 edge,
1254 ring: chunk.ring,
1255 up: b.y > a.y,
1256 right_of: (x - extent - buffer).div_euclid(extent),
1258 });
1259 }
1260 }
1261 }
1262 sort_hits(&mut s.hits, &mut s.hits_tmp, &mut s.counts);
1263 s.long.sort_unstable_by_key(|c| (c.first, c.edge));
1264 debug_assert!(s.crossings.is_sorted_by_key(|c| c.edge));
1265 s.by_x.clear();
1266 #[expect(clippy::cast_possible_truncation, reason = "one crossing per edge")]
1267 s.by_x.extend(0..s.crossings.len() as u32);
1268 let crossings = &s.crossings;
1269 s.by_x
1270 .sort_unstable_by_key(|&i| crossings[i as usize].right_of);
1271 fenwick_build(&mut s.fenwick, s.crossings.iter().map(|c| c.sign()));
1272 Ok(())
1273 }
1274
1275 #[expect(
1280 clippy::inline_always,
1281 reason = "run per edge tile; out of line it costs the polygon benchmarks 2%"
1282 )]
1283 #[inline(always)]
1284 fn tile_hits<'x>(
1285 &self,
1286 hits: &'x [Hit],
1287 covering: &'x [Hit],
1288 inside: &[bool],
1289 ) -> Option<&'x [Hit]> {
1290 let hits = match &self.cover {
1291 Some(range) => &hits[range.clone()],
1292 None => covering,
1293 };
1294 let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1296 if !exterior_touched && !inside[0] {
1297 return None; }
1299 let holes_in = self.holes_in as usize;
1302 if holes_in > 0
1303 && holes_in
1304 > ring_groups(hits)
1305 .filter(|g| g[0].ring != 0 && inside[g[0].ring as usize])
1306 .count()
1307 {
1308 return None;
1309 }
1310 Some(hits)
1311 }
1312
1313 fn whole_tile<V>(&self, grid: Grid, s: &PolyScratch<V>, tile: TileId) -> Option<Event> {
1315 self.tile_hits(&s.hits, &s.covering, &s.inside)?;
1316 let origin = grid.tile_origin(tile);
1317 Some(Event::Whole { tile, origin })
1318 }
1319
1320 #[cfg_attr(feature = "hotpath", hotpath::measure)]
1324 fn build_tile<V: PolyVertex, R: AsRef<[V]>>(
1325 &mut self,
1326 cx: &mut Cx<'_, V, R>,
1327 tile: TileId,
1328 ) -> Result<Option<Coord<i32>>, TileError> {
1329 let (min, max) = cx.grid.tile_buffered_bounds(tile)?;
1330 let origin = cx.grid.origin_of(min);
1331 let s = &mut *cx.s;
1332 let rings = cx.rings;
1333 let Some(hits) = self.tile_hits(&s.hits, &s.covering, &s.inside) else {
1334 return Ok(None);
1335 };
1336 let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1337 s.piece.clear();
1339 if !exterior_touched {
1340 let orient = *self
1342 .orient
1343 .get_or_insert_with(|| ring_orientation(s.rings[0].pts(rings, &s.copies)));
1344 s.piece.push_fill_box(orient, min, max)?;
1345 }
1346 for ring_hits in ring_groups(hits) {
1347 let info = s.rings[ring_hits[0].ring as usize];
1348 s.arcs.clear();
1349 for x in ring_hits {
1350 keep_edge(&mut s.arcs, (x.edge - info.edge0) as usize);
1351 }
1352 let (cs, fenwick) = (&s.crossings, &s.fenwick);
1353 let winding =
1354 |first: usize, count: usize| excursion_winding(cs, fenwick, info, first, count);
1355 let pts = info.pts(rings, &s.copies);
1356 let (arcs, piece) = (&mut s.arcs, &mut s.piece);
1357 s.clip.close_ring(pts, arcs, (min, max), winding, piece)?;
1358 }
1359 if !self.make_valid(s, rings, (min, max), origin, exterior_touched)? {
1360 return Ok(None);
1361 }
1362 Ok(Some(origin))
1363 }
1364
1365 #[inline(never)]
1369 fn make_valid<V: PolyVertex, R: AsRef<[V]>>(
1370 &self,
1371 s: &mut PolyScratch<V>,
1372 rings: &[R],
1373 (min, max): (Coord<i32>, Coord<i32>),
1374 origin: Coord<i32>,
1375 exterior_touched: bool,
1376 ) -> Result<bool, TileError> {
1377 let Some(budget) = needs_repair(s, (min, max)) else {
1378 return Ok(true);
1379 };
1380 let fill = (!exterior_touched).then(|| self.orient.unwrap_or(Ordering::Equal));
1382 let whole_fits = fits_whole(rings);
1383 repair(
1384 s,
1385 (min, max),
1386 origin,
1387 rings.len(),
1388 whole_fits,
1389 budget,
1390 |s, a, b| self.piece(s, rings, (min, max), fill, a, b),
1391 )
1392 }
1393
1394 #[inline]
1398 fn piece<V: PolyVertex, R: AsRef<[V]>>(
1399 &self,
1400 s: &mut PolyScratch<V>,
1401 rings: &[R],
1402 (min, max): (Coord<i32>, Coord<i32>),
1403 fill: Option<Ordering>,
1404 attempt: Attempt,
1405 budget: &mut usize,
1406 ) -> Result<Built, TileError> {
1407 let hits = match &self.cover {
1409 Some(range) => &s.hits[range.clone()],
1410 None => &s.covering[..],
1411 };
1412 let covered = fill.is_some();
1413 if attempt == Attempt::ENCLOSE && (!covered || rings.len() == 1) {
1415 return Ok(Built::Failed);
1416 }
1417 s.piece.clear();
1418 s.clip.start_attempt();
1419 if attempt.frame {
1420 let probed = adapt_frame(
1421 |depths, probe, bases| {
1422 bases.clear();
1423 bases.resize(rings.len(), 0);
1424 for ring_hits in ring_groups(hits) {
1425 let info = s.rings[ring_hits[0].ring as usize];
1426 s.arcs.clear();
1427 for x in ring_hits {
1428 keep_edge(&mut s.arcs, (x.edge - info.edge0) as usize);
1429 }
1430 let plan = RingPlan {
1431 depths: Some(depths),
1432 bridge_base: probe.bridges,
1433 ..RingPlan::PLAIN
1434 };
1435 bases[info.input as usize] = probe.bridges;
1436 let pts = info.pts(rings, &s.copies);
1437 arcs_points(pts, &mut s.arcs, plan, (min, max), probe);
1438 }
1439 Ok(())
1440 },
1441 &mut s.depths,
1442 &mut s.probe,
1443 &mut s.frame_hull,
1444 &mut s.ring_bases,
1445 (min, max),
1446 budget,
1447 )?;
1448 if !probed {
1449 return Ok(Built::Failed);
1450 }
1451 }
1452 if let Some(orient) = fill {
1453 s.piece.push_fill_box(orient, min, max)?;
1454 }
1455 for ring_hits in ring_groups(hits) {
1456 let info = s.rings[ring_hits[0].ring as usize];
1457 s.arcs.clear();
1458 for x in ring_hits {
1459 keep_edge(&mut s.arcs, (x.edge - info.edge0) as usize);
1460 }
1461 let (cs, fenwick) = (&s.crossings, &s.fenwick);
1462 let winding =
1463 |first: usize, count: usize| excursion_winding(cs, fenwick, info, first, count);
1464 let pts = info.pts(rings, &s.copies);
1465 let plan = if attempt == Attempt::PLAIN {
1466 RingPlan::PLAIN
1467 } else {
1468 let frame = attempt.frame.then(|| {
1469 let ring = info.input as usize;
1470 (
1471 s.frame_hull.as_slice(),
1472 s.depths.as_slice(),
1473 s.ring_bases[ring],
1474 )
1475 });
1476 attempt.ring_plan(info.input as usize, rings.len(), frame)
1477 };
1478 let (arcs, piece) = (&mut s.arcs, &mut s.piece);
1479 let closed = s
1480 .clip
1481 .close_ring_planned(pts, arcs, (min, max), winding, &plan, piece)?;
1482 if closed == Closed::FrameMissed {
1483 return Ok(Built::Failed);
1484 }
1485 }
1486 if attempt.enclose && covered && s.piece.ring_ends().len() > 1 {
1487 s.piece.grow_fill_box(min, max)?;
1488 }
1489 Ok(Built::Piece)
1490 }
1491}
1492
1493type LoadedRing = (RingInfo, Coord<i32>, Coord<i32>);
1495
1496#[expect(
1501 clippy::inline_always,
1502 reason = "run per ring of every polygon; out of line it costs the small-polygon benchmarks 3-5%"
1503)]
1504#[inline(always)]
1505fn load_ring<V: Vertex>(
1506 s: &mut PolyScratch<V>,
1507 index: u32,
1508 ring: &[V],
1509 edge0: u32,
1510) -> Result<Option<LoadedRing>, TileError> {
1511 let Some(first) = ring.first().map(Vertex::position) else {
1512 return Ok(None);
1513 };
1514 let mut n = ring.len();
1515 while n >= 2 && ring[n - 1].position() == first {
1516 n -= 1;
1517 }
1518 let (mut lo, mut hi, mut prev, mut distinct) = (first, first, first, true);
1520 for v in &ring[1..n] {
1521 let p = v.position();
1522 (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));
1523 distinct &= p != prev;
1524 prev = p;
1525 }
1526 let (src, len) = if distinct {
1527 (Src::Input, n)
1528 } else {
1529 let start = s.copies.len();
1530 (Src::Copy(start), push_distinct(ring, &mut s.copies))
1531 };
1532 if len < 3 {
1533 if let Src::Copy(start) = src {
1534 s.copies.truncate(start);
1535 }
1536 return Ok(None);
1537 }
1538 let info = RingInfo {
1539 input: index,
1540 src,
1541 len: u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)?,
1542 edge0,
1543 };
1544 Ok(Some((info, lo, hi)))
1545}
1546
1547fn index_chunks<V: Vertex, R: AsRef<[V]>>(
1550 s: &mut PolyScratch<V>,
1551 rings: &[R],
1552 extent: i64,
1553 buffer: i64,
1554) {
1555 for (ring_id, info) in (0_u32..).zip(&s.rings) {
1556 let pts = info.pts(rings, &s.copies);
1557 for first in (0..pts.len()).step_by(CHUNK) {
1558 let count = CHUNK.min(pts.len() - first);
1559 let (mut y0, mut y1) = (i32::MAX, i32::MIN);
1560 for v in pts[first..first + count]
1561 .iter()
1562 .chain(iter::once(&pts[(first + count) % pts.len()]))
1563 {
1564 let y = v.position().y;
1565 (y0, y1) = (y0.min(y), y1.max(y));
1566 }
1567 #[expect(
1568 clippy::cast_possible_truncation,
1569 reason = "within the ring's u32 length"
1570 )]
1571 s.chunks.push(Chunk {
1572 ring: ring_id,
1573 first: first as u32,
1574 count: count as u32,
1575 rows: (
1576 (i64::from(y0) - buffer).div_euclid(extent),
1577 (i64::from(y1) + buffer).div_euclid(extent),
1578 ),
1579 });
1580 }
1581 }
1582}
1583
1584#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1587enum Lanes {
1588 Shared,
1589 HolesOut,
1590 ExteriorOut,
1591}
1592
1593type Frame<'a> = (&'a [Coord<i32>], &'a [(u32, u32)], usize);
1596
1597#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600struct Attempt {
1601 deepen: usize,
1602 lanes: Lanes,
1603 enclose: bool,
1604 frame: bool,
1608}
1609
1610impl Attempt {
1611 const PLAIN: Self = Self {
1613 deepen: 0,
1614 lanes: Lanes::Shared,
1615 enclose: false,
1616 frame: false,
1617 };
1618
1619 const ENCLOSE: Self = Self {
1621 enclose: true,
1622 ..Self::PLAIN
1623 };
1624
1625 fn ring_plan(self, ring: usize, rings: usize, frame: Option<Frame<'_>>) -> RingPlan<'_> {
1626 let (lane_base, lane_step) = match self.lanes {
1627 Lanes::Shared => (0, 0),
1628 Lanes::HolesOut => (i32::try_from(ring).unwrap_or(i32::MAX), 1),
1629 Lanes::ExteriorOut => (i32::try_from(rings - 1 - ring).unwrap_or(i32::MAX), 1),
1630 };
1631 RingPlan {
1632 deepen: self.deepen,
1633 lane_base,
1634 lane_step,
1635 frame: frame.map(|f| f.0),
1636 depths: frame.map(|f| f.1),
1637 bridge_base: frame.map_or(0, |f| f.2),
1638 }
1639 }
1640}
1641
1642const FRAME_ROUNDS: usize = 16;
1645
1646const ROUND_COST: usize = 8;
1648
1649fn adapt_frame(
1654 mut probe_all: impl FnMut(&[(u32, u32)], &mut Probe, &mut Vec<usize>) -> Result<(), TileError>,
1655 depths: &mut Vec<(u32, u32)>,
1656 probe: &mut Probe,
1657 hull: &mut Vec<Coord<i32>>,
1658 bases: &mut Vec<usize>,
1659 (min, max): (Coord<i32>, Coord<i32>),
1660 budget: &mut usize,
1661) -> Result<bool, TileError> {
1662 let (lo, hi) = lane_bounds(min, max, 1)?;
1663 depths.clear();
1664 for round in 0..FRAME_ROUNDS {
1665 probe.clear();
1666 probe_all(depths, probe, bases)?;
1667 let cost = probe.pts.len().saturating_mul(ROUND_COST);
1668 if cost > *budget {
1669 *budget = 0;
1670 return Ok(false);
1671 }
1672 *budget -= cost;
1673 depths.resize(probe.bridges, (0, 0));
1674 probe.pts.extend([
1675 lo,
1676 Coord { x: hi.x, y: lo.y },
1677 hi,
1678 Coord { x: lo.x, y: hi.y },
1679 ]);
1680 convex_hull(&mut probe.pts, hull);
1681 let mut deepened = false;
1682 for &(tip, bridge, side) in &probe.tips {
1683 if !hull.contains(&tip) {
1684 let d = &mut depths[bridge as usize];
1685 let step = 1 + u32::try_from(round / 4).unwrap_or(0);
1686 if side == 0 {
1687 d.0 += step;
1688 } else {
1689 d.1 += step;
1690 }
1691 deepened = true;
1692 }
1693 }
1694 if !deepened {
1695 return Ok(true);
1696 }
1697 }
1698 Ok(false)
1699}
1700
1701const WHOLE_LIMIT: usize = 2048;
1705
1706fn fits_whole<V, R: AsRef<[V]>>(rings: &[R]) -> bool {
1708 rings.iter().map(|r| r.as_ref().len()).sum::<usize>() <= WHOLE_LIMIT
1709}
1710
1711const DEPTHS: [usize; 5] = [1, 2, 4, 8, 16];
1713
1714fn repair_attempts(rings: usize) -> impl Iterator<Item = Attempt> {
1718 let lanes = move |frame: bool, deepen: usize| {
1719 [Lanes::Shared, Lanes::HolesOut, Lanes::ExteriorOut]
1720 .into_iter()
1721 .filter(move |&l| rings > 1 || l != Lanes::ExteriorOut)
1723 .map(move |lanes| Attempt {
1724 deepen,
1725 lanes,
1726 enclose: true,
1727 frame,
1728 })
1729 };
1730 iter::once(Attempt::ENCLOSE)
1731 .chain(lanes(true, 0))
1732 .chain(DEPTHS.into_iter().flat_map(move |d| lanes(false, d)))
1733 .chain(iter::once(Attempt {
1734 deepen: usize::MAX,
1735 lanes: Lanes::Shared,
1736 enclose: true,
1737 frame: false,
1738 }))
1739}
1740
1741#[expect(
1744 clippy::too_many_lines,
1745 reason = "one cohesive build of an attempt's piece; splitting it would only scatter shared state"
1746)]
1747fn build_piece<V: PolyVertex, R: AsRef<[V]>>(
1748 rings: &[R],
1749 min: Coord<i32>,
1750 max: Coord<i32>,
1751 attempt: Attempt,
1752 s: &mut PolyScratch<V>,
1753 budget: &mut usize,
1754) -> Result<Built, TileError> {
1755 s.piece.clear();
1756 s.clip.start_attempt();
1757 let Some((exterior, holes)) = rings.split_first() else {
1758 return Ok(Built::Empty);
1759 };
1760 if attempt.frame {
1763 s.probe_arcs.clear();
1765 s.probe_rings.clear();
1766 for ring in rings {
1767 s.probe_rings
1768 .push(scan_arcs(ring.as_ref(), min, max, &mut s.probe_arcs));
1769 }
1770 let probed = adapt_frame(
1771 |depths, probe, bases| {
1772 bases.clear();
1773 for (ring, scan) in rings.iter().zip(&s.probe_rings) {
1774 bases.push(probe.bridges);
1775 let plan = RingPlan {
1776 depths: Some(depths),
1777 bridge_base: probe.bridges,
1778 ..RingPlan::PLAIN
1779 };
1780 match scan {
1781 Some((m, arcs)) => {
1782 if !arcs.is_empty() {
1783 let ring = &ring.as_ref()[..*m];
1784 arcs_points(
1785 ring,
1786 &mut s.probe_arcs[arcs.clone()],
1787 plan,
1788 (min, max),
1789 probe,
1790 );
1791 }
1792 }
1793 None => {
1795 clip_ring_planned(
1796 ring.as_ref(),
1797 min,
1798 max,
1799 &mut s.clip,
1800 &plan,
1801 Some(&mut *probe),
1802 &mut s.piece,
1803 )?;
1804 }
1805 }
1806 }
1807 Ok(())
1808 },
1809 &mut s.depths,
1810 &mut s.probe,
1811 &mut s.frame_hull,
1812 &mut s.ring_bases,
1813 (min, max),
1814 budget,
1815 )?;
1816 if !probed {
1817 return Ok(Built::Failed);
1818 }
1819 }
1820 let frame = |ring: usize| {
1821 attempt.frame.then(|| {
1822 (
1823 s.frame_hull.as_slice(),
1824 s.depths.as_slice(),
1825 s.ring_bases[ring],
1826 )
1827 })
1828 };
1829 let plan = attempt.ring_plan(0, rings.len(), frame(0));
1830 let Some(exterior_clip) = clip_ring_planned(
1832 exterior.as_ref(),
1833 min,
1834 max,
1835 &mut s.clip,
1836 &plan,
1837 None,
1838 &mut s.piece,
1839 )?
1840 else {
1841 return Ok(Built::Failed);
1842 };
1843 if exterior_clip == RingClip::Outside {
1844 return Ok(Built::Empty);
1845 }
1846 if attempt == Attempt::ENCLOSE && (exterior_clip != RingClip::Covers || rings.len() == 1) {
1848 return Ok(Built::Failed);
1849 }
1850 for (i, hole) in (1..).zip(holes) {
1851 let plan = attempt.ring_plan(i, rings.len(), frame(i));
1852 match clip_ring_planned(
1853 hole.as_ref(),
1854 min,
1855 max,
1856 &mut s.clip,
1857 &plan,
1858 None,
1859 &mut s.piece,
1860 )? {
1861 None => return Ok(Built::Failed),
1862 Some(RingClip::Covers) => return Ok(Built::Empty),
1864 Some(RingClip::Clipped | RingClip::Outside) => {}
1865 }
1866 }
1867 if attempt.enclose && exterior_clip == RingClip::Covers && s.piece.ring_ends().len() > 1 {
1868 s.piece.grow_fill_box(min, max)?;
1869 }
1870 Ok(Built::Piece)
1871}
1872
1873#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1875enum Built {
1876 Empty,
1878 Failed,
1882 Piece,
1884}
1885
1886#[inline]
1890fn needs_repair<V: PolyVertex>(
1891 s: &mut PolyScratch<V>,
1892 (min, max): (Coord<i32>, Coord<i32>),
1893) -> Option<usize> {
1894 if s.piece.synthetic().is_empty() {
1895 return None;
1896 }
1897 let mut budget = PAIR_BUDGET;
1898 let verdict = is_planar(&s.piece, false, min, max, &mut s.segs, &mut budget);
1899 (verdict == Verdict::Crossing).then_some(budget)
1900}
1901
1902#[cold]
1910#[inline(never)]
1911fn repair<V: PolyVertex>(
1912 s: &mut PolyScratch<V>,
1913 (min, max): (Coord<i32>, Coord<i32>),
1914 origin: Coord<i32>,
1915 rings: usize,
1916 whole_fits: bool,
1917 mut budget: usize,
1918 mut attempt_piece: impl FnMut(&mut PolyScratch<V>, Attempt, &mut usize) -> Result<Built, TileError>,
1919) -> Result<bool, TileError> {
1920 for attempt in repair_attempts(rings) {
1921 let whole = attempt.deepen == usize::MAX;
1922 if whole && !whole_fits {
1923 break;
1924 }
1925 let built = match attempt_piece(s, attempt, &mut budget) {
1926 Err(TileError::Overflow) => Built::Failed,
1928 built => built?,
1929 };
1930 match built {
1931 Built::Empty => return Ok(false),
1932 Built::Failed => {}
1933 Built::Piece => {
1934 let verdict = if whole {
1936 Verdict::Planar
1937 } else {
1938 is_planar(&s.piece, attempt.frame, min, max, &mut s.segs, &mut budget)
1939 };
1940 match verdict {
1941 Verdict::Planar if check_local(s.piece.verts(), origin).is_ok() => {
1943 return Ok(true);
1944 }
1945 Verdict::Planar | Verdict::Crossing => {}
1946 Verdict::OverBudget => break,
1947 }
1948 }
1949 }
1950 if budget == 0 {
1951 break;
1952 }
1953 }
1954 let built = attempt_piece(s, Attempt::PLAIN, &mut budget)?;
1956 debug_assert_ne!(built, Built::Failed, "the plain clip was built before");
1957 Ok(built == Built::Piece)
1958}
1959
1960#[cfg_attr(feature = "hotpath", hotpath::measure)]
1963pub(crate) fn clip_polygon_to_tile<'s, V: PolyVertex, R: AsRef<[V]>>(
1964 grid: Grid,
1965 tile: TileId,
1966 rings: &'s [R],
1967 s: &'s mut PolyScratch<V>,
1968) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
1969 let (min, max) = grid.tile_buffered_bounds(tile)?;
1970 if let [ring] = rings
1973 && let ring = ring.as_ref()
1974 && ring.len() <= LONE_RING
1975 && let Some(n) = ring_within(ring, min, max.x.wrapping_sub(min.x).cast_unsigned())
1976 {
1977 s.rings.clear();
1978 s.rings.push(RingInfo {
1979 input: 0,
1980 src: Src::Input,
1981 len: u32::try_from(n).unwrap_or(0),
1982 edge0: 0,
1983 });
1984 return Ok(Some(PolygonPiece {
1985 tile,
1986 rings: PieceRings::Whole {
1987 rings: &s.rings,
1988 input: rings,
1989 copies: &s.copies,
1990 origin: grid.origin_of(min),
1991 },
1992 }));
1993 }
1994 let Some(origin) = build_tile_piece(grid, min, max, rings, s)? else {
1995 return Ok(None);
1996 };
1997 Ok(Some(PolygonPiece {
1998 tile,
1999 rings: PieceRings::Built {
2000 verts: s.piece.verts(),
2001 ends: s.piece.ring_ends(),
2002 origin,
2003 },
2004 }))
2005}
2006
2007#[inline(always)]
2011#[expect(
2012 clippy::inline_always,
2013 reason = "out of line, the one-tile call costs the polygon_one benchmark about 4%"
2014)]
2015fn build_tile_piece<V: PolyVertex, R: AsRef<[V]>>(
2016 grid: Grid,
2017 min: Coord<i32>,
2018 max: Coord<i32>,
2019 rings: &[R],
2020 s: &mut PolyScratch<V>,
2021) -> Result<Option<Coord<i32>>, TileError> {
2022 let origin = grid.origin_of(min);
2023 s.piece.clear();
2024 let Some((exterior, holes)) = rings.split_first() else {
2025 return Ok(None);
2026 };
2027 if clip_ring(exterior.as_ref(), min, max, &mut s.clip, &mut s.piece)? == RingClip::Outside {
2029 return Ok(None);
2030 }
2031 check_local(s.piece.verts(), origin)?;
2032 for hole in holes {
2033 let start = s.piece.len();
2034 match clip_ring(hole.as_ref(), min, max, &mut s.clip, &mut s.piece)? {
2035 RingClip::Covers => return Ok(None),
2037 RingClip::Clipped => check_local(&s.piece.verts()[start..], origin)?,
2038 RingClip::Outside => {}
2039 }
2040 }
2041 if !make_valid(rings, (min, max), origin, s)? {
2042 return Ok(None);
2043 }
2044 Ok(Some(origin))
2045}
2046
2047#[inline(never)]
2050fn make_valid<V: PolyVertex, R: AsRef<[V]>>(
2051 rings: &[R],
2052 (min, max): (Coord<i32>, Coord<i32>),
2053 origin: Coord<i32>,
2054 s: &mut PolyScratch<V>,
2055) -> Result<bool, TileError> {
2056 let Some(budget) = needs_repair(s, (min, max)) else {
2057 return Ok(true);
2058 };
2059 let whole_fits = fits_whole(rings);
2060 repair(
2061 s,
2062 (min, max),
2063 origin,
2064 rings.len(),
2065 whole_fits,
2066 budget,
2067 |s, a, b| build_piece(rings, min, max, a, s, b),
2068 )
2069}
2070
2071pub(crate) fn whole_polygon_in_tile<'s, V: PolyVertex, R: AsRef<[V]>>(
2075 grid: Grid,
2076 tile: TileId,
2077 rings: &'s [R],
2078 s: &'s mut PolyScratch<V>,
2079) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
2080 let (min, max) = grid.tile_buffered_bounds(tile)?;
2081 let origin = grid.origin_of(min);
2082 s.rings.clear();
2083 s.copies.clear();
2084 for (index, ring) in (0_u32..).zip(rings) {
2085 let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), 0)? else {
2087 if index == 0 {
2088 return Ok(None); }
2090 continue;
2091 };
2092 match ring_meets_box(info.pts(rings, &s.copies), min, max) {
2093 RingClip::Outside if index == 0 => return Ok(None),
2095 RingClip::Covers if index > 0 => return Ok(None),
2096 _ => {}
2097 }
2098 check_local(&[lo, hi], origin)?;
2099 s.rings.push(info);
2100 }
2101 if s.rings.is_empty() {
2102 return Ok(None); }
2104 Ok(Some(PolygonPiece {
2105 tile,
2106 rings: PieceRings::Whole {
2107 rings: &s.rings,
2108 input: rings,
2109 copies: &s.copies,
2110 origin,
2111 },
2112 }))
2113}