Skip to main content

map_tile_toolkit/
polygon_all.rs

1//! [`PolygonSlicerAll`]: slices whole **multipolygon** features into every tile they reach, producing
2//! per tile exactly the rings [`PolygonSlicerOne`](crate::PolygonSlicerOne) produces for that tile.
3//!
4//! One pass per feature, in three steps (see `docs/polygon-slicer.md` §6/§8):
5//!
6//! 1. **Route** every ring through [`Grid::route_within`] (one shared tile-visit budget), recording
7//!    which edges touch which tile's buffered box — the keep-rule's whole input. Tiles no edge
8//!    touches are never visited.
9//! 2. **Index**, during the same walk, where each edge crosses each tile row's center line `y = cy`.
10//!    That line carries the winding ray of every tile in the row, so one sorted list per row answers both remaining
11//!    questions: how far an excursion outside a tile winds around it (the detour's shape) and whether
12//!    a ring with no edge in a tile contains it (ray parity).
13//! 3. **Sweep** each row left to right over its edge tiles, closing every touching ring's arcs with
14//!    the shared [`close_ring`] core. A Fenwick tree over the row's crossings (in edge order) answers
15//!    "signed crossings right of this tile among edges `i..j`" in `O(log n)`, so the cost is
16//!    proportional to the routing work plus the crossings, never to ring length × tiles.
17
18use core::cmp::Ordering;
19use core::ops::Range;
20
21use geo_types::Coord;
22
23use crate::TileError;
24use crate::clip_polygon::{close_ring, fill_box, keep_edge, push_distinct, push_fill_box};
25use crate::clip_polyline::to_local;
26use crate::geom::{crossing_x_ceil, ring_orientation};
27use crate::grid::{Grid, MAX_TILE_VISITS, RouteSink};
28use crate::polygon_view::{PolygonView, copy_view, offset, span};
29use crate::tile::TileId;
30use crate::vertex::{PolyVertex, Vertex};
31
32/// The most vertices one feature may add to the output. Each detour has fewer corners than the
33/// vertices it replaces, so a tile's output stays within its rings' size, but a ring winding many times
34/// around many tiles (or many overlapping polygons) can still multiply that across tiles; valid
35/// geometry stays far below this.
36const MAX_FEATURE_VERTICES: usize = 1 << 28;
37
38/// One ring of the feature being added. Its distinct vertices are `pts[start .. start + len]`, followed
39/// by a copy of the first so it routes as a closed polyline; edge `i` is global edge `start + i`.
40#[derive(Debug, Clone, Copy)]
41struct RingInfo {
42    start: u32,
43    len: u32,
44    poly: u32,
45    hole: bool,
46}
47
48/// One polygon of the feature (only those with a non-degenerate exterior): its exterior ring `first`
49/// (its holes follow it), the exterior's winding (for a fill box), and its position among the
50/// polygons passed in.
51#[derive(Debug, Clone, Copy)]
52struct PolyInfo {
53    first: u32,
54    orient: Ordering,
55    index: u32,
56}
57
58/// A ring edge touching a tile's buffered box (from routing). Sorted by `(ty, tx, edge)`: rows in
59/// order for the sweep, and within a tile edges in ring (hence polygon) order.
60#[derive(Debug, Clone, Copy)]
61struct Hit {
62    ty: i32,
63    tx: i32,
64    edge: u32,
65    ring: u32,
66}
67
68/// A ring edge crossing row `ty`'s center line, the line of every winding ray in that row (the
69/// half-open rule of [`ray_crossing`](crate::clip_polygon) — each crossing counted once). The crossing
70/// lies right of tile `tx`'s buffered box — on its winding ray — iff `tx <= right_of`.
71#[derive(Debug, Clone, Copy)]
72struct Crossing {
73    ty: i32,
74    edge: u32,
75    ring: u32,
76    up: bool,
77    right_of: i64,
78}
79
80impl Crossing {
81    fn sign(self) -> i64 {
82        if self.up { 1 } else { -1 }
83    }
84}
85
86/// Collects routing hits and row-center crossings for one ring, numbering its edges globally.
87struct HitSink<'a> {
88    hits: &'a mut Vec<Hit>,
89    crossings: &'a mut Vec<Crossing>,
90    extent: i64,
91    buffer: i64,
92    ring: u32,
93    next: u32,
94    edge: u32,
95}
96
97impl HitSink<'_> {
98    /// Record edge `a → b`'s crossing of row `ty`'s center line `cy`, which its `y` span holds.
99    fn push_crossing(
100        &mut self,
101        a: Coord<i32>,
102        b: Coord<i32>,
103        ty: i64,
104        cy: i64,
105    ) -> Result<(), TileError> {
106        let cy = i32::try_from(cy).map_err(|_| TileError::Overflow)?;
107        // Right of tile `tx`'s box iff x > tx·extent + extent − 1 + buffer.
108        let x = crossing_x_ceil(a, b, cy);
109        self.crossings.push(Crossing {
110            ty: i32::try_from(ty).map_err(|_| TileError::Overflow)?,
111            edge: self.edge,
112            ring: self.ring,
113            up: b.y > a.y,
114            right_of: (x - self.extent - self.buffer).div_euclid(self.extent),
115        });
116        Ok(())
117    }
118}
119
120impl<V: PolyVertex> RouteSink<V> for HitSink<'_> {
121    fn begin_polyline(&mut self) {}
122
123    fn begin_segment(&mut self) {
124        // The routed ring has no consecutive duplicates, so every edge is one segment, in order.
125        self.edge = self.next;
126        self.next += 1;
127    }
128
129    fn emit(&mut self, tile: TileId, _: Coord<i32>, _: V, _: V) -> Result<(), TileError> {
130        self.hits.push(Hit {
131            ty: tile.y,
132            tx: tile.x,
133            edge: self.edge,
134            ring: self.ring,
135        });
136        Ok(())
137    }
138
139    /// Index the edge's crossings of the tile-row center lines it spans. Routing has charged it at least
140    /// one visit for every row it spans (a touched tile, or the gap row itself), so this stays within
141    /// the routing budget.
142    fn end_segment(
143        &mut self,
144        a: Coord<i32>,
145        b: Coord<i32>,
146        row: Option<i32>,
147    ) -> Result<(), TileError> {
148        // Rows whose center `cy` satisfies `lo <= cy < hi` (the half-open crossing rule).
149        let (lo, hi) = (i64::from(a.y.min(b.y)), i64::from(a.y.max(b.y)));
150        if let Some(ty) = row {
151            // Inside one tile's inner box, the edge spans no other row's center line.
152            let (ty, cy) = (i64::from(ty), row_center(i64::from(ty), self.extent));
153            return if lo <= cy && cy < hi {
154                self.push_crossing(a, b, ty, cy)
155            } else {
156                Ok(())
157            };
158        }
159        let mut ty = lo.div_euclid(self.extent) - 1;
160        let mut cy = row_center(ty, self.extent);
161        while cy < lo {
162            ty += 1;
163            cy = row_center(ty, self.extent);
164        }
165        while cy < hi {
166            self.push_crossing(a, b, ty, cy)?;
167            ty += 1;
168            cy = row_center(ty, self.extent);
169        }
170        Ok(())
171    }
172}
173
174/// Row `ty`'s center line: the midpoint of its buffered box `[ty·e − b, ty·e + e − 1 + b]`, rounded
175/// toward zero, as the clip computes it with `midpoint` — in closed form, as this runs for every edge:
176/// the buffer cancels, and the sum `2·ty·e + e − 1` is negative exactly when `ty < 0`, where rounding
177/// toward zero rounds up.
178fn row_center(ty: i64, extent: i64) -> i64 {
179    ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
180}
181
182/// Stable counting sort of `items` into `buckets` buckets by `bucket(item)` (which must lie in
183/// `0..buckets`): in `O(n + buckets)`, keeping each bucket's items in input order. `tmp` and `counts`
184/// are reused scratch.
185fn counting_sort<T: Copy>(
186    items: &mut [T],
187    tmp: &mut Vec<T>,
188    counts: &mut Vec<usize>,
189    buckets: usize,
190    bucket: impl Fn(&T) -> usize,
191) {
192    counts.clear();
193    counts.resize(buckets, 0);
194    for item in items.iter() {
195        counts[bucket(item)] += 1;
196    }
197    // Exclusive prefix sums: where each bucket starts.
198    let mut start = 0;
199    for c in counts.iter_mut() {
200        (*c, start) = (start, start + *c);
201    }
202    tmp.clear();
203    tmp.extend_from_slice(items);
204    for item in tmp.iter() {
205        let at = &mut counts[bucket(item)];
206        items[*at] = *item;
207        *at += 1;
208    }
209}
210
211/// Sort routing `hits` by `(ty, tx, edge)`. They arrive in ascending edge order (rings are routed in
212/// order, numbered by their position in the input; each edge visits its tiles once), so a stable
213/// counting sort by tile finishes the job in `O(n + span)` over the hits' tile span — one pass keyed by
214/// the tile itself when the span's area is small, else a pass by column then one by row (routing bounds
215/// the span per axis). A span much wider than the hits are many (a few hits far apart) falls back to a
216/// comparison sort. `tmp` and `counts` are reused scratch.
217fn sort_hits(hits: &mut [Hit], tmp: &mut Vec<Hit>, counts: &mut Vec<usize>) {
218    let key = |h: &Hit| (h.ty, h.tx, h.edge);
219    let Some(&first) = hits.first() else {
220        return;
221    };
222    let (mut x, mut y) = ((first.tx, first.tx), (first.ty, first.ty));
223    for h in hits.iter() {
224        x = (x.0.min(h.tx), x.1.max(h.tx));
225        y = (y.0.min(h.ty), y.1.max(h.ty));
226    }
227    let (columns, rows) = (
228        x.1.abs_diff(x.0) as usize + 1,
229        y.1.abs_diff(y.0) as usize + 1,
230    );
231    let column = |h: &Hit| h.tx.abs_diff(x.0) as usize;
232    let row = |h: &Hit| h.ty.abs_diff(y.0) as usize;
233    let budget = hits.len().saturating_mul(4);
234    if columns == 1 && rows == 1 {
235        // One tile: already in edge order.
236    } else if hits.len() < 32 || columns + rows > budget {
237        hits.sort_unstable_by_key(key);
238    } else if let Some(tiles) = columns.checked_mul(rows)
239        && tiles <= budget
240    {
241        counting_sort(hits, tmp, counts, tiles, |h| row(h) * columns + column(h));
242    } else {
243        counting_sort(hits, tmp, counts, columns, column);
244        counting_sort(hits, tmp, counts, rows, row);
245    }
246    debug_assert!(hits.is_sorted_by_key(key));
247}
248
249/// Sort `crossings` by `(ty, edge)`. They arrive in ascending edge order (each edge's rows ascending),
250/// so one stable counting sort by row does it, in `O(n + rows)`; a row span much wider than the
251/// crossings are many falls back to a comparison sort. `tmp` and `counts` are reused scratch.
252fn sort_crossings(crossings: &mut [Crossing], tmp: &mut Vec<Crossing>, counts: &mut Vec<usize>) {
253    let key = |c: &Crossing| (c.ty, c.edge);
254    let Some(&first) = crossings.first() else {
255        return;
256    };
257    let y = crossings.iter().fold((first.ty, first.ty), |(lo, hi), c| {
258        (lo.min(c.ty), hi.max(c.ty))
259    });
260    let rows = y.1.abs_diff(y.0) as usize + 1;
261    if crossings.len() < 32 || rows > crossings.len().saturating_mul(4) {
262        crossings.sort_unstable_by_key(key);
263    } else if rows > 1 {
264        counting_sort(crossings, tmp, counts, rows, |c| {
265            c.ty.abs_diff(y.0) as usize
266        });
267    }
268    debug_assert!(crossings.is_sorted_by_key(key));
269}
270
271/// Rebuild `tree` as a Fenwick (binary indexed) tree over `values`, in `O(n)`.
272fn fenwick_build(tree: &mut Vec<i64>, values: impl Iterator<Item = i64>) {
273    tree.clear();
274    tree.extend(values);
275    for i in 0..tree.len() {
276        let parent = i | (i + 1);
277        if parent < tree.len() {
278            tree[parent] += tree[i];
279        }
280    }
281}
282
283/// Add `delta` at position `i`.
284fn fenwick_add(tree: &mut [i64], mut i: usize, delta: i64) {
285    while i < tree.len() {
286        tree[i] += delta;
287        i |= i + 1;
288    }
289}
290
291/// Sum of the first `end` positions.
292fn fenwick_prefix(tree: &[i64], mut end: usize) -> i64 {
293    let mut sum = 0;
294    while end > 0 {
295        sum += tree[end - 1];
296        end &= end - 1;
297    }
298    sum
299}
300
301/// Re-express freshly written vertices in the tile-local frame.
302fn localize<V: PolyVertex>(verts: &mut [V], origin: Coord<i32>) -> Result<(), TileError> {
303    for v in verts {
304        *v = to_local(*v, origin)?;
305    }
306    Ok(())
307}
308
309/// The feature being added, copied once: distinct ring vertices plus ring/polygon structure.
310#[derive(Debug, Clone)]
311struct Input<V> {
312    pts: Vec<V>,
313    rings: Vec<RingInfo>,
314    polys: Vec<PolyInfo>,
315}
316
317impl<V: PolyVertex> Input<V> {
318    /// Copy one feature, dropping degenerate rings (fewer than 3 distinct vertices) — and a polygon
319    /// whose exterior is degenerate — exactly as the single-tile clip treats them.
320    fn load<P>(&mut self, polygons: P) -> Result<(), TileError>
321    where
322        P: IntoIterator,
323        P::Item: IntoIterator,
324        <P::Item as IntoIterator>::Item: AsRef<[V]>,
325    {
326        self.pts.clear();
327        self.rings.clear();
328        self.polys.clear();
329        for (index, polygon) in polygons.into_iter().enumerate() {
330            let index = offset(index)?;
331            let mut rings = polygon.into_iter();
332            let Some(exterior) = rings.next() else {
333                continue;
334            };
335            let first = offset(self.rings.len())?;
336            let poly = offset(self.polys.len())?;
337            if !self.push_ring(exterior.as_ref(), poly, false)? {
338                continue;
339            }
340            for hole in rings {
341                self.push_ring(hole.as_ref(), poly, true)?;
342            }
343            self.polys.push(PolyInfo {
344                first,
345                // Set by `orient_polygons` when the feature needs fill boxes.
346                orient: Ordering::Equal,
347                index,
348            });
349        }
350        Ok(())
351    }
352
353    /// Record each polygon's exterior winding, which its fill boxes and runs follow.
354    fn orient_polygons(&mut self) {
355        for poly in &mut self.polys {
356            let ext = self.rings[poly.first as usize];
357            poly.orient =
358                ring_orientation(&self.pts[ext.start as usize..(ext.start + ext.len) as usize]);
359        }
360    }
361
362    /// Append one ring's distinct vertices (closed with a copy of the first), if it has at least 3.
363    #[inline]
364    fn push_ring(&mut self, ring: &[V], poly: u32, hole: bool) -> Result<bool, TileError> {
365        let start = self.pts.len();
366        let len = push_distinct(ring, &mut self.pts);
367        if len < 3 {
368            self.pts.truncate(start);
369            return Ok(false);
370        }
371        self.pts.push(self.pts[start]);
372        // The closing copy's index must fit too: it is the head of the last edge.
373        offset(self.pts.len())?;
374        self.rings.push(RingInfo {
375            start: offset(start)?,
376            len: offset(len)?,
377            poly,
378            hole,
379        });
380        Ok(true)
381    }
382
383    /// The ring's distinct vertices (without the closing copy).
384    fn ring_pts(&self, ring: RingInfo) -> &[V] {
385        &self.pts[ring.start as usize..(ring.start + ring.len) as usize]
386    }
387}
388
389/// A horizontal run of tiles lying **entirely inside** one of a feature's polygons: row `y`, columns
390/// `x.start..x.end`, covered by polygon `polygon`.
391///
392/// No ring edge touches these tiles' buffered boxes, so each is uniformly covered. In each of them,
393/// [`PolygonSlicerOne`](crate::PolygonSlicerOne) emits one polygon for this one: a single
394/// all-synthetic box ring around the buffered box, which
395/// [`PolygonFeatureView::fill_ring`] returns (the same tile-local ring for every tile of the run).
396///
397/// Runs never overlap the feature's edge tiles, are maximal per polygon, and come in row-major order,
398/// then by polygon. Polygons of a valid multipolygon don't overlap, so neither do their runs; only
399/// overlapping parts give a tile several runs, which `PolygonSlicerOne` emits in polygon order.
400#[derive(Debug, Clone, PartialEq, Eq, Hash)]
401#[non_exhaustive]
402pub struct FillRun {
403    /// The tile row.
404    pub y: i32,
405    /// The tile columns, end-exclusive.
406    pub x: Range<i32>,
407    /// The covering polygon: its position among the polygons passed to
408    /// [`add_feature_with`](PolygonSlicerAll::add_feature_with).
409    pub polygon: u32,
410    /// The covering polygon's exterior winding, which its fill ring follows.
411    orient: Ordering,
412}
413
414/// Where each feature's geometry lands, in flat arenas (no per-tile allocations): tile-local ring
415/// vertices, ring/polygon/tile end offsets, and fill runs.
416#[derive(Debug, Clone)]
417struct Pieces<V> {
418    verts: Vec<V>,
419    ring_ends: Vec<u32>,
420    poly_ends: Vec<u32>,
421    tiles: Vec<TileEntry>,
422    runs: Vec<FillRun>,
423}
424
425/// One edge tile of one feature: its id and the end of its polygons in `poly_ends`.
426#[derive(Debug, Clone, Copy)]
427struct TileEntry {
428    tile: TileId,
429    poly_end: u32,
430}
431
432/// Arena lengths before a feature, to roll back to on error.
433#[derive(Clone, Copy, Default)]
434struct Savepoint {
435    verts: usize,
436    ring_ends: usize,
437    poly_ends: usize,
438    tiles: usize,
439    runs: usize,
440}
441
442impl<V> Pieces<V> {
443    fn savepoint(&self) -> Savepoint {
444        Savepoint {
445            verts: self.verts.len(),
446            ring_ends: self.ring_ends.len(),
447            poly_ends: self.poly_ends.len(),
448            tiles: self.tiles.len(),
449            runs: self.runs.len(),
450        }
451    }
452
453    fn rollback(&mut self, s: Savepoint) {
454        self.verts.truncate(s.verts);
455        self.ring_ends.truncate(s.ring_ends);
456        self.poly_ends.truncate(s.poly_ends);
457        self.tiles.truncate(s.tiles);
458        self.runs.truncate(s.runs);
459    }
460
461    fn clear(&mut self) {
462        self.rollback(Savepoint::default());
463    }
464
465    fn shrink_to_fit(&mut self) {
466        self.verts.shrink_to_fit();
467        self.ring_ends.shrink_to_fit();
468        self.poly_ends.shrink_to_fit();
469        self.tiles.shrink_to_fit();
470        self.runs.shrink_to_fit();
471    }
472}
473
474/// Reusable working memory for one feature's routing and row sweep (kept across features).
475#[derive(Debug, Clone)]
476struct Scratch {
477    hits: Vec<Hit>,
478    /// Sorting scratch (see [`sort_hits`] and [`sort_crossings`]).
479    hits_tmp: Vec<Hit>,
480    crossings_tmp: Vec<Crossing>,
481    counts: Vec<usize>,
482    crossings: Vec<Crossing>,
483    /// The current row's crossings (indices into its edge-ordered slice), by `right_of`.
484    by_x: Vec<u32>,
485    /// Signs of the current row's crossings still right of the sweep, in edge order.
486    fenwick: Vec<i64>,
487    /// Per ring: odd crossings right of the current tile (= the ring contains the tile, when no edge
488    /// of it touches the tile).
489    inside: Vec<bool>,
490    /// Per polygon: how many of its holes contain the current tile (by the same parity).
491    holes_in: Vec<u32>,
492    /// Polygons containing the current tile (exterior inside, no hole inside), as a swap-remove set.
493    covering: Vec<u32>,
494    cover_pos: Vec<usize>,
495    /// Per polygon: its latest fill run (an index into the output runs; stale unless in this row).
496    last_run: Vec<usize>,
497    /// Whether the tile-local fill box fits `i32` (see [`PolygonSlicerAll::fill_box`]).
498    fill_ok: bool,
499    /// The output length the current feature must not pass ([`MAX_FEATURE_VERTICES`] past its start).
500    /// It bounds the fill runs too: a polygon's run ends only beside a tile where that polygon emits
501    /// a ring (an edge of it, or its fill box in another polygon's edge tile), so runs never
502    /// outnumber the output rings by more than one per row and polygon.
503    vert_limit: usize,
504    /// Per-tile temporaries.
505    arcs: Vec<(usize, usize)>,
506    touched: Vec<u32>,
507    extra: Vec<u32>,
508}
509
510impl Scratch {
511    const fn new() -> Self {
512        Self {
513            hits: Vec::new(),
514            hits_tmp: Vec::new(),
515            crossings_tmp: Vec::new(),
516            counts: Vec::new(),
517            crossings: Vec::new(),
518            by_x: Vec::new(),
519            fenwick: Vec::new(),
520            inside: Vec::new(),
521            holes_in: Vec::new(),
522            covering: Vec::new(),
523            cover_pos: Vec::new(),
524            last_run: Vec::new(),
525            fill_ok: false,
526            vert_limit: 0,
527            arcs: Vec::new(),
528            touched: Vec::new(),
529            extra: Vec::new(),
530        }
531    }
532
533    /// Route every ring, recording its tile hits and row-center crossings, all sharing one `budget`
534    /// of visited tiles.
535    fn route<V: PolyVertex>(
536        &mut self,
537        grid: Grid,
538        input: &Input<V>,
539        mut budget: i64,
540    ) -> Result<(), TileError> {
541        self.hits.clear();
542        self.crossings.clear();
543        // Every edge hits at least one tile. Reserving that up front also keeps the two lists, which
544        // grow together, from reallocating each other out of place on a fresh slicer.
545        self.hits.reserve(input.pts.len());
546        for (ring, info) in (0..).zip(&input.rings) {
547            let start = info.start as usize;
548            let closed = &input.pts[start..=start + info.len as usize];
549            let mut sink = HitSink {
550                hits: &mut self.hits,
551                crossings: &mut self.crossings,
552                extent: i64::from(grid.extent()),
553                buffer: i64::from(grid.buffer()),
554                ring,
555                next: info.start,
556                edge: 0,
557            };
558            grid.route_within(closed, &mut sink, &mut budget)?;
559        }
560        sort_hits(&mut self.hits, &mut self.hits_tmp, &mut self.counts);
561        sort_crossings(
562            &mut self.crossings,
563            &mut self.crossings_tmp,
564            &mut self.counts,
565        );
566        Ok(())
567    }
568
569    /// Emit every edge tile of the feature into `out`, row by row, adding at most `max_verts` vertices.
570    fn sweep<V: PolyVertex>(
571        &mut self,
572        grid: Grid,
573        input: &Input<V>,
574        out: &mut Pieces<V>,
575        max_verts: usize,
576        fill_ok: bool,
577    ) -> Result<(), TileError> {
578        self.vert_limit = out.verts.len().saturating_add(max_verts);
579        self.fill_ok = fill_ok;
580        self.last_run.clear();
581        self.last_run.resize(input.polys.len(), usize::MAX);
582        // Containment state starts (and, since every ring crosses a line an even number of times, ends
583        // each row) empty.
584        self.inside.clear();
585        self.inside.resize(input.rings.len(), false);
586        self.holes_in.clear();
587        self.holes_in.resize(input.polys.len(), 0);
588        self.cover_pos.clear();
589        self.cover_pos.resize(input.polys.len(), 0);
590        self.covering.clear();
591
592        let hits = core::mem::take(&mut self.hits);
593        let crossings = core::mem::take(&mut self.crossings);
594        let result = self.sweep_rows(grid, input, &hits, &crossings, out);
595        self.hits = hits;
596        self.crossings = crossings;
597        result
598    }
599
600    fn sweep_rows<V: PolyVertex>(
601        &mut self,
602        grid: Grid,
603        input: &Input<V>,
604        hits: &[Hit],
605        crossings: &[Crossing],
606        out: &mut Pieces<V>,
607    ) -> Result<(), TileError> {
608        let (mut h, mut c) = (0, 0);
609        loop {
610            let ty = match (hits.get(h), crossings.get(c)) {
611                (Some(a), Some(b)) => a.ty.min(b.ty),
612                (Some(a), None) => a.ty,
613                (None, Some(b)) => b.ty,
614                (None, None) => return Ok(()),
615            };
616            let h_end = h + hits[h..].partition_point(|x| x.ty == ty);
617            let c_end = c + crossings[c..].partition_point(|x| x.ty == ty);
618            self.sweep_row(grid, input, ty, &hits[h..h_end], &crossings[c..c_end], out)?;
619            (h, c) = (h_end, c_end);
620        }
621    }
622
623    /// One tile row: walk its edge tiles left to right, retiring each crossing from the winding rays
624    /// (and toggling its ring's containment) once the sweep passes it. Between consecutive crossings
625    /// the containment state is constant, so while some polygon covers it, every tile there that is
626    /// not an edge tile is filled — one run, however many tiles.
627    fn sweep_row<V: PolyVertex>(
628        &mut self,
629        grid: Grid,
630        input: &Input<V>,
631        ty: i32,
632        hits: &[Hit],
633        cs: &[Crossing],
634        out: &mut Pieces<V>,
635    ) -> Result<(), TileError> {
636        self.by_x.clear();
637        self.by_x.extend(0..offset(cs.len())?);
638        self.by_x.sort_unstable_by_key(|&i| cs[i as usize].right_of);
639        fenwick_build(&mut self.fenwick, cs.iter().map(|c| c.sign()));
640
641        let row_runs = out.runs.len();
642        let (mut k, mut h) = (0, 0);
643        // The first tile not yet emitted or filled.
644        let mut next = i64::MIN;
645        loop {
646            // Tiles `next..=bound` all see the same crossings on their winding rays.
647            let bound = self
648                .by_x
649                .get(k)
650                .map_or(i64::MAX, |&i| cs[i as usize].right_of);
651            while let Some(hit) = hits.get(h)
652                && i64::from(hit.tx) <= bound
653            {
654                let tx = i64::from(hit.tx);
655                self.fill(input, ty, next, tx, row_runs, out)?;
656                let end = h + hits[h..].partition_point(|x| x.tx == hit.tx);
657                self.tile(grid, input, TileId::new(hit.tx, ty), &hits[h..end], cs, out)?;
658                // One tile's output is bounded by the feature's size; only the sum over tiles is not.
659                if out.verts.len() > self.vert_limit {
660                    return Err(TileError::OutputTooLarge);
661                }
662                (h, next) = (end, tx + 1);
663            }
664            if k == self.by_x.len() {
665                // Past every crossing each ring's parity is even again: nothing covers. Order the
666                // row's runs by column, then polygon (they were extended per polygon as found).
667                out.runs[row_runs..].sort_unstable_by_key(|r| (r.x.start, r.polygon));
668                return Ok(());
669            }
670            // `bound` is a crossing's `right_of` (|·| ≤ 2^32), so `+ 1` cannot overflow.
671            self.fill(input, ty, next, bound + 1, row_runs, out)?;
672            next = bound + 1;
673            // Tiles past `bound` have these crossings on their left.
674            while let Some(&i) = self.by_x.get(k)
675                && cs[i as usize].right_of == bound
676            {
677                let crossing = cs[i as usize];
678                self.toggle(input, crossing);
679                fenwick_add(&mut self.fenwick, i as usize, -crossing.sign());
680                k += 1;
681            }
682        }
683    }
684
685    /// Record tiles `start..end` of row `y` as covered by every polygon now covering, extending each
686    /// polygon's previous run of the row when they abut. `row_runs` is where this row's runs begin, so
687    /// runs never merge across rows or features.
688    fn fill<V>(
689        &mut self,
690        input: &Input<V>,
691        y: i32,
692        start: i64,
693        end: i64,
694        row_runs: usize,
695        out: &mut Pieces<V>,
696    ) -> Result<(), TileError> {
697        if start >= end || self.covering.is_empty() {
698            return Ok(());
699        }
700        if !self.fill_ok {
701            // The single-tile clip can't build this tile's fill box either.
702            return Err(TileError::Overflow);
703        }
704        let start = i32::try_from(start).map_err(|_| TileError::Overflow)?;
705        let end = i32::try_from(end).map_err(|_| TileError::Overflow)?;
706        for &p in &self.covering {
707            let info = input.polys[p as usize];
708            let last = &mut self.last_run[p as usize];
709            match out.runs.get_mut(*last) {
710                Some(run)
711                    if *last >= row_runs && run.polygon == info.index && run.x.end == start =>
712                {
713                    run.x.end = end;
714                }
715                _ => {
716                    *last = out.runs.len();
717                    out.runs.push(FillRun {
718                        y,
719                        x: start..end,
720                        polygon: info.index,
721                        orient: info.orient,
722                    });
723                }
724            }
725        }
726        Ok(())
727    }
728
729    /// The sweep passed `crossing`: flip its ring's containment and update which polygons cover.
730    fn toggle<V>(&mut self, input: &Input<V>, crossing: Crossing) {
731        let ring = input.rings[crossing.ring as usize];
732        let p = ring.poly as usize;
733        let ext = input.polys[p].first as usize;
734        let covers = |s: &Self| s.inside[ext] && s.holes_in[p] == 0;
735        let was = covers(self);
736        let r = crossing.ring as usize;
737        self.inside[r] = !self.inside[r];
738        if ring.hole {
739            if self.inside[r] {
740                self.holes_in[p] += 1;
741            } else {
742                // Parity alternates from an empty row start, so a hole only leaves after entering.
743                self.holes_in[p] = self.holes_in[p].saturating_sub(1);
744            }
745        }
746        match (was, covers(self)) {
747            (false, true) => {
748                self.cover_pos[p] = self.covering.len();
749                self.covering.push(ring.poly);
750            }
751            (true, false) => {
752                let i = self.cover_pos[p];
753                self.covering.swap_remove(i);
754                if let Some(&moved) = self.covering.get(i) {
755                    self.cover_pos[moved as usize] = i;
756                }
757            }
758            _ => {}
759        }
760    }
761
762    /// Emit one edge tile: every polygon in input order, exactly as the single-tile clip would.
763    fn tile<V: PolyVertex>(
764        &mut self,
765        grid: Grid,
766        input: &Input<V>,
767        tile: TileId,
768        hits: &[Hit],
769        cs: &[Crossing],
770        out: &mut Pieces<V>,
771    ) -> Result<(), TileError> {
772        let (min, max) = grid.tile_buffered_bounds(tile)?;
773        let origin = tile.origin(grid.extent()).ok_or(TileError::Overflow)?;
774        // Polygons with an edge here, ascending (hits are in edge order; rings and polygons in input
775        // order).
776        self.touched.clear();
777        for hit in hits {
778            let p = input.rings[hit.ring as usize].poly;
779            if self.touched.last() != Some(&p) {
780                self.touched.push(p);
781            }
782        }
783        // Polygons covering this tile with no edge here. Only overlapping parts of an (invalid)
784        // multipolygon can do that; each contributes a fill box, as in the single-tile clip.
785        self.extra.clear();
786        let touched = &self.touched;
787        self.extra.extend(
788            self.covering
789                .iter()
790                .filter(|p| touched.binary_search(p).is_err()),
791        );
792        self.extra.sort_unstable();
793
794        let polys_before = out.poly_ends.len();
795        // Merge both (ascending, disjoint) polygon lists so the output follows input order.
796        let (mut next_touched, mut next_extra, mut hit) = (0, 0, 0);
797        loop {
798            let touched = self.touched.get(next_touched).copied();
799            let extra = self.extra.get(next_extra).copied();
800            match (touched, extra) {
801                (Some(poly), fill) if fill.is_none_or(|fill| poly < fill) => {
802                    let n =
803                        hits[hit..].partition_point(|x| input.rings[x.ring as usize].poly == poly);
804                    let poly_hits = &hits[hit..hit + n];
805                    self.polygon(input, poly, poly_hits, cs, min, max, origin, out)?;
806                    next_touched += 1;
807                    hit += n;
808                }
809                (_, Some(fill)) => {
810                    let ring_start = out.verts.len();
811                    push_fill_box(&mut out.verts, input.polys[fill as usize].orient, min, max)?;
812                    localize(&mut out.verts[ring_start..], origin)?;
813                    out.ring_ends.push(offset(out.verts.len())?);
814                    out.poly_ends.push(offset(out.ring_ends.len())?);
815                    next_extra += 1;
816                }
817                _ => break,
818            }
819        }
820        if out.poly_ends.len() > polys_before {
821            out.tiles.push(TileEntry {
822                tile,
823                poly_end: offset(out.poly_ends.len())?,
824            });
825        }
826        Ok(())
827    }
828
829    /// Emit polygon `p` into the current tile from its rings' `hits` here: touching rings are clipped,
830    /// the rest decided by containment (an exterior missing the tile or a hole covering it drops the
831    /// polygon).
832    ///
833    /// Only the touching rings are visited, so a polygon with many holes costs its hits here, not its
834    /// ring count: the untouched holes are settled at once by how many of them contain the tile.
835    #[expect(
836        clippy::too_many_arguments,
837        reason = "the tile context, passed down once per polygon"
838    )]
839    fn polygon<V: PolyVertex>(
840        &mut self,
841        input: &Input<V>,
842        p: u32,
843        hits: &[Hit],
844        cs: &[Crossing],
845        min: Coord<i32>,
846        max: Coord<i32>,
847        origin: Coord<i32>,
848        out: &mut Pieces<V>,
849    ) -> Result<(), TileError> {
850        let info = input.polys[p as usize];
851        // Hits come in edge order, so the exterior's (if any) come first, then each hole's.
852        let exterior_touched = hits.first().is_some_and(|x| x.ring == info.first);
853        if !exterior_touched && !self.inside[info.first as usize] {
854            return Ok(()); // the exterior misses the tile
855        }
856        // `holes_in` counts every hole whose ray parity is odd, but a touched hole's parity says
857        // nothing about the tile: what remains are untouched holes containing it, which leave nothing
858        // to draw.
859        let holes_in = self.holes_in[p as usize] as usize;
860        if holes_in > 0
861            && holes_in
862                > ring_groups(hits)
863                    .filter(|g| g[0].ring != info.first && self.inside[g[0].ring as usize])
864                    .count()
865        {
866            return Ok(());
867        }
868        if !exterior_touched {
869            let ring_start = out.verts.len();
870            push_fill_box(&mut out.verts, info.orient, min, max)?;
871            localize(&mut out.verts[ring_start..], origin)?;
872            out.ring_ends.push(offset(out.verts.len())?);
873        }
874        for ring_hits in ring_groups(hits) {
875            let ring = input.rings[ring_hits[0].ring as usize];
876            let ring_start = out.verts.len();
877            self.arcs.clear();
878            for x in ring_hits {
879                keep_edge(&mut self.arcs, (x.edge - ring.start) as usize);
880            }
881            let fenwick = &self.fenwick;
882            let winding =
883                |first: usize, count: usize| excursion_winding(cs, fenwick, ring, first, count);
884            close_ring(
885                input.ring_pts(ring),
886                &mut self.arcs,
887                min,
888                max,
889                winding,
890                &mut out.verts,
891            )?;
892            localize(&mut out.verts[ring_start..], origin)?;
893            out.ring_ends.push(offset(out.verts.len())?);
894        }
895        out.poly_ends.push(offset(out.ring_ends.len())?);
896        Ok(())
897    }
898}
899
900/// `hits` (one tile's, in edge order, so each ring's are contiguous) split into runs of the same ring,
901/// with one binary search per ring rather than a comparison per hit.
902fn ring_groups(mut hits: &[Hit]) -> impl Iterator<Item = &[Hit]> {
903    core::iter::from_fn(move || {
904        let ring = hits.first()?.ring;
905        let (group, rest) = hits.split_at(hits.partition_point(|x| x.ring == ring));
906        hits = rest;
907        Some(group)
908    })
909}
910
911/// Signed crossings, right of the current tile, of `ring`'s `count` edges from local edge `first`
912/// (cyclically): the row's still-active crossings in that edge range, summed by the Fenwick tree.
913fn excursion_winding(
914    cs: &[Crossing],
915    fenwick: &[i64],
916    ring: RingInfo,
917    first: usize,
918    count: usize,
919) -> i64 {
920    let (start, len) = (ring.start as usize, ring.len as usize);
921    let sum = |lo: usize, hi: usize| {
922        let i = cs.partition_point(|c| (c.edge as usize) < lo);
923        let j = cs.partition_point(|c| (c.edge as usize) < hi);
924        fenwick_prefix(fenwick, j) - fenwick_prefix(fenwick, i)
925    };
926    if first + count <= len {
927        sum(start + first, start + first + count)
928    } else {
929        sum(start + first, start + len) + sum(start, start + first + count - len)
930    }
931}
932
933/// One recorded feature: the ends of its edge tiles and fill runs in [`Pieces`], and its attribute.
934#[derive(Debug, Clone)]
935struct FeatureEntry<A> {
936    tiles_end: u32,
937    runs_end: u32,
938    attr: A,
939}
940
941/// Slices integer **multipolygon** features into every tile they reach, keeping original vertices.
942///
943/// The polygon counterpart to [`SlicerAll`](crate::SlicerAll), and the all-tiles counterpart to
944/// [`PolygonSlicerOne`](crate::PolygonSlicerOne): for every tile a feature's ring edges touch (its
945/// **edge tiles**), the pieces are exactly what a `PolygonSlicerOne` bound to that tile yields for each
946/// of the feature's polygons, in order. Generic over the [`PolyVertex`] type `V` (default
947/// [`Coord<i32>`]) and the per-feature attribute `A` (default `()`).
948///
949/// Reading back is per feature ([`iter_features`](Self::iter_features)), then per edge tile, polygon,
950/// and ring. Output rings keep the winding of the input rings they come from (see
951/// [`signed_area_2x`](crate::signed_area_2x) to normalize it first).
952///
953/// The cost is proportional to the routing work (the edge tiles) plus the ring edges' crossings of
954/// tile-row center lines — independent of ring length per tile. All storage is flat and reused:
955/// [`clear`](Self::clear) keeps every buffer's capacity, so one slicer per worker, cleared between
956/// features, stops allocating once warmed up.
957#[derive(Debug, Clone)]
958pub struct PolygonSlicerAll<V: PolyVertex = Coord<i32>, A = ()> {
959    grid: Grid,
960    pieces: Pieces<V>,
961    /// Per recorded feature: where its tiles and fill runs end, and its attribute.
962    features: Vec<FeatureEntry<A>>,
963    input: Input<V>,
964    scratch: Scratch,
965    /// [`MAX_FEATURE_VERTICES`], lowered by tests.
966    max_feature_verts: usize,
967    /// The tile-local `B⁺` corners `(sw, ne)` of every fill box, or `None` if they leave `i32` (then
968    /// no tile can be filled: the single-tile clip reports `Overflow` there).
969    fill_box: Option<(Coord<i32>, Coord<i32>)>,
970}
971
972impl<V: PolyVertex, A> PolygonSlicerAll<V, A> {
973    /// Create a slicer with the given tile side / per-tile output resolution `extent` and `buffer`
974    /// (same coordinate model as [`SlicerAll`](crate::SlicerAll)).
975    ///
976    /// # Errors
977    ///
978    /// - [`TileError::InvalidExtent`] if `extent` is `0` or greater than `i32::MAX`.
979    /// - [`TileError::BufferTooLarge`] if `buffer` is not strictly less than half the `extent`.
980    pub fn new(extent: u32, buffer: u16) -> Result<Self, TileError> {
981        let grid = Grid::new(extent, buffer)?;
982        // A fill box is the same in every tile's local frame: tile (0, 0)'s, where global = local.
983        let fill_box = grid
984            .tile_buffered_bounds(TileId::new(0, 0))
985            .ok()
986            .and_then(|(min, max)| {
987                let sw = Coord {
988                    x: min.x.checked_sub(1)?,
989                    y: min.y.checked_sub(1)?,
990                };
991                let ne = Coord {
992                    x: max.x.checked_add(1)?,
993                    y: max.y.checked_add(1)?,
994                };
995                Some((sw, ne))
996            });
997        Ok(Self {
998            grid,
999            pieces: Pieces {
1000                verts: Vec::new(),
1001                ring_ends: Vec::new(),
1002                poly_ends: Vec::new(),
1003                tiles: Vec::new(),
1004                runs: Vec::new(),
1005            },
1006            features: Vec::new(),
1007            input: Input {
1008                pts: Vec::new(),
1009                rings: Vec::new(),
1010                polys: Vec::new(),
1011            },
1012            scratch: Scratch::new(),
1013            max_feature_verts: MAX_FEATURE_VERTICES,
1014            fill_box,
1015        })
1016    }
1017
1018    /// The tile side / per-tile output resolution.
1019    #[must_use]
1020    pub fn extent(&self) -> u32 {
1021        self.grid.extent()
1022    }
1023
1024    /// The buffer kept around every tile, in tile-space units.
1025    #[must_use]
1026    pub fn buffer(&self) -> u16 {
1027        self.grid.buffer()
1028    }
1029
1030    /// Add one multipolygon feature carrying `attr`, slicing it into every tile it reaches. Chainable.
1031    ///
1032    /// `polygons` yields each polygon as its rings — exterior first, then holes — each a vertex slice,
1033    /// open or closed (a repeated first/last vertex is fine), e.g. `[[&exterior[..], &hole[..]]]`.
1034    /// Rings with fewer than 3 distinct vertices are ignored, and so is a polygon whose exterior is.
1035    /// The feature (and `attr`) is recorded only if some tile receives geometry.
1036    ///
1037    /// When `A = ()`, prefer [`add_feature`](Self::add_feature).
1038    ///
1039    /// **Atomic:** on error nothing is recorded, so the slicer stays usable — skip the input and go on.
1040    ///
1041    /// # Errors
1042    ///
1043    /// - [`TileError::TooManyTiles`] if the feature's edges touch more than 2^25 tiles in total,
1044    ///   counting each row an edge crosses without touching a tile as one (one budget of visits per
1045    ///   feature; covered tiles are free).
1046    /// - [`TileError::Overflow`] if coordinate math overflows `i32` (geometry too near its limits).
1047    /// - [`TileError::OutputTooLarge`] if the feature's pieces would exceed 2^28 vertices (only rings
1048    ///   winding around tiles many times, or many overlapping polygons, get there).
1049    /// - [`TileError::GeometryTooLarge`] if the geometry exceeds the `u32` indexing of the storage.
1050    #[cfg_attr(feature = "hotpath", hotpath::measure)]
1051    pub fn add_feature_with<P>(&mut self, polygons: P, attr: A) -> Result<&mut Self, TileError>
1052    where
1053        P: IntoIterator,
1054        P::Item: IntoIterator,
1055        <P::Item as IntoIterator>::Item: AsRef<[V]>,
1056    {
1057        self.input.load(polygons)?;
1058        let points = self.input.pts.iter().map(Vertex::position);
1059        if let Some((tile, origin)) = self.grid.inner_tile(points) {
1060            return self.add_in_one_tile(tile, origin, attr);
1061        }
1062        self.input.orient_polygons();
1063        self.scratch
1064            .route(self.grid, &self.input, MAX_TILE_VISITS)?;
1065        let save = self.pieces.savepoint();
1066        let ends = self
1067            .scratch
1068            .sweep(
1069                self.grid,
1070                &self.input,
1071                &mut self.pieces,
1072                self.max_feature_verts,
1073                self.fill_box.is_some(),
1074            )
1075            .and_then(|()| {
1076                Ok((
1077                    offset(self.pieces.tiles.len())?,
1078                    offset(self.pieces.runs.len())?,
1079                ))
1080            });
1081        match ends {
1082            Ok((tiles_end, runs_end)) => {
1083                if self.pieces.tiles.len() > save.tiles || self.pieces.runs.len() > save.runs {
1084                    self.features.push(FeatureEntry {
1085                        tiles_end,
1086                        runs_end,
1087                        attr,
1088                    });
1089                }
1090                Ok(self)
1091            }
1092            Err(err) => {
1093                self.pieces.rollback(save);
1094                Err(err)
1095            }
1096        }
1097    }
1098
1099    /// [`add_feature_with`](Self::add_feature_with) for a loaded feature lying in one tile's inner box
1100    /// (most buildings): every ring touches only that tile, so each goes there verbatim — exactly what
1101    /// routing and the row sweep would produce, without them.
1102    fn add_in_one_tile(
1103        &mut self,
1104        tile: TileId,
1105        origin: Coord<i32>,
1106        attr: A,
1107    ) -> Result<&mut Self, TileError> {
1108        let save = self.pieces.savepoint();
1109        let result = self.push_in_one_tile(tile, origin, save.verts);
1110        match result {
1111            Ok(()) => {
1112                self.features.push(FeatureEntry {
1113                    tiles_end: offset(self.pieces.tiles.len())?,
1114                    runs_end: offset(self.pieces.runs.len())?,
1115                    attr,
1116                });
1117                Ok(self)
1118            }
1119            Err(err) => {
1120                self.pieces.rollback(save);
1121                Err(err)
1122            }
1123        }
1124    }
1125
1126    fn push_in_one_tile(
1127        &mut self,
1128        tile: TileId,
1129        origin: Coord<i32>,
1130        verts_before: usize,
1131    ) -> Result<(), TileError> {
1132        let (input, out) = (&self.input, &mut self.pieces);
1133        for (i, ring) in input.rings.iter().enumerate() {
1134            // Each ring's distinct vertices and the closing copy, as `close_ring` keeps a ring whose
1135            // every edge touches the box.
1136            let start = out.verts.len();
1137            let closed = ring.start as usize..=(ring.start + ring.len) as usize;
1138            out.verts.extend_from_slice(&input.pts[closed]);
1139            localize(&mut out.verts[start..], origin)?;
1140            out.ring_ends.push(offset(out.verts.len())?);
1141            if input
1142                .rings
1143                .get(i + 1)
1144                .is_none_or(|next| next.poly != ring.poly)
1145            {
1146                out.poly_ends.push(offset(out.ring_ends.len())?);
1147            }
1148        }
1149        if out.verts.len() - verts_before > self.max_feature_verts {
1150            return Err(TileError::OutputTooLarge);
1151        }
1152        out.tiles.push(TileEntry {
1153            tile,
1154            poly_end: offset(out.poly_ends.len())?,
1155        });
1156        Ok(())
1157    }
1158
1159    /// Iterate the recorded features, in the order added.
1160    pub fn iter_features(&self) -> impl Iterator<Item = PolygonFeatureView<'_, V, A>> {
1161        let pieces = &self.pieces;
1162        let features = &self.features;
1163        let fill_box = self.fill_box.unwrap_or_default();
1164        (0..features.len()).map(move |f| {
1165            let (tiles_start, runs_start) = f
1166                .checked_sub(1)
1167                .map_or((0, 0), |p| (features[p].tiles_end, features[p].runs_end));
1168            let feature = &features[f];
1169            PolygonFeatureView {
1170                pieces,
1171                tiles_start: tiles_start as usize,
1172                tiles_end: feature.tiles_end as usize,
1173                runs: &pieces.runs[runs_start as usize..feature.runs_end as usize],
1174                fill_box,
1175                attr: &feature.attr,
1176            }
1177        })
1178    }
1179
1180    /// Number of features recorded.
1181    #[must_use]
1182    pub fn len(&self) -> usize {
1183        self.features.len()
1184    }
1185
1186    /// Whether nothing has been recorded yet.
1187    #[must_use]
1188    pub fn is_empty(&self) -> bool {
1189        self.features.is_empty()
1190    }
1191
1192    /// Discard everything recorded, keeping the extent/buffer config and every buffer's capacity.
1193    pub fn clear(&mut self) {
1194        self.pieces.clear();
1195        self.features.clear();
1196    }
1197
1198    /// Release the memory not needed for what is recorded, including the working memory a large
1199    /// feature left behind: [`clear`](Self::clear) keeps every buffer's capacity, so after an outlier
1200    /// a long-lived slicer would otherwise hold its peak forever.
1201    pub fn shrink_to_fit(&mut self) {
1202        self.pieces.shrink_to_fit();
1203        self.features.shrink_to_fit();
1204        self.input = Input {
1205            pts: Vec::new(),
1206            rings: Vec::new(),
1207            polys: Vec::new(),
1208        };
1209        self.scratch = Scratch::new();
1210    }
1211}
1212
1213impl<V: PolyVertex> PolygonSlicerAll<V, ()> {
1214    /// Add one multipolygon feature with no attribute — shorthand for
1215    /// [`add_feature_with`](Self::add_feature_with)`(polygons, ())`. Available only when `A = ()`.
1216    ///
1217    /// # Errors
1218    ///
1219    /// As in [`add_feature_with`](Self::add_feature_with).
1220    pub fn add_feature<P>(&mut self, polygons: P) -> Result<&mut Self, TileError>
1221    where
1222        P: IntoIterator,
1223        P::Item: IntoIterator,
1224        <P::Item as IntoIterator>::Item: AsRef<[V]>,
1225    {
1226        self.add_feature_with(polygons, ())
1227    }
1228}
1229
1230/// A borrowed view of one feature added to a [`PolygonSlicerAll`].
1231pub struct PolygonFeatureView<'a, V: PolyVertex, A = ()> {
1232    pieces: &'a Pieces<V>,
1233    /// The feature's edge tiles, `tiles_start..tiles_end` in `pieces.tiles`.
1234    tiles_start: usize,
1235    tiles_end: usize,
1236    runs: &'a [FillRun],
1237    /// The tile-local fill-box corners (unused when there are no runs).
1238    fill_box: (Coord<i32>, Coord<i32>),
1239    attr: &'a A,
1240}
1241
1242impl<'a, V: PolyVertex, A> PolygonFeatureView<'a, V, A> {
1243    /// The feature's attribute, as passed to
1244    /// [`add_feature_with`](PolygonSlicerAll::add_feature_with).
1245    #[must_use]
1246    pub fn attr(&self) -> &'a A {
1247        self.attr
1248    }
1249
1250    /// Iterate the feature's edge tiles — every tile a ring edge touches that received geometry — in
1251    /// row-major order (by `y`, then `x`).
1252    pub fn iter_tiles(&self) -> impl Iterator<Item = PolygonTileView<'a, V, A>> + use<'a, V, A> {
1253        let (pieces, attr) = (self.pieces, self.attr);
1254        (self.tiles_start..self.tiles_end).map(move |index| PolygonTileView {
1255            pieces,
1256            index,
1257            attr,
1258        })
1259    }
1260
1261    /// Iterate the feature's fully covered tiles as [`FillRun`]s, in row-major order. Storage is per
1262    /// run, not per tile, so a feature covering millions of tiles costs one run per row of them.
1263    pub fn iter_fill_runs(&self) -> impl Iterator<Item = FillRun> + use<'a, V, A> {
1264        self.runs.iter().cloned()
1265    }
1266
1267    /// The ring [`PolygonSlicerOne`](crate::PolygonSlicerOne) emits for `run`'s polygon in each tile
1268    /// of the run: the tile's buffered box grown by one unit, tile-local and closed, all synthetic
1269    /// vertices, wound like the polygon's exterior. It is the polygon's only ring there.
1270    #[must_use]
1271    pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
1272        let (sw, ne) = self.fill_box;
1273        fill_box(run.orient, sw, ne).map(V::synthetic_at)
1274    }
1275}
1276
1277/// A borrowed view of one feature's pieces in one edge tile.
1278pub struct PolygonTileView<'a, V: PolyVertex, A = ()> {
1279    pieces: &'a Pieces<V>,
1280    index: usize,
1281    attr: &'a A,
1282}
1283
1284impl<'a, V: PolyVertex, A> PolygonTileView<'a, V, A> {
1285    /// The tile this view is for.
1286    #[must_use]
1287    pub fn tile_id(&self) -> TileId {
1288        self.pieces.tiles[self.index].tile
1289    }
1290
1291    /// Iterate the clipped polygons in this tile, in input-polygon order (at most one per input
1292    /// polygon), each carrying the feature's attribute.
1293    pub fn iter_polygons(&self) -> impl Iterator<Item = PolygonView<'a, V, A>> + use<'a, V, A> {
1294        let (pieces, attr) = (self.pieces, self.attr);
1295        let start = self
1296            .index
1297            .checked_sub(1)
1298            .map_or(0, |prev| pieces.tiles[prev].poly_end as usize);
1299        (start..pieces.tiles[self.index].poly_end as usize).map(move |p| {
1300            PolygonView::new(
1301                &pieces.verts,
1302                &pieces.ring_ends,
1303                span(&pieces.poly_ends, p),
1304                attr,
1305            )
1306        })
1307    }
1308}
1309
1310copy_view!(PolygonFeatureView<A>: PolyVertex, PolygonTileView<A>: PolyVertex);
1311
1312#[cfg(test)]
1313mod tests {
1314    use super::*;
1315
1316    #[test]
1317    fn fenwick_matches_brute_force() {
1318        let values = [3, -1, 4, 1, -5, 9, 2, -6, 5, 3, -5];
1319        let mut current = values.to_vec();
1320        let mut tree = Vec::new();
1321        fenwick_build(&mut tree, values.iter().copied());
1322        for (i, delta) in [(4, 5), (0, -3), (10, 7), (6, -2)] {
1323            fenwick_add(&mut tree, i, delta);
1324            current[i] += delta;
1325            for end in 0..=current.len() {
1326                assert_eq!(
1327                    fenwick_prefix(&tree, end),
1328                    current[..end].iter().sum::<i64>()
1329                );
1330            }
1331        }
1332    }
1333
1334    #[test]
1335    fn row_center_matches_midpoint() {
1336        for extent in [1, 2, 3, 4, 25, 256, 4096] {
1337            for buffer in [0, 1, (extent - 1) / 2] {
1338                for ty in -5..=5 {
1339                    assert_eq!(
1340                        row_center(ty, extent),
1341                        i64::midpoint(ty * extent - buffer, ty * extent + extent - 1 + buffer),
1342                        "extent {extent}, buffer {buffer}, row {ty}"
1343                    );
1344                }
1345            }
1346        }
1347    }
1348
1349    #[test]
1350    fn sort_hits_matches_a_plain_sort() {
1351        // Routed order: ascending edges, each edge's tiles once. A compact span (with negative tiles)
1352        // takes the one-pass tile sort, a diagonal one the column-then-row passes, and a span far
1353        // wider than the hits are many the comparison sort.
1354        let routed = |tiles: &[(i32, i32)], per_edge: u32| -> Vec<Hit> {
1355            (0..)
1356                .zip(tiles)
1357                .map(|(i, &(tx, ty))| Hit {
1358                    ty,
1359                    tx,
1360                    edge: i / per_edge,
1361                    ring: 0,
1362                })
1363                .collect()
1364        };
1365        let long_runs: Vec<_> = [(1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1)]
1366            .iter()
1367            .flat_map(|&t| [t; 20])
1368            .collect();
1369        let short_runs: Vec<_> = (0..100).map(|i| (i % 7 - 3, i % 3 - 1)).collect();
1370        let diagonal: Vec<_> = (0..64).map(|i| ((i * 13) % 64, (i * 29) % 64)).collect();
1371        let sparse: Vec<_> = (0..40).map(|i| ((i * 977) % 5000, i % 2)).collect();
1372        for (tiles, per_edge) in [
1373            (&long_runs, 1),
1374            (&short_runs, 1),
1375            (&short_runs, 4),
1376            (&diagonal, 1),
1377            (&diagonal, 2),
1378            (&sparse, 1),
1379        ] {
1380            let mut hits = routed(tiles, per_edge);
1381            let mut expected = hits.clone();
1382            expected.sort_unstable_by_key(|h| (h.ty, h.tx, h.edge));
1383            sort_hits(&mut hits, &mut Vec::new(), &mut Vec::new());
1384            let key = |h: &Hit| (h.ty, h.tx, h.edge);
1385            assert_eq!(
1386                hits.iter().map(key).collect::<Vec<_>>(),
1387                expected.iter().map(key).collect::<Vec<_>>()
1388            );
1389        }
1390    }
1391
1392    #[test]
1393    fn output_cap_applies_to_a_feature_in_one_tile() {
1394        // Inside one tile's inner box, a feature takes the verbatim path, which honors the cap too.
1395        let square = [(5, 5), (20, 5), (20, 20), (5, 20)].map(|(x, y)| Coord { x, y });
1396        let mut s = PolygonSlicerAll::<Coord<i32>>::new(25, 2).expect("config");
1397        let empty = format!("{:?}", s.pieces);
1398        s.max_feature_verts = 3;
1399        let err = s.add_feature([[&square[..]]]).err();
1400        assert_eq!(err, Some(TileError::OutputTooLarge));
1401        assert!(s.is_empty(), "the rejected feature is not recorded");
1402        assert_eq!(format!("{:?}", s.pieces), empty, "nor any of its pieces");
1403    }
1404
1405    #[test]
1406    fn output_cap_rejects_the_feature_atomically() {
1407        let small = [(5, 5), (20, 5), (20, 20), (5, 20)].map(|(x, y)| Coord { x, y });
1408        let big = [(5, 5), (190, 5), (190, 190), (5, 190)].map(|(x, y)| Coord { x, y });
1409        let mut s = PolygonSlicerAll::<Coord<i32>>::new(25, 2).expect("config");
1410        s.add_feature([[&small[..]]]).expect("slice");
1411        let before = format!("{:?}", s.pieces);
1412        s.max_feature_verts = 40;
1413        let err = s.add_feature([[&big[..]]]).err();
1414        assert_eq!(err, Some(TileError::OutputTooLarge));
1415        assert_eq!(s.len(), 1, "the rejected feature is not recorded");
1416        assert_eq!(format!("{:?}", s.pieces), before, "nor any of its pieces");
1417        s.max_feature_verts = MAX_FEATURE_VERTICES;
1418        s.add_feature([[&big[..]]]).expect("under the real cap");
1419        assert_eq!(s.len(), 2);
1420    }
1421
1422    #[test]
1423    fn shrink_to_fit_releases_working_memory() {
1424        let ring = [(5, 5), (190, 5), (190, 190), (5, 190)].map(|(x, y)| Coord { x, y });
1425        let mut s = PolygonSlicerAll::<Coord<i32>>::new(25, 2).expect("config");
1426        s.add_feature([[&ring[..]]]).expect("slice");
1427        let output = format!("{:?}", s.pieces);
1428        s.clear();
1429        assert!(s.scratch.hits.capacity() > 0 && s.pieces.verts.capacity() > 0);
1430        s.shrink_to_fit();
1431        assert_eq!(s.scratch.hits.capacity(), 0);
1432        assert_eq!(s.input.pts.capacity(), 0);
1433        assert_eq!(s.pieces.verts.capacity(), 0);
1434        s.add_feature([[&ring[..]]]).expect("slice");
1435        assert_eq!(
1436            format!("{:?}", s.pieces),
1437            output,
1438            "a shrunk slicer works the same"
1439        );
1440    }
1441
1442    #[test]
1443    fn budget_counts_visited_tiles_across_rings() {
1444        let square = [(0, 0), (900, 0), (900, 900), (0, 900)].map(|(x, y)| Coord { x, y });
1445        let diagonal = [(3, 5), (2990, 2000), (40, 2900)].map(|(x, y)| Coord { x, y });
1446        let grid = Grid::new(10, 2).expect("config");
1447        let mut input = Input {
1448            pts: Vec::new(),
1449            rings: Vec::new(),
1450            polys: Vec::new(),
1451        };
1452        let mut scratch = Scratch::new();
1453        // Each ring as its own polygon of one feature.
1454        let mut visits = |rings: &[&[Coord<i32>]], budget| {
1455            input.load(rings.iter().map(|r| [*r])).expect("load");
1456            scratch
1457                .route(grid, &input, budget)
1458                .map(|()| i64::try_from(scratch.hits.len()).unwrap())
1459        };
1460        let alone = visits(&[&diagonal], MAX_TILE_VISITS).expect("route");
1461        let square_alone = visits(&[&square], MAX_TILE_VISITS).expect("route");
1462        let both = visits(&[&square, &diagonal], MAX_TILE_VISITS).expect("route");
1463        // Every charge is one touched tile (a hit), and the feature's rings draw on one budget: the
1464        // pair fails one visit short, though either ring alone needs less.
1465        assert_eq!(alone + square_alone, both);
1466        assert_eq!(visits(&[&diagonal], alone), Ok(alone));
1467        assert_eq!(visits(&[&square, &diagonal], both), Ok(both));
1468        assert_eq!(
1469            visits(&[&square, &diagonal], both - 1),
1470            Err(TileError::TooManyTiles)
1471        );
1472        assert_eq!(
1473            visits(&[&diagonal], alone - 1),
1474            Err(TileError::TooManyTiles)
1475        );
1476    }
1477
1478    #[test]
1479    fn clear_keeps_capacity() {
1480        let ring = [(5, 5), (190, 5), (190, 190), (5, 190)].map(|(x, y)| Coord { x, y });
1481        let hole = [(60, 60), (60, 120), (120, 120), (120, 60)].map(|(x, y)| Coord { x, y });
1482        let mut s = PolygonSlicerAll::<Coord<i32>>::new(25, 2).expect("config");
1483        s.add_feature([[&ring[..], &hole[..]]]).expect("slice");
1484        let capacities = |s: &PolygonSlicerAll<Coord<i32>>| {
1485            [
1486                s.pieces.verts.capacity(),
1487                s.pieces.ring_ends.capacity(),
1488                s.pieces.tiles.capacity(),
1489                s.pieces.runs.capacity(),
1490                s.features.capacity(),
1491                s.input.pts.capacity(),
1492                s.scratch.hits.capacity(),
1493                s.scratch.crossings.capacity(),
1494            ]
1495        };
1496        let warm = capacities(&s);
1497        let output = format!("{:?}", s.pieces);
1498        s.clear();
1499        assert!(s.is_empty());
1500        assert_eq!(capacities(&s), warm, "clear keeps every buffer");
1501        s.add_feature([[&ring[..], &hole[..]]]).expect("slice");
1502        assert_eq!(
1503            capacities(&s),
1504            warm,
1505            "a warmed-up slicer does not grow for the same feature"
1506        );
1507        assert_eq!(format!("{:?}", s.pieces), output);
1508    }
1509}