Skip to main content

map_tile_toolkit/
polygon.rs

1//! Polygon slicing for [`Slicer`](crate::Slicer): [`Slicer::polygon`](crate::Slicer::polygon)
2//! yields every piece of a polygon in every tile it reaches, and
3//! [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) its piece in one tile.
4//!
5//! A polygon is sliced lazily by a row-by-row scanline:
6//!
7//! 1. **Prepare** — one pass over the polygon's rings, borrowing them in place: drop degenerate rings,
8//!    take the bounding box, and record the `y`-range of every run of [`CHUNK`] consecutive edges. A
9//!    polygon inside one tile's inner box is yielded whole, without the rest.
10//! 2. **Scan rows** top to bottom. A row looks only at the chunks whose `y`-range reaches it: their
11//!    edges that touch the row's buffered slab give the row's tile spans, and those crossing the row's
12//!    center line `y = cy` give its crossings. The center line carries the winding ray of every tile in
13//!    the row, so the crossings answer both remaining questions: how far an excursion outside a tile
14//!    winds around it (the detour's shape), and whether a ring with no edge in a tile contains it.
15//! 3. **Sweep** the row left to right over its edge tiles, closing every touching ring's arcs with the
16//!    shared [`close_ring`] core, and yielding the covered spans between them as [`FillRun`]s. A
17//!    Fenwick tree over the row's crossings (in edge order) answers "signed crossings right of this
18//!    tile among edges `i..j`" in `O(log n)`.
19//!
20//! Nothing outside the current row is kept, and a piece is built in one reused buffer, in the global
21//! frame: [`Local`] moves it into the tile's frame as it is read.
22//!
23//! Unclipped ([`ClipMode::Unclipped`]), the sweep is the same but builds nothing: each edge tile that
24//! would get a piece, and each tile of a fill run, gets the whole polygon, lent from the input.
25
26use 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
43/// Edges per chunk of the row index: a chunk's `y`-range decides which rows look at its edges.
44const CHUNK: usize = 32;
45
46/// How many tiles of one row an edge must touch to be kept as a single [`Span`] for the row rather
47/// than one [`Hit`] per tile.
48const LONG_EDGE: i64 = 4;
49
50/// A horizontal run of tiles lying **entirely inside** the polygon: row `y`, columns `x.start..x.end`.
51///
52/// No ring edge touches these tiles' buffered boxes, so each is uniformly covered: in each of them,
53/// [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) yields this polygon as a single
54/// all-synthetic box ring around the buffered box, which [`PolygonSlices::fill_ring`] (or
55/// [`Slicer::fill_ring`](crate::Slicer::fill_ring)) returns — the same tile-local ring for every tile
56/// of the run. Runs are maximal, and never overlap the polygon's edge tiles.
57#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58#[non_exhaustive]
59pub struct FillRun {
60    /// The tile row.
61    pub y: i32,
62    /// The tile columns, end-exclusive.
63    pub x: Range<i32>,
64    /// The polygon's exterior winding, which its fill ring follows.
65    orient: Ordering,
66}
67
68/// One slice of a polygon.
69#[derive(Debug)]
70pub enum PolygonSlice<'a, V, R> {
71    /// One polygon's piece in one tile.
72    Piece(PolygonPiece<'a, V, R>),
73    /// Tiles a polygon covers entirely, whose content is [`PolygonSlices::fill_ring`] in each.
74    Run(FillRun),
75}
76
77/// One polygon's rings in one tile: exterior first, then its holes that reach the tile (all of them,
78/// and each whole, when unclipped).
79#[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    /// Clipped rings in the piece buffer (global, each closed), ending at `ends`, and the tile
88    /// origin.
89    Built {
90        verts: &'a [V],
91        ends: &'a [u32],
92        origin: Coord<i32>,
93    },
94    /// The whole polygon — lying in the tile, or unclipped: its kept rings, lent from the input (or
95    /// the copy of a ring that had duplicate vertices).
96    Whole {
97        rings: &'a [RingInfo],
98        input: &'a [R],
99        copies: &'a [V],
100        origin: Coord<i32>,
101    },
102}
103
104// Borrowed views: `Clone`/`Copy` without requiring them of the vertex or ring types.
105impl<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    /// The tile this piece belongs to.
120    #[must_use]
121    pub fn tile(&self) -> TileId {
122        self.tile
123    }
124
125    /// The piece's rings, exterior first, each closed and in the tile's local frame.
126    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/// Where one kept ring's distinct vertices are.
152#[derive(Debug, Clone, Copy)]
153enum Src {
154    /// The first `len` vertices of the input ring.
155    Input,
156    /// `copies[start..start + len]`, the ring with its duplicate vertices dropped.
157    Copy(usize),
158}
159
160/// One kept ring (at least three distinct vertices) of the polygon being sliced: ring 0 is its
161/// exterior. Edge `i` runs from vertex `i` to vertex `(i + 1) % len`, and is edge `edge0 + i` of the
162/// polygon.
163#[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/// [`CHUNK`] consecutive edges of one ring, and the tile rows their buffered `y`-range reaches.
182#[derive(Debug, Clone, Copy)]
183struct Chunk {
184    ring: u32,
185    first: u32,
186    count: u32,
187    rows: (i64, i64),
188}
189
190/// A ring edge touching tile `tx` of the current row. An edge touching fewer than [`LONG_EDGE`]
191/// tiles there has one hit per tile, so a tile's hits are one contiguous run, used as is.
192#[derive(Debug, Clone, Copy)]
193struct Hit {
194    tx: i32,
195    edge: u32,
196    ring: u32,
197}
198
199/// A long edge touching tiles `first..=last` of the current row: one entry for the row, so a row
200/// costs memory per edge, never per tile, however far an edge runs along it.
201#[derive(Debug, Clone, Copy)]
202struct Span {
203    first: i32,
204    last: i32,
205    edge: u32,
206    ring: u32,
207}
208
209/// A ring edge crossing the current row's center line, the line of every winding ray in that row
210/// (half-open in `y`, so each crossing is counted once). The crossing lies right of tile `tx`'s
211/// buffered box — on its winding ray — iff `tx <= right_of`.
212#[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
226/// Row `ty`'s center line: the midpoint of its buffered box `[ty·e − b, ty·e + e − 1 + b]`, rounded
227/// toward zero, as the clip computes it with `midpoint` — in closed form: the buffer cancels, and the
228/// sum `2·ty·e + e − 1` is negative exactly when `ty < 0`, where rounding toward zero rounds up.
229fn row_center(ty: i64, extent: i64) -> i64 {
230    ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
231}
232
233/// Rebuild `tree` as a Fenwick (binary indexed) tree over `values`, in `O(n)`.
234fn 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
245/// Add `delta` at position `i`.
246fn 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
253/// Sum of the first `end` positions.
254fn 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
263/// Sort one row's `hits` by `(tx, edge)`. They arrive in ascending edge order, so a stable counting
264/// sort by column finishes the job in `O(n + columns)`; a column range much wider than the hits are
265/// many falls back to a comparison sort. `tmp` and `counts` are reused scratch.
266fn 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        // One column: already in edge order.
276    } 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        // Exclusive prefix sums: where each column starts.
285        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
300/// Check that freshly written vertices fit the tile-local frame of `origin`, as [`Local`] lends them.
301fn 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
308/// Restore edge order to `spans`, whose first `kept` are in edge order and the rest were appended (in
309/// first-column order, so in edge order too when they all start in the same column): sort the
310/// appended part only if needed, then merge the two in one pass through `tmp`.
311fn 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
341/// `hits` (the spans covering one tile, in edge order, so each ring's are contiguous) split into runs
342/// of the same ring.
343fn 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
352/// Signed crossings, right of the current tile, of `ring`'s `count` edges from local edge `first`
353/// (cyclically): the row's still-active crossings in that edge range, summed by the Fenwick tree.
354fn 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
374/// Reusable working memory for polygon slicing, kept in the [`Slicer`](crate::Slicer) between
375/// features (see [`Scratch`]): only its capacity survives a call.
376pub(crate) struct PolyScratch<V> {
377    /// The current polygon's kept rings, copies of rings that had duplicate vertices, and chunks.
378    rings: Vec<RingInfo>,
379    copies: Vec<V>,
380    chunks: Vec<Chunk>,
381    /// Chunk ids by first row, and the chunks reaching the current row (ascending, so edge order).
382    order: Vec<u32>,
383    active: Vec<u32>,
384    /// The current row: its per-tile spans of short edges (by column, then edge), the spans of its
385    /// long edges (by first column, then edge), the long ones covering the sweep's current tile (by
386    /// edge), and a tile's merged spans when a long edge crosses it. Then its crossings (by edge), the
387    /// crossings by `right_of`, and the Fenwick tree of the signs of those still right of the sweep.
388    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    /// Sorting scratch for the row's spans.
395    hits_tmp: Vec<Hit>,
396    spans_tmp: Vec<Span>,
397    counts: Vec<usize>,
398    fenwick: Vec<i64>,
399    /// Per ring: odd crossings right of the current tile.
400    inside: Vec<bool>,
401    /// The piece being yielded: tile-local ring vertices and where each ring ends.
402    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
435/// The [`Slicer`](crate::Slicer)'s polygon working memory, allocated by its first polygon, so a
436/// slicer used only for lines and points never builds it. It holds nothing between calls that affects
437/// results: it compares equal, clones empty, and only its capacity survives a call.
438pub(crate) struct Scratch<V>(Option<Box<PolyState<V>>>);
439
440/// The working memory, and the state of the current polygon's sweep (kept out of the iterator, so the
441/// iterator stays small to move).
442pub(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    /// The working memory, allocated on first use.
453    #[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
486/// The fill ring of `run` on `grid`: tile (0, 0)'s buffered box `[-buffer, extent − 1 + buffer]`
487/// grown by one unit, wound like the run's polygon — the same in every tile's local frame. It fits
488/// `i32` because [`Grid::new`] keeps `extent + buffer` within it.
489pub(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
502/// What the next slice is; the borrowed view is built from it.
503enum Event {
504    /// The whole current polygon, for `tile`: it lies in the tile's inner box, or is unclipped.
505    Whole {
506        tile: TileId,
507        origin: Coord<i32>,
508    },
509    /// A piece built into the piece buffer, for the tile at `origin`.
510    Built {
511        tile: TileId,
512        origin: Coord<i32>,
513    },
514    Run(FillRun),
515}
516
517/// The row sweep of the current polygon.
518struct Sweep {
519    /// The next row to load, and the last row.
520    row: i64,
521    last_row: i64,
522    /// Chunks not yet active, as an index into the by-first-row order.
523    next_chunk: usize,
524    /// Whether a row is loaded, and the sweep's place in it: the next crossing (by `right_of`), the
525    /// next short-edge span, the next long-edge span, and the first tile not yet yielded or filled.
526    in_row: bool,
527    ty: i32,
528    k: usize,
529    h: usize,
530    lh: usize,
531    next: i64,
532    /// The spans of the current tile: its short-edge spans as they are (`Some`, the usual case: no
533    /// copy), or merged with the long edges crossing it into the scratch's `covering` (`None`).
534    cover: Option<Range<usize>>,
535    /// The last column a long edge covering the sweep reaches (`i64::MIN` when none does).
536    long_last: i64,
537    /// How many holes have odd parity right of the sweep.
538    holes_in: u32,
539    /// A covered span of the current row not yet yielded (it may still grow), as `start..end`.
540    pending: Option<(i64, i64)>,
541    /// A run to yield before anything else, and an edge tile to build after it (from the spans
542    /// covering it).
543    ready: Option<FillRun>,
544    tile_ready: Option<i32>,
545    /// The exterior's winding, once a fill needs it.
546    orient: Option<Ordering>,
547}
548
549/// Lazy iterator over the slices of a polygon in every tile it reaches; see
550/// [`Slicer::polygon`](crate::Slicer::polygon).
551///
552/// A **lending** iterator: a [`PolygonPiece`] borrows the iterator's piece buffer, so it is valid
553/// until the next call to [`next`](Self::next). Loop with `while let Some(slice) = slices.next()`.
554pub struct PolygonSlices<'s, V, R> {
555    grid: Grid,
556    /// [`ClipMode::Unclipped`]: every tile gets the whole polygon.
557    whole: bool,
558    rings: &'s [R],
559    s: &'s mut PolyScratch<V>,
560    /// How the polygon starts, until the first step; then its sweep, if it has one.
561    start: Option<Start>,
562    sweep: &'s mut Option<Sweep>,
563    done: bool,
564}
565
566/// How a loaded polygon starts: small, so the iterator can be built in place, its sweep only on the
567/// first step.
568enum Start {
569    /// It lies wholly in `tile`'s inner box.
570    Whole { tile: TileId, origin: Coord<i32> },
571    /// It is swept over rows `row..=last_row`.
572    Sweep { row: i64, last_row: i64 },
573}
574
575impl<'s, V: PolyVertex, R: AsRef<[V]>> PolygonSlices<'s, V, R> {
576    /// Load the polygon `rings`, bounding and indexing it.
577    #[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    /// The next slice, or `None` when the polygon is done. An error ends the iteration: only
600    /// [`TileError::GeometryTooLarge`], for a piece of more than `u32::MAX` vertices, since
601    /// [`Slicer::polygon`](crate::Slicer::polygon) settled every overflow up front.
602    #[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    /// The ring [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) yields for `run`'s polygon in
624    /// each tile of the run: the tile's buffered box grown by one unit, tile-local and closed (its
625    /// first vertex repeated), all synthetic vertices, wound like the polygon's exterior. It is the
626    /// polygon's only ring there. The same as [`Slicer::fill_ring`](crate::Slicer::fill_ring), for use
627    /// while the iterator borrows the slicer.
628    #[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/// Load the polygon `rings` into `s`: how it starts, or `None` if there is nothing to slice (a
680/// degenerate exterior). Every overflow the slicing could meet is settled here, from the polygon's
681/// bounds.
682// Once per polygon: inlined into `PolygonSlices::new`, it costs small polygons ~4%.
683#[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); // a degenerate exterior drops the polygon
699            }
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    // A polygon inside one tile's inner box touches only that tile, whose box `inner_tile` proved
724    // fits: it goes there whole, lent as it is, with nothing else to check.
725    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
749/// What the sweep reads besides its own state: the grid, the scratch, and the polygon being swept.
750struct Cx<'x, V, R> {
751    grid: Grid,
752    s: &'x mut PolyScratch<V>,
753    rings: &'x [R],
754}
755
756impl Sweep {
757    /// A sweep over rows `row..=last_row`, before its first row.
758    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    /// [`Self::step`] unclipped.
780    // Keeps this second copy of the sweep out of `PolygonSlices::next`, so the clipped one inlines as
781    // before (any extra code there costs the clipped sweep 2-5%).
782    #[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    /// Advance the sweep to its next slice; `None` when the polygon is done. `WHOLE`
791    /// ([`ClipMode::Unclipped`]): give every tile the whole polygon, fill runs tile by tile.
792    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            // Tiles `next..=bound` all see the same crossings on their winding rays.
826            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            // The next edge tile, and its spans. Rows without long edges (most) take the short path.
832            let tile = if s.long.is_empty() {
833                s.hits.get(self.h).map(|c| i64::from(c.tx))
834            } else {
835                // The sweep's next tile if a long edge still covers it, else where the next short or
836                // long span starts.
837                let next = self.next;
838                // (`long_last` is `i64::MIN` when no long edge covers the sweep, as at a row start.)
839                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                // The tile's short-edge spans: one contiguous run.
851                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                // The edge tile ends any covered span before it.
859                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                // Past every crossing each ring's parity is even again: nothing covers.
866                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            // `bound` is a crossing's `right_of` (|·| ≤ 2^32), so `+ 1` cannot overflow.
872            self.fill(cx, self.next, bound + 1)?;
873            self.next = bound + 1;
874            // Tiles past `bound` have these crossings on their left.
875            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                        // Parity alternates from an empty row start, so a hole leaves only after
887                        // entering.
888                        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            // Coverage that ends here ends the covered span: it cannot grow past this crossing.
895            if !(s.inside[0] && self.holes_in == 0) {
896                self.ready = self.take_pending();
897            }
898        }
899    }
900
901    /// Unclipped, each covered tile gets the whole polygon: the first tile of `run`, keeping the rest
902    /// of it for the next steps.
903    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    /// Add the long edges covering tile `tx` to its spans (the short ones are `s.hits[start..h]`):
916    /// drop those ending before it, add those starting by it, and merge both in edge order.
917    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    /// The covered span not yet yielded, as a run (ending it).
954    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    /// Record tiles `start..end` of the current row as covered, if the polygon covers them,
968    /// extending the span not yet yielded when they abut.
969    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    /// Gather row `ty`'s hits and crossings from the chunks reaching it.
999    #[cfg_attr(feature = "hotpath", hotpath::measure)]
1000    // (The `hotpath` wrapper hides the body from this lint.)
1001    #[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                // Crossings of the center line, half-open in `y`.
1069                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 tile `tx`'s box iff x > tx·extent + extent − 1 + buffer.
1077                        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    /// The spans covering the current edge tile, if the polygon leaves anything there. Only the
1096    /// touching rings are visited: the untouched holes are settled at once by how many of them
1097    /// contain the tile. Takes the scratch's row fields, `hits`, `covering` and `inside`, rather than
1098    /// the scratch, so the caller can still write the piece buffer.
1099    #[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        // Hits come in edge order, so the exterior's (if any) come first, then each hole's.
1115        let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1116        if !exterior_touched && !inside[0] {
1117            return None; // the exterior misses the tile
1118        }
1119        // `holes_in` counts every hole whose ray parity is odd, but a touched hole's parity says
1120        // nothing about the tile: what remains are untouched holes containing it.
1121        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    /// Unclipped, edge tile `tile` gets the whole polygon if it would get a piece.
1134    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    /// Build the polygon's piece in edge tile `tile` from [the spans covering it](Self::tile_hits)
1141    /// into the piece buffer, returning the tile origin; `None` if it leaves nothing there. Every
1142    /// vertex fits the tile's local frame: the polygon's bounds were checked when it was loaded.
1143    #[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
1189/// A kept ring and its bounding box `(lo, hi)`.
1190type LoadedRing = (RingInfo, Coord<i32>, Coord<i32>);
1191
1192/// Load one ring of the polygon in `s` (its `index` among the input rings, its first edge `edge0`):
1193/// borrowed when it is distinct as given — no consecutive duplicates once its repeated closing
1194/// vertices are dropped — else its distinct vertices copied, with its bounding box. `None` for a
1195/// degenerate ring (fewer than 3 distinct vertices).
1196#[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    // One pass: the bounding box, and whether the ring is distinct as given.
1215    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
1243/// The row index of the polygon in `s.rings`: every run of [`CHUNK`] edges, with the rows its buffered
1244/// `y`-range reaches.
1245fn 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
1280/// `len` as a `u32` offset into the piece buffer.
1281fn offset(len: usize) -> Result<u32, TileError> {
1282    u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)
1283}
1284
1285/// Clip a polygon's `rings` to one `tile` into the scratch's piece buffer, and lend the piece; `None`
1286/// if the polygon leaves nothing in the tile. See [`Slicer::tile_polygon`](crate::Slicer::tile_polygon).
1287#[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 the exterior misses the tile entirely, the whole polygon is absent here.
1302    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            // A hole covering the whole tile leaves nothing to draw here.
1311            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
1329/// A polygon's `rings` whole, lent in one `tile`'s local frame; `None` if the polygon leaves nothing
1330/// in the tile — the test [`clip_polygon_to_tile`] makes, without building the piece. See
1331/// [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) in [`ClipMode::Unclipped`].
1332pub(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        // (Edge ids only index the sweep's rows.)
1344        let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), 0)? else {
1345            if index == 0 {
1346                return Ok(None); // a degenerate exterior drops the polygon
1347            }
1348            continue;
1349        };
1350        match ring_meets_box(info.pts(rings, &s.copies), min, max) {
1351            // An exterior missing the tile, or a hole covering it, leaves nothing to draw here.
1352            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); // no rings at all
1361    }
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}