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 inside one tile's inner box is yielded whole, and one smaller than a tile with few vertices
6//! (a building) by a walk over the few tiles it reaches, each given what
7//! [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) gives it: no tile it reaches can be covered, and
8//! the sweep's setup would cost more than the clips. Both lend a lone ring that lies inside a tile's
9//! buffered box as it is, without clipping it (most of OSM's polygons are rings of 4 to 8 vertices).
10//! Any other polygon is sliced lazily by a row-by-row scanline:
11//!
12//! 1. **Prepare** — one pass over the polygon's rings, borrowing them in place: drop degenerate rings,
13//!    take the bounding box, and record the `y`-range of every run of [`CHUNK`] consecutive edges. A
14//!    polygon inside one tile's inner box is yielded whole, without the rest.
15//! 2. **Scan rows** top to bottom. A row looks only at the chunks whose `y`-range reaches it: their
16//!    edges that touch the row's buffered slab give the row's tile spans, and those crossing the row's
17//!    center line `y = cy` give its crossings. The center line carries the winding ray of every tile in
18//!    the row, so the crossings answer both remaining questions: how far an excursion outside a tile
19//!    winds around it (the detour's shape), and whether a ring with no edge in a tile contains it.
20//! 3. **Sweep** the row left to right over its edge tiles, closing every touching ring's arcs with
21//!    the clip's shared core ([`ClipScratch::close_ring`]), and yielding the covered spans between
22//!    them as [`FillRun`]s. A Fenwick tree over the row's crossings (in edge order) answers "signed
23//!    crossings right of this tile among edges `i..j`" in `O(log n)`.
24//!
25//! Nothing outside the current row is kept, and a piece is built in one reused buffer, in the global
26//! frame: [`Local`] moves it into the tile's frame as it is read.
27//!
28//! A piece is a valid polygon whenever the input is: the plain clip is kept if its outside segments
29//! (those with an endpoint outside the box) are planar, which [`planar`](crate::planar) checks, and
30//! otherwise rebuilt by the cheapest repair that is: [`repair`] runs the search for both slicers, each
31//! building the attempts its own way (from the sweep's hits, or by clipping the rings).
32//!
33//! Unclipped ([`ClipMode::Unclipped`]), the sweep is the same but builds nothing: each edge tile that
34//! would get a piece, and each tile of a fill run, gets the whole polygon, lent from the input.
35
36use 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
56/// Edges per chunk of the row index: a chunk's `y`-range decides which rows look at its edges.
57const CHUNK: usize = 32;
58
59/// How many tiles of one row an edge must touch to be kept as a single [`Span`] for the row rather
60/// than one [`Hit`] per tile.
61const LONG_EDGE: i64 = 4;
62
63/// A horizontal run of tiles lying **entirely inside** the polygon: row `y`, columns `x.start..x.end`.
64///
65/// No ring edge touches these tiles' buffered boxes, so each is uniformly covered: in each of them,
66/// [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) yields this polygon as a single
67/// all-synthetic box ring around the buffered box, which [`PolygonSlices::fill_ring`] (or
68/// [`Slicer::fill_ring`](crate::Slicer::fill_ring)) returns — the same tile-local ring for every tile
69/// of the run. Runs are maximal, and never overlap the polygon's edge tiles.
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71#[non_exhaustive]
72pub struct FillRun {
73    /// The tile row.
74    pub y: i32,
75    /// The tile columns, end-exclusive.
76    pub x: Range<i32>,
77    /// The polygon's exterior winding, which its fill ring follows.
78    orient: Ordering,
79}
80
81/// One slice of a polygon.
82#[derive(Debug)]
83pub enum PolygonSlice<'a, V, R> {
84    /// One polygon's piece in one tile.
85    Piece(PolygonPiece<'a, V, R>),
86    /// Tiles a polygon covers entirely, whose content is [`PolygonSlices::fill_ring`] in each.
87    Run(FillRun),
88}
89
90/// One polygon's rings in one tile: exterior first, then its holes that reach the tile (all of them,
91/// and each whole, when unclipped).
92#[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    /// Clipped rings in the piece buffer (global, each closed), ending at `ends`, and the tile
101    /// origin.
102    Built {
103        verts: &'a [V],
104        ends: &'a [u32],
105        origin: Coord<i32>,
106    },
107    /// The whole polygon — lying in the tile, or unclipped: its kept rings, lent from the input (or
108    /// the copy of a ring that had duplicate vertices).
109    Whole {
110        rings: &'a [RingInfo],
111        input: &'a [R],
112        copies: &'a [V],
113        origin: Coord<i32>,
114    },
115}
116
117// Borrowed views: `Clone`/`Copy` without requiring them of the vertex or ring types.
118impl<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    /// The tile this piece belongs to.
133    #[must_use]
134    pub fn tile(&self) -> TileId {
135        self.tile
136    }
137
138    /// The piece's rings, exterior first, each closed and in the tile's local frame.
139    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/// Where one kept ring's distinct vertices are.
165#[derive(Debug, Clone, Copy)]
166enum Src {
167    /// The first `len` vertices of the input ring.
168    Input,
169    /// `copies[start..start + len]`, the ring with its duplicate vertices dropped.
170    Copy(usize),
171}
172
173/// One kept ring (at least three distinct vertices) of the polygon being sliced: ring 0 is its
174/// exterior. Edge `i` runs from vertex `i` to vertex `(i + 1) % len`, and is edge `edge0 + i` of the
175/// polygon.
176#[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/// [`CHUNK`] consecutive edges of one ring, and the tile rows their buffered `y`-range reaches.
195#[derive(Debug, Clone, Copy)]
196struct Chunk {
197    ring: u32,
198    first: u32,
199    count: u32,
200    rows: (i64, i64),
201}
202
203/// A ring edge touching tile `tx` of the current row. An edge touching fewer than [`LONG_EDGE`]
204/// tiles there has one hit per tile, so a tile's hits are one contiguous run, used as is.
205#[derive(Debug, Clone, Copy)]
206struct Hit {
207    tx: i32,
208    edge: u32,
209    ring: u32,
210}
211
212/// A long edge touching tiles `first..=last` of the current row: one entry for the row, so a row
213/// costs memory per edge, never per tile, however far an edge runs along it.
214#[derive(Debug, Clone, Copy)]
215struct Span {
216    first: i32,
217    last: i32,
218    edge: u32,
219    ring: u32,
220}
221
222/// A ring edge crossing the current row's center line, the line of every winding ray in that row
223/// (half-open in `y`, so each crossing is counted once). The crossing lies right of tile `tx`'s
224/// buffered box — on its winding ray — iff `tx <= right_of`.
225#[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
239/// Row `ty`'s center line: the midpoint of its buffered box `[ty·e − b, ty·e + e − 1 + b]`, rounded
240/// toward zero, as the clip computes it with `midpoint` — in closed form: the buffer cancels, and the
241/// sum `2·ty·e + e − 1` is negative exactly when `ty < 0`, where rounding toward zero rounds up.
242fn row_center(ty: i64, extent: i64) -> i64 {
243    ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
244}
245
246/// Rebuild `tree` as a Fenwick (binary indexed) tree over `values`, in `O(n)`.
247fn 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
258/// Add `delta` at position `i`.
259fn 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
266/// Sum of the first `end` positions.
267fn 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
276/// Sort one row's `hits` by `(tx, edge)`. They arrive in ascending edge order, so a stable counting
277/// sort by column finishes the job in `O(n + columns)`; a column range much wider than the hits are
278/// many falls back to a comparison sort. `tmp` and `counts` are reused scratch.
279fn 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        // One column: already in edge order.
289    } 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        // Exclusive prefix sums: where each column starts.
298        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
313/// Check that freshly written vertices fit the tile-local frame of `origin`, as [`Local`] lends them.
314fn 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
321/// Restore edge order to `spans`, whose first `kept` are in edge order and the rest were appended (in
322/// first-column order, so in edge order too when they all start in the same column): sort the
323/// appended part only if needed, then merge the two in one pass through `tmp`.
324fn 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
354/// `hits` (the spans covering one tile, in edge order, so each ring's are contiguous) split into runs
355/// of the same ring.
356fn 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
365/// Signed crossings, right of the current tile, of `ring`'s `count` edges from local edge `first`
366/// (cyclically): the row's still-active crossings in that edge range, summed by the Fenwick tree.
367fn 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
387/// Reusable working memory for polygon slicing, kept in the [`Slicer`](crate::Slicer) between
388/// features (see [`Scratch`]): only its capacity survives a call.
389pub(crate) struct PolyScratch<V> {
390    /// The current polygon's kept rings, copies of rings that had duplicate vertices, and chunks.
391    rings: Vec<RingInfo>,
392    copies: Vec<V>,
393    chunks: Vec<Chunk>,
394    /// Chunk ids by first row, and the chunks reaching the current row (ascending, so edge order).
395    order: Vec<u32>,
396    active: Vec<u32>,
397    /// The current row: its per-tile spans of short edges (by column, then edge), the spans of its
398    /// long edges (by first column, then edge), the long ones covering the sweep's current tile (by
399    /// edge), and a tile's merged spans when a long edge crosses it. Then its crossings (by edge), the
400    /// crossings by `right_of`, and the Fenwick tree of the signs of those still right of the sweep.
401    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    /// Sorting scratch for the row's spans.
408    hits_tmp: Vec<Hit>,
409    spans_tmp: Vec<Span>,
410    counts: Vec<usize>,
411    fenwick: Vec<i64>,
412    /// Per ring: odd crossings right of the current tile.
413    inside: Vec<bool>,
414    /// The piece being yielded.
415    piece: PieceBuf<V>,
416    arcs: Vec<(usize, usize)>,
417    clip: ClipScratch<V>,
418    /// Working memory of the piece's planarity check.
419    segs: PlanarScratch,
420    /// A repair's frame: the outside points it is built around, and their convex hull.
421    probe: Probe,
422    frame_hull: Vec<Coord<i32>>,
423    /// How many vertices a frame keeps past each tip of each bridge, and each input ring's first
424    /// bridge (see [`RingPlan::depths`]).
425    depths: Vec<(u32, u32)>,
426    ring_bases: Vec<usize>,
427    /// Each input ring's arcs for the tile, scanned once for the rounds of a frame's probe.
428    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
465/// The [`Slicer`](crate::Slicer)'s polygon working memory, allocated by its first polygon, so a
466/// slicer used only for lines and points never builds it. It holds nothing between calls that affects
467/// results: it compares equal, clones empty, and only its capacity survives a call.
468pub(crate) struct Scratch<V>(Option<Box<PolyState<V>>>);
469
470/// The working memory, and the state of the current polygon's sweep (kept out of the iterator, so the
471/// iterator stays small to move).
472pub(crate) struct PolyState<V> {
473    pub(crate) scratch: PolyScratch<V>,
474    sweep: Option<Sweep>,
475    /// The walk over the few tiles of a small polygon.
476    small: Option<SmallWalk>,
477}
478
479impl<V> Scratch<V> {
480    pub(crate) const fn new() -> Self {
481        Self(None)
482    }
483
484    /// The working memory, allocated on first use.
485    #[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
519/// The fill ring of `run` on `grid`: tile (0, 0)'s buffered box `[-buffer, extent − 1 + buffer]`
520/// grown by one unit, wound like the run's polygon — the same in every tile's local frame. It fits
521/// `i32` because [`Grid::new`] keeps `extent + buffer` within it.
522pub(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
535/// What the next slice is; the borrowed view is built from it.
536enum Event {
537    /// The whole current polygon, for `tile`: it lies in the tile's inner box, or is unclipped.
538    Whole {
539        tile: TileId,
540        origin: Coord<i32>,
541    },
542    /// A piece built into the piece buffer, for the tile at `origin`.
543    Built {
544        tile: TileId,
545        origin: Coord<i32>,
546    },
547    Run(FillRun),
548}
549
550/// The row sweep of the current polygon.
551struct Sweep {
552    /// The rows not yet loaded.
553    rows: RangeInclusive<i64>,
554    /// Chunks not yet active, as an index into the by-first-row order.
555    next_chunk: usize,
556    /// Whether a row is loaded, and the sweep's place in it: the next crossing (by `right_of`), the
557    /// next short-edge span, the next long-edge span, and the first tile not yet yielded or filled.
558    in_row: bool,
559    ty: i32,
560    k: usize,
561    h: usize,
562    lh: usize,
563    next: i64,
564    /// The spans of the current tile: its short-edge spans as they are (`Some`, the usual case: no
565    /// copy), or merged with the long edges crossing it into the scratch's `covering` (`None`).
566    cover: Option<Range<usize>>,
567    /// The last column a long edge covering the sweep reaches (`i64::MIN` when none does).
568    long_last: i64,
569    /// How many holes have odd parity right of the sweep.
570    holes_in: u32,
571    /// A covered span of the current row not yet yielded (it may still grow), as `start..end`.
572    pending: Option<(i64, i64)>,
573    /// A run to yield before anything else, and an edge tile to build after it (from the spans
574    /// covering it).
575    ready: Option<FillRun>,
576    tile_ready: Option<i32>,
577    /// The exterior's winding, once a fill needs it.
578    orient: Option<Ordering>,
579    /// The columns yielded: see [`PolygonSlices::within`].
580    cols: RangeInclusive<i32>,
581}
582
583/// Lazy iterator over the slices of a polygon in every tile it reaches; see
584/// [`Slicer::polygon`](crate::Slicer::polygon).
585///
586/// A **lending** iterator: a [`PolygonPiece`] borrows the iterator's piece buffer, so it is valid
587/// until the next call to [`next`](Self::next). Loop with `while let Some(slice) = slices.next()`.
588pub struct PolygonSlices<'s, V, R> {
589    grid: Grid,
590    /// [`ClipMode::Unclipped`]: every tile gets the whole polygon.
591    whole: bool,
592    rings: &'s [R],
593    s: &'s mut PolyScratch<V>,
594    /// How the polygon starts, until the first step; then its sweep, if it has one.
595    start: Option<Start>,
596    sweep: &'s mut Option<Sweep>,
597    small: &'s mut Option<SmallWalk>,
598    done: bool,
599    /// The columns and rows yielded: see [`Self::within`].
600    cols: RangeInclusive<i32>,
601    rows: RangeInclusive<i32>,
602}
603
604/// How a loaded polygon starts: small, so the iterator can be built in place, its sweep only on the
605/// first step.
606enum Start {
607    /// It lies wholly in `tile`'s inner box.
608    Whole { tile: TileId, origin: Coord<i32> },
609    /// It is swept over `rows`.
610    Sweep { rows: RangeInclusive<i64> },
611    /// It is smaller than a tile, so no tile is covered: each of the tiles `x0..=x1` by `y0..=y1`
612    /// its buffered box reaches gets what [`clip_polygon_to_tile`] gives it, with no sweep.
613    Small { x0: i32, x1: i32, y0: i32, y1: i32 },
614}
615
616/// The walk over a small polygon's tiles: the next tile, and the columns and last row to go.
617#[derive(Clone, Copy)]
618struct SmallWalk {
619    x: i32,
620    y: i32,
621    x0: i32,
622    x1: i32,
623    y1: i32,
624}
625
626/// The most vertices of a polygon walked tile by tile (each tile scans all of them) rather than swept.
627const SMALL_VERTICES: u32 = 128;
628
629impl<'s, V: PolyVertex, R: AsRef<[V]>> PolygonSlices<'s, V, R> {
630    /// Load the polygon `rings`, bounding and indexing it.
631    #[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    /// Yield only the slices in tiles `min..=max` (columns `min.x..=max.x`, rows `min.y..=max.y`).
663    /// Needs to be called before [`next`](Self::next).
664    #[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    /// The next slice, or `None` when the polygon is done. An error ends the iteration: only
673    /// [`TileError::GeometryTooLarge`], for a piece of more than `u32::MAX` vertices, since
674    /// [`Slicer::polygon`](crate::Slicer::polygon) settled every overflow up front.
675    #[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        // A polygon inside one tile's inner box (most of them) is that tile's only slice.
684        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    /// The ring [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) yields for `run`'s polygon in
704    /// each tile of the run: the tile's buffered box grown by one unit, tile-local and closed (its
705    /// first vertex repeated), all synthetic vertices, wound like the polygon's exterior. It is the
706    /// polygon's only ring there. The same as [`Slicer::fill_ring`](crate::Slicer::fill_ring), for use
707    /// while the iterator borrows the slicer.
708    #[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    /// The next tile of a small polygon's walk ([`Start::Small`]).
738    #[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            // (`prepare` proved the box of every tile the polygon reaches fits `i32`.)
755            let (min, max) = self.grid.tile_box(tile);
756            // A lone ring inside the tile's buffered box (a building near a tile edge) is its
757            // piece there as it is: lent, not clipped.
758            if let [info] = self.s.rings[..]
759                // (The box's side may pass `i32::MAX`, but fits `u32`.)
760                && 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            // (`next` yields a polygon that starts whole itself.)
778            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        // (No sweep: a small polygon's walk, if any.)
799        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
815/// The most vertices of a ring [`Grid::inner_tile_of_ring`] is asked about: a building, not a coastline.
816const LONE_RING: usize = 16;
817
818/// Load the polygon `rings` into `s`: how it starts, or `None` if there is nothing to slice (a
819/// degenerate exterior). Every overflow the slicing could meet is settled here, from the polygon's
820/// bounds.
821// Once per polygon: inlined into `PolygonSlices::new`, it costs small polygons ~4%.
822#[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    // The usual polygon is one small ring with no repeated vertex (a building, most often four
833    // corners) lying inside a tile's inner box: found in one pass, with none of the loop's bookkeeping.
834    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); // a degenerate exterior drops the polygon
861            }
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    // A polygon inside one tile's inner box touches only that tile, whose box `inner_tile` proved
873    // fits: it goes there whole, lent as it is, with nothing else to check.
874    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    // A polygon smaller than a tile, with few vertices (a building): no tile it reaches is covered,
880    // so each gets its edge piece from the one-tile clip, which costs less than the sweep's setup.
881    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            // The tile index of `c + d`: it fits `i32` (`check_polygon_bounds` proved the boxes do).
888            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
916/// What the sweep reads besides its own state: the grid, the scratch, and the polygon being swept.
917struct Cx<'x, V, R> {
918    grid: Grid,
919    s: &'x mut PolyScratch<V>,
920    rings: &'x [R],
921}
922
923impl Sweep {
924    /// A sweep over `rows`, before its first row.
925    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    /// [`Self::step`] unclipped.
947    // Keeps this second copy of the sweep out of `PolygonSlices::next`, so the clipped one inlines as
948    // before (any extra code there costs the clipped sweep 2-5%).
949    #[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    /// Advance the sweep to its next slice; `None` when the polygon is done. `WHOLE`
958    /// ([`ClipMode::Unclipped`]): give every tile the whole polygon, fill runs tile by tile.
959    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                // Past the window: nothing more in this row, whose parity starts the next one even.
994                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            // Tiles `next..=bound` all see the same crossings on their winding rays.
1001            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            // The next edge tile, and its spans. Rows without long edges (most) take the short path.
1007            let tile = if s.long.is_empty() {
1008                s.hits.get(self.h).map(|c| i64::from(c.tx))
1009            } else {
1010                // The sweep's next tile if a long edge still covers it, else where the next short or
1011                // long span starts.
1012                let next = self.next;
1013                // (`long_last` is `i64::MIN` when no long edge covers the sweep, as at a row start.)
1014                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                // The tile's short-edge spans: one contiguous run.
1026                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                // The edge tile ends any covered span before it.
1034                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                // Past every crossing each ring's parity is even again: nothing covers.
1041                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            // `bound` is a crossing's `right_of` (|·| ≤ 2^32), so `+ 1` cannot overflow.
1047            self.fill(cx, self.next, bound + 1)?;
1048            self.next = bound + 1;
1049            // Tiles past `bound` have these crossings on their left.
1050            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                        // Parity alternates from an empty row start, so a hole leaves only after
1062                        // entering.
1063                        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            // Coverage that ends here ends the covered span: it cannot grow past this crossing.
1070            if !(s.inside[0] && self.holes_in == 0) {
1071                self.ready = self.take_pending();
1072            }
1073        }
1074    }
1075
1076    /// Unclipped, each covered tile gets the whole polygon: the first tile of `run`, keeping the rest
1077    /// of it for the next steps.
1078    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    /// Add the long edges covering tile `tx` to its spans (the short ones are `s.hits[start..h]`):
1091    /// drop those ending before it, add those starting by it, and merge both in edge order.
1092    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    /// The covered span not yet yielded, as a run (ending it).
1129    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    /// Record tiles `start..end` of the current row as covered, if the polygon covers them,
1143    /// extending the span not yet yielded when they abut.
1144    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    /// Gather row `ty`'s hits and crossings from the chunks reaching it.
1179    #[cfg_attr(feature = "hotpath", hotpath::measure)]
1180    // (The `hotpath` wrapper hides the body from this lint.)
1181    #[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                // Crossings of the center line, half-open in `y`.
1249                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 tile `tx`'s box iff x > tx·extent + extent − 1 + buffer.
1257                        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    /// The spans covering the current edge tile, if the polygon leaves anything there. Only the
1276    /// touching rings are visited: the untouched holes are settled at once by how many of them
1277    /// contain the tile. Takes the scratch's row fields, `hits`, `covering` and `inside`, rather than
1278    /// the scratch, so the caller can still write the piece buffer.
1279    #[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        // Hits come in edge order, so the exterior's (if any) come first, then each hole's.
1295        let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
1296        if !exterior_touched && !inside[0] {
1297            return None; // the exterior misses the tile
1298        }
1299        // `holes_in` counts every hole whose ray parity is odd, but a touched hole's parity says
1300        // nothing about the tile: what remains are untouched holes containing it.
1301        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    /// Unclipped, edge tile `tile` gets the whole polygon if it would get a piece.
1314    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    /// Build the polygon's piece in edge tile `tile` from [the spans covering it](Self::tile_hits)
1321    /// into the piece buffer, returning the tile origin; `None` if it leaves nothing there. Every
1322    /// vertex fits the tile's local frame: the polygon's bounds were checked when it was loaded.
1323    #[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        // The plain clip: what [`Self::piece`] builds for [`Attempt::PLAIN`], without its bookkeeping.
1338        s.piece.clear();
1339        if !exterior_touched {
1340            // A tile the exterior covers gets a fill box, wound like the exterior.
1341            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    /// [`Self::build_tile`]'s plain clip, just built, made planar outside the box if it is not:
1366    /// `false` if the polygon then leaves nothing in the tile. Out of line, to keep the plain clip's
1367    /// small.
1368    #[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        // (The plain clip of a covered tile just found the exterior's winding.)
1381        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    /// Build the piece of the edge tile with box `(min, max)`, which [`Self::tile_hits`] found gets one,
1395    /// by `attempt` into the piece buffer, from the spans covering it and the winding index, so a repair costs the piece, not
1396    /// the ring. `fill` is the exterior's winding if it covers the tile, which then gets a fill box.
1397    #[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        // (The caller checked the tile gets a piece: these are the spans covering it.)
1408        let hits = match &self.cover {
1409            Some(range) => &s.hits[range.clone()],
1410            None => &s.covering[..],
1411        };
1412        let covered = fill.is_some();
1413        // Growing the fill box does nothing unless the exterior covers the tile and there are holes.
1414        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
1493/// A kept ring and its bounding box `(lo, hi)`.
1494type LoadedRing = (RingInfo, Coord<i32>, Coord<i32>);
1495
1496/// Load one ring of the polygon in `s` (its `index` among the input rings, its first edge `edge0`):
1497/// borrowed when it is distinct as given — no consecutive duplicates once its repeated closing
1498/// vertices are dropped — else its distinct vertices copied, with its bounding box. `None` for a
1499/// degenerate ring (fewer than 3 distinct vertices).
1500#[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    // One pass: the bounding box, and whether the ring is distinct as given.
1519    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
1547/// The row index of the polygon in `s.rings`: every run of [`CHUNK`] edges, with the rows its buffered
1548/// `y`-range reaches.
1549fn 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/// How the rings of one repair attempt place their detours: all in the same lane, or each ring (and
1585/// each of its bridges) one lane further out — the exterior innermost, or outermost.
1586#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1587enum Lanes {
1588    Shared,
1589    HolesOut,
1590    ExteriorOut,
1591}
1592
1593/// A frame's data for one ring: the hull to go around, how deep each bridge's tips go, and the ring's
1594/// first bridge.
1595type Frame<'a> = (&'a [Coord<i32>], &'a [(u32, u32)], usize);
1596
1597/// One attempt at building a piece: how deep each excursion keeps its original vertices, where the
1598/// detours run, and whether a covered tile's fill box grows to enclose the holes.
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600struct Attempt {
1601    deepen: usize,
1602    lanes: Lanes,
1603    enclose: bool,
1604    /// Close excursions on a frame around the hull of everything kept, each tip keeping as many
1605    /// original vertices as it takes to be a vertex of that hull (see [`RingPlan::frame`]); `deepen`
1606    /// is not used.
1607    frame: bool,
1608}
1609
1610impl Attempt {
1611    /// The plain clip, whose pieces are what the slicer yields whenever they are planar.
1612    const PLAIN: Self = Self {
1613        deepen: 0,
1614        lanes: Lanes::Shared,
1615        enclose: false,
1616        frame: false,
1617    };
1618
1619    /// Only the fill box of a covered tile grows around the holes: the plain clip otherwise.
1620    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
1642/// How many times a frame probe may deepen the tips that are not on the hull before giving up. A tip
1643/// goes one vertex deeper the first rounds, then more each round, so this reaches depths over 40.
1644const FRAME_ROUNDS: usize = 16;
1645
1646/// What a probe round costs, per point probed, in segment pairs of the tile's budget (the hull sorts).
1647const ROUND_COST: usize = 8;
1648
1649/// Find a frame for the piece: how many vertices each bridge's tips keep (into `depths`) so that every
1650/// tip is a vertex of the convex hull (into `hull`) of what the piece keeps outside the box, and the
1651/// corners of `B⁺`. `probe_all(depths, probe)` probes every ring of the piece at those depths, filling
1652/// `probe` and the first bridge of each input ring (`bases`). `false` if the tips do not get there.
1653fn 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
1701/// The most vertices a repair may fall back to keeping whole, as its last resort, counted over all
1702/// the polygon's rings (as given): a bigger polygon keeps the plain clip (invalid outside the box, but
1703/// bounded) rather than costing a tile its whole ring.
1704const WHOLE_LIMIT: usize = 2048;
1705
1706/// Whether the polygon `rings` is small enough for its excursions to be kept whole ([`WHOLE_LIMIT`]).
1707fn fits_whole<V, R: AsRef<[V]>>(rings: &[R]) -> bool {
1708    rings.iter().map(|r| r.as_ref().len()).sum::<usize>() <= WHOLE_LIMIT
1709}
1710
1711/// How deep the repairs without a frame keep each excursion's original vertices, shallowest first.
1712const DEPTHS: [usize; 5] = [1, 2, 4, 8, 16];
1713
1714/// The repairs tried after the plain clip, cheapest first: the fill box grown around the holes; the
1715/// frame (whose lanes nest the rings); the plain way with more vertices kept; and last every excursion
1716/// whole (`deepen == usize::MAX`), which is valid because the input is.
1717fn 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            // One ring has no holes to put outside it, but its excursions still take lanes of their own.
1722            .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/// Build `rings`' piece in the box `[min, max]` for one `attempt` into the scratch's piece buffer,
1742/// in the input's frame: what [`Sweep::piece`] builds for the same attempt, from the rings.
1743#[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    // A frame goes around the hull of everything the piece keeps outside the box: find how deep each
1761    // tip must go to be on it.
1762    if attempt.frame {
1763        // Scan every ring once: the rounds of the probe only change how deep the tips go.
1764        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                        // A ring with repeated vertices is scanned from a copy, each round.
1794                        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    // If the exterior misses the tile entirely, the whole polygon is absent here.
1831    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    // Growing the fill box does nothing unless the exterior covers the tile and there are holes.
1847    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            // A hole covering the whole tile leaves nothing to draw here.
1863            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/// What building one attempt's piece came to.
1874#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1875enum Built {
1876    /// The polygon leaves nothing in the tile.
1877    Empty,
1878    /// The attempt does not apply here, or can't be built: a fill box grown with no covered tile or
1879    /// no hole, a frame whose tips never reach its hull (within the budget) or can't close an
1880    /// excursion, or a lane past the `i32` limits.
1881    Failed,
1882    /// The piece is in the scratch's piece buffer.
1883    Piece,
1884}
1885
1886/// For the plain clip just built into `s` for the box `[min, max]`: `None` if it is planar outside
1887/// the box, or nothing says it is not (nothing synthetic can cross anything, and a check that runs
1888/// out of budget passes it); else the budget the check left for repairs.
1889#[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/// Rebuild the piece in `[min, max]`, whose plain clip is not planar outside the box, by the cheapest
1903/// repair that is (see [`repair_attempts`]), into the scratch's piece buffer; if none is found within
1904/// `budget`, or the last resort would keep more than [`WHOLE_LIMIT`] vertices (`!whole_fits`), the
1905/// plain clip again. `attempt_piece(s, attempt, budget)` builds one attempt's piece: [`Sweep::piece`] for the
1906/// all-tiles slicer, [`build_piece`] for one tile, which build the same pieces, so this one search
1907/// gives a tile the same piece either way. `false` if the polygon leaves nothing in the tile (as its
1908/// plain clip says).
1909#[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            // The plain clip fits `i32`: only a repair's growth past it can overflow.
1927            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                // Every excursion whole is the input's own rings: as valid as it is.
1935                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                    // (A piece grown past the tile's local frame is only a failed attempt.)
1942                    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    // Nothing better within the budget: the plain clip, as it was.
1955    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/// Clip a polygon's `rings` to one `tile` into the scratch's piece buffer, and lend the piece; `None`
1961/// if the polygon leaves nothing in the tile. See [`Slicer::tile_polygon`](crate::Slicer::tile_polygon).
1962#[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    // A lone small ring inside the tile's buffered box (a building, most often) is its piece there as
1971    // it is: lent, as `polygon` lends it, not clipped.
1972    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/// Clip a polygon's `rings` to the tile with buffered box `(min, max)` into the scratch's piece buffer
2008/// (tile-global frame), and return the tile's origin; `None` if the polygon leaves nothing in it.
2009/// A piece not planar outside the box is repaired.
2010#[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 the exterior misses the tile entirely, the whole polygon is absent here.
2028    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            // A hole covering the whole tile leaves nothing to draw here.
2036            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/// [`build_tile_piece`]'s plain clip, just built, made planar outside the box if it is not: `false`
2048/// if the polygon then leaves nothing in the tile. Out of line, to keep the plain clip's code small.
2049#[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
2071/// A polygon's `rings` whole, lent in one `tile`'s local frame; `None` if the polygon leaves nothing
2072/// in the tile — the test [`clip_polygon_to_tile`] makes, without building the piece. See
2073/// [`Slicer::tile_polygon`](crate::Slicer::tile_polygon) in [`ClipMode::Unclipped`].
2074pub(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        // (Edge ids only index the sweep's rows.)
2086        let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), 0)? else {
2087            if index == 0 {
2088                return Ok(None); // a degenerate exterior drops the polygon
2089            }
2090            continue;
2091        };
2092        match ring_meets_box(info.pts(rings, &s.copies), min, max) {
2093            // An exterior missing the tile, or a hole covering it, leaves nothing to draw here.
2094            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); // no rings at all
2103    }
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}