Skip to main content

map_tile_toolkit/
slicer.rs

1//! The public slicing API: [`Slicer`] takes **one geometry** at a time — a line, a point or a polygon —
2//! and returns a lazy iterator over its slices: [`Slicer::line`] / [`Slicer::point`] for every tile
3//! the geometry reaches, or [`Slicer::tile_line`] / [`Slicer::tile_point`] for one given tile.
4//!
5//! A multi-geometry is sliced one element at a time, by the caller: whoever keeps a feature's
6//! attributes with each of its slices, tile by tile, has to do that for a multi-feature too, so the
7//! slicer has no parts of its own to index. Slices of several elements that land in one tile are
8//! simply several slices there, which the caller groups (a `MultiLineString` in that tile, say).
9//!
10//! A line run is never copied: it borrows the caller's vertices ([`Local`]) and is computed only when
11//! the iterator reaches it, so a line with millions of vertices costs no memory beyond the iterator's
12//! own few fields, and stopping early stops the work. A polygon piece is built in the slicer's
13//! working memory, one at a time (a polygon inside one tile is lent as it is).
14//!
15//! Each call reads the whole geometry once, to bound it. That settles every possible overflow up
16//! front: the call returns a [`TileError`] or nothing at all, and the line and point iterators are
17//! plain [`Iterator`]s whose items cannot fail. (The polygon iterator still yields `Result`s, for a
18//! piece too large to index; see [`PolygonSlices`].)
19//!
20//! **Order is unspecified** for the all-tiles iterators: a line that leaves and re-enters a tile
21//! gives that tile several runs, each its own item.
22//!
23//! Runs follow the keep-original rule (see [`clip_polyline`](crate::clip_polyline)): a vertex is kept
24//! when one of its segments touches the tile's buffered box, and a run breaks only where a vertex is
25//! dropped. So a run is always a **contiguous range** of the line's vertices, which [`Local`] lends
26//! together with the tile origin.
27//!
28//! In terms of a line's segments (skipping zero-length ones), with "touches" meaning
29//! [`segment_intersects`] against the tile's buffered box, a run of tile `t` is the vertex range of
30//! segments `k..=e` where
31//! - `k` touches `t`, but neither `k − 1` nor `k − 2` does ([`Prev::run_started`]: the run starts
32//!   there, and only there, so each run is yielded once, at its first segment), and
33//! - the run ends at the first `e` after which neither `e + 1` nor `e + 2` touches `t`
34//!   ([`extend_run`]: one missed segment is bridged, keeping both its endpoints).
35//!
36//! [`LineSlices`] finds each `k` while walking the segments in order, [`TileLines`] by scanning the
37//! line for one tile, and both extend with the same [`extend_run`], so they agree by construction.
38
39use core::iter::FusedIterator;
40
41use geo_types::Coord;
42
43use crate::TileError;
44use crate::clip_polyline::segment_intersects;
45use crate::grid::{Grid, Located, SegmentTiles};
46use crate::polygon::{
47    FillRun, PolygonPiece, PolygonSlices, Scratch, clip_polygon_to_tile, fill_ring,
48};
49use crate::tile::TileId;
50use crate::vertex::{PolyVertex, Vertex};
51
52/// Slices one geometry at a time into per-tile pieces on an integer grid, lazily.
53///
54/// Coordinates are integers in tile space: a vertex belongs to tile `x.div_euclid(extent)`, whose own
55/// area is `[0, extent)` in its local frame (`x − tile·extent`); every tile's clip box grows `buffer`
56/// on each side. Slices come in that local frame, so a vertex outside the tile's own area — in its
57/// buffer, a polygon's synthetic corner one unit past the buffer, or a line's first vertex beyond the
58/// box — lies outside `[0, extent)`. Project, simplify, and scale float source data into this space
59/// before slicing.
60///
61/// Generic over the [`Vertex`] type `V` (defaults to [`Coord<i32>`]): plain coordinates or
62/// payload-carrying vertices (e.g. a per-vertex M value via [`Measured`](crate::Measured)), whose
63/// payload rides through slicing unchanged.
64///
65/// The slicer never panics: bad input yields a [`TileError`] from the call itself (and, for a polygon
66/// piece too large to index, as an `Err` item that ends the iteration).
67///
68/// Lines and points take `&self`, so one slicer can serve many threads. Polygons take `&mut self`:
69/// they are sliced in the slicer's reusable working memory, so use one slicer per worker.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Slicer<V: Vertex = Coord<i32>> {
72    grid: Grid,
73    /// Working memory for polygon slicing, allocated by the first polygon and kept for its capacity
74    /// (never part of the slicer's value).
75    poly: Scratch<V>,
76}
77
78impl<V: Vertex> Slicer<V> {
79    /// Create a slicer with the given tile side / per-tile output resolution `extent` and `buffer`.
80    ///
81    /// # Errors
82    ///
83    /// - [`TileError::InvalidExtent`] if `extent` is `0`, or `extent + buffer` exceeds `i32::MAX`.
84    /// - [`TileError::BufferTooLarge`] if `buffer` is not strictly less than half the `extent`.
85    pub fn new(extent: u32, buffer: u16) -> Result<Self, TileError> {
86        Ok(Self {
87            grid: Grid::new(extent, buffer)?,
88            poly: Scratch::new(),
89        })
90    }
91
92    /// The tile side / per-tile output resolution: a tile's own area is `0..extent` in its local
93    /// frame (its buffer and kept vertices beyond it lie outside).
94    #[must_use]
95    pub fn extent(&self) -> u32 {
96        self.grid.extent()
97    }
98
99    /// The buffer kept around every tile, in tile-space units.
100    #[must_use]
101    pub fn buffer(&self) -> u16 {
102        self.grid.buffer()
103    }
104
105    /// Release the working memory the slicer keeps between features. It only ever grows to what the
106    /// largest polygon row or piece needed, but after an outlier a long-lived slicer would otherwise
107    /// hold that peak forever.
108    pub fn shrink_to_fit(&mut self) {
109        self.poly = Scratch::new();
110    }
111
112    /// Slice a line into every tile it touches.
113    ///
114    /// Yields one [`LineSlice`] per run, in no particular order. A line that leaves a tile and comes
115    /// back gives that tile several items. Consecutive duplicate vertices are skipped. For a
116    /// multi-line, call this for each of its lines.
117    ///
118    /// The call reads every vertex once to bound the line, so that nothing can fail once the
119    /// iteration starts; the slices themselves are still computed one at a time as it advances.
120    ///
121    /// # Errors
122    ///
123    /// [`TileError::Overflow`] if the line lies too close to the `i32` limits for this `extent` and
124    /// `buffer`: a tile box beyond `i32`, or a vertex more than an `i32` span from the origin of a
125    /// tile its bounding box reaches. (So a line spanning more than `2^31` units on one axis is
126    /// rejected whole, even if each run would fit its tile.)
127    pub fn line<'a>(&self, line: &'a [V]) -> Result<LineSlices<'a, V>, TileError> {
128        if let Some((lo, hi)) = bounds(line) {
129            self.grid.check_bounds(lo, hi)?;
130        }
131        Ok(LineSlices {
132            grid: self.grid,
133            pts: line,
134            cursor: None,
135        })
136    }
137
138    /// Clip a line to one `tile`: yields its runs there, each in walk order — exactly the runs
139    /// [`line`](Self::line) yields for that tile.
140    ///
141    /// Like [`line`](Self::line), the call reads every vertex once; a line whose bounds miss the
142    /// tile's buffered box yields nothing without being scanned.
143    ///
144    /// # Errors
145    ///
146    /// [`TileError::Overflow`] if `tile`'s buffered box overflows `i32` (a tile far outside the
147    /// representable range for this `extent`), or if the line reaches the tile but has a vertex more
148    /// than an `i32` span from the tile origin.
149    pub fn tile_line<'a>(
150        &self,
151        tile: TileId,
152        line: &'a [V],
153    ) -> Result<TileLines<'a, V>, TileError> {
154        let (min, max) = self.grid.tile_buffered_bounds(tile)?;
155        let origin = self.grid.origin_of(min);
156        let mut next = 0;
157        match bounds(line) {
158            Some((lo, hi)) if hi.x >= min.x && lo.x <= max.x && hi.y >= min.y && lo.y <= max.y => {
159                // The line reaches the tile: every vertex must fit the local frame.
160                let fits = |p: Coord<i32>| {
161                    p.x.checked_sub(origin.x).is_some() && p.y.checked_sub(origin.y).is_some()
162                };
163                if !fits(lo) || !fits(hi) {
164                    return Err(TileError::Overflow);
165                }
166            }
167            // Nothing to scan: no vertices, or none near the tile.
168            _ => next = line.len(),
169        }
170        Ok(TileLines {
171            min,
172            max,
173            origin,
174            tile,
175            pts: line,
176            next,
177        })
178    }
179
180    /// Slice a point into every tile whose buffered box holds it: its own tile, plus up to three
181    /// neighbors when it lies within `buffer` of an edge. Yields one [`PointSlice`] per tile. For a
182    /// multi-point, call this for each of its points.
183    ///
184    /// # Errors
185    ///
186    /// [`TileError::Overflow`] if one of those tiles has a buffered box beyond `i32`.
187    pub fn point(&self, point: V) -> Result<PointSlices<V>, TileError> {
188        let p = point.position();
189        self.grid.check_tiles(p, p)?;
190        Ok(PointSlices::new(self.grid, point))
191    }
192
193    /// The `point` in `tile`'s local frame, if the tile's buffered box holds it.
194    ///
195    /// # Errors
196    ///
197    /// [`TileError::Overflow`] if `tile`'s buffered box overflows `i32`.
198    pub fn tile_point(&self, tile: TileId, point: V) -> Result<Option<V>, TileError> {
199        let (extent, buffer) = (i64::from(self.grid.extent()), i64::from(self.grid.buffer()));
200        let p = point.position();
201        // Per axis: the tile's buffered box `[base − buffer, base + extent − 1 + buffer]`, which must fit
202        // `i32` (what `Grid::tile_buffered_bounds` checks), and the point's offset from its base.
203        let axis = |tile: i32, v: i32| {
204            let base = i64::from(tile) * extent;
205            let (lo, hi) = (base - buffer, base + extent - 1 + buffer);
206            if lo < i64::from(i32::MIN) || hi > i64::from(i32::MAX) {
207                return Err(TileError::Overflow);
208            }
209            let v = i64::from(v);
210            // Inside the box, so within `extent + buffer` of the base: the offset fits `i32`.
211            #[expect(clippy::cast_possible_truncation, reason = "within the box, see above")]
212            Ok((lo..=hi).contains(&v).then_some((v - base) as i32))
213        };
214        let (x, y) = (axis(tile.x, p.x)?, axis(tile.y, p.y)?);
215        Ok(x.zip(y).map(|(x, y)| point.with_position(Coord { x, y })))
216    }
217}
218
219impl<V: PolyVertex> Slicer<V> {
220    /// Slice a polygon into every tile it reaches. `rings` are its rings — the exterior first, then
221    /// the holes — each open or closed (a repeated first/last vertex is fine). Rings with fewer than
222    /// 3 vertices once consecutive duplicates are dropped are ignored, and so is a polygon whose
223    /// exterior is. For a multipolygon, call this for each of its polygons.
224    ///
225    /// Yields a [`PolygonPiece`] for every tile a ring edge touches (its
226    /// **edge tiles**), exactly what [`tile_polygon`](Self::tile_polygon) yields there, and a
227    /// [`FillRun`] for every row span of tiles the polygon covers entirely. Output
228    /// rings keep the winding of the input rings (see [`signed_area_2x`](crate::signed_area_2x) to
229    /// normalize it first).
230    ///
231    /// Lazy and lending: see [`PolygonSlices`]. The call reads the rings once, to bound and index
232    /// them (only a ring with consecutive duplicate vertices is copied, to drop them); after that the
233    /// iterator holds one tile row of the polygon at a time, and builds each piece as it is reached.
234    /// Every row of the polygon's bounding box is visited, including rows where it yields nothing.
235    ///
236    /// # Errors
237    ///
238    /// - [`TileError::Overflow`] if the polygon lies too close to the `i32` limits for this `extent`
239    ///   and `buffer`: a tile box (grown one unit, for the synthetic corners) beyond `i32`, or a
240    ///   vertex more than an `i32` span from the origin of a tile its bounding box reaches.
241    /// - [`TileError::GeometryTooLarge`] if there are more than `u32::MAX` rings or edges.
242    ///
243    /// The only error left for the iterator is a piece of more than `u32::MAX` vertices.
244    #[inline]
245    pub fn polygon<'s, R: AsRef<[V]>>(
246        &'s mut self,
247        rings: &'s [R],
248    ) -> Result<PolygonSlices<'s, V, R>, TileError> {
249        PolygonSlices::new(self.grid, rings, self.poly.get())
250    }
251
252    /// Clip a polygon to one `tile`: its piece there, or `None` if it leaves nothing — what
253    /// [`polygon`](Self::polygon) yields for that tile, with a covered tile's
254    /// [`FillRun`] as its fill ring. The piece borrows the slicer's working memory
255    /// until the next call.
256    ///
257    /// # Errors
258    ///
259    /// - [`TileError::Overflow`] if `tile`'s buffered box overflows `i32`, or a synthetic corner or
260    ///   kept vertex does.
261    /// - [`TileError::GeometryTooLarge`] if there are more than `u32::MAX` rings, or the piece has
262    ///   more than `u32::MAX` vertices.
263    pub fn tile_polygon<'s, R: AsRef<[V]>>(
264        &'s mut self,
265        tile: TileId,
266        rings: &'s [R],
267    ) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
268        u32::try_from(rings.len()).map_err(|_| TileError::GeometryTooLarge)?;
269        clip_polygon_to_tile(self.grid, tile, rings, &mut self.poly.get().scratch)
270    }
271
272    /// The ring [`tile_polygon`](Self::tile_polygon) yields for `run`'s polygon in each tile of the
273    /// run: the tile's buffered box grown by one unit, tile-local and closed (its first vertex
274    /// repeated), all synthetic vertices, wound like the polygon's exterior. See also
275    /// [`PolygonSlices::fill_ring`], for use while the iterator borrows the slicer.
276    #[must_use]
277    pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
278        fill_ring(self.grid, run)
279    }
280}
281
282/// Vertices in a tile's local frame — `v − origin`, computed as they are read — every position checked
283/// to fit `i32` before the slice is handed out.
284///
285/// A line run is lent from the caller's input, and skips consecutive duplicate positions (the first
286/// of each is kept). A polygon ring has no consecutive duplicates, and is closed: its first vertex
287/// repeated at the end, which a ring lent open has [`iter`](Self::iter) append (drop it for an MVT
288/// `ClosePath`). It is lent from the input when the polygon lies inside one tile, else built in the
289/// slicer's working memory, clipped and with synthetic corners.
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub struct Local<'a, V> {
292    verts: &'a [V],
293    origin: Coord<i32>,
294    shape: Shape,
295}
296
297/// What [`Local::iter`] does besides subtracting the origin.
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299enum Shape {
300    /// A line run: skip consecutive duplicates.
301    Run,
302    /// A distinct ring that is already closed.
303    Ring,
304    /// A distinct ring lent open: repeat its first vertex at the end.
305    OpenRing,
306}
307
308impl<'a, V: Vertex> Local<'a, V> {
309    /// A line run, lent as it is.
310    pub(crate) fn run(verts: &'a [V], origin: Coord<i32>) -> Self {
311        Self {
312            verts,
313            origin,
314            shape: Shape::Run,
315        }
316    }
317
318    /// A polygon ring (distinct vertices), closed by `iter` unless it `is_closed` already.
319    pub(crate) fn ring(verts: &'a [V], origin: Coord<i32>, is_closed: bool) -> Self {
320        Self {
321            verts,
322            origin,
323            shape: if is_closed {
324                Shape::Ring
325            } else {
326                Shape::OpenRing
327            },
328        }
329    }
330
331    /// The vertices in the tile's local frame.
332    #[must_use]
333    #[inline]
334    pub fn iter(&self) -> LocalIter<'a, V> {
335        LocalIter {
336            verts: self.verts.iter(),
337            origin: self.origin,
338            dedupe: self.shape == Shape::Run,
339            last: None,
340            closing: if self.shape == Shape::OpenRing {
341                self.verts.first()
342            } else {
343                None
344            },
345        }
346    }
347
348    /// The vertices collected into a `Vec`, in the tile's local frame.
349    #[must_use]
350    pub fn to_vec(self) -> Vec<V> {
351        self.iter().collect()
352    }
353
354    /// The vertices in the **global** frame: each local vertex plus [`origin`](Self::origin),
355    /// consecutive duplicates included. For a line run this is a subslice of the input line, so its
356    /// position in the line tells which vertices the run covers; for a clipped polygon ring it is the
357    /// slicer's built ring. For a ring lent open this is without the repeated first vertex.
358    #[must_use]
359    pub fn global(&self) -> &'a [V] {
360        self.verts
361    }
362
363    /// The tile origin (`tile · extent`) the local frame is relative to.
364    #[must_use]
365    pub fn origin(&self) -> Coord<i32> {
366        self.origin
367    }
368}
369
370impl<'a, V: Vertex> IntoIterator for &Local<'a, V> {
371    type Item = V;
372    type IntoIter = LocalIter<'a, V>;
373
374    fn into_iter(self) -> Self::IntoIter {
375        self.iter()
376    }
377}
378
379impl<'a, V: Vertex> IntoIterator for Local<'a, V> {
380    type Item = V;
381    type IntoIter = LocalIter<'a, V>;
382
383    fn into_iter(self) -> Self::IntoIter {
384        self.iter()
385    }
386}
387
388/// Iterator over a [`Local`]'s vertices.
389#[derive(Debug, Clone)]
390pub struct LocalIter<'a, V> {
391    verts: core::slice::Iter<'a, V>,
392    origin: Coord<i32>,
393    /// Whether to skip consecutive duplicates, and the last position seen if so.
394    dedupe: bool,
395    last: Option<Coord<i32>>,
396    /// The vertex that closes the ring, once the rest is done.
397    closing: Option<&'a V>,
398}
399
400impl<V: Vertex> LocalIter<'_, V> {
401    /// `v` in the local frame (checked to fit before the slice was yielded).
402    #[inline]
403    fn local(&self, v: V) -> V {
404        let p = v.position();
405        v.with_position(Coord {
406            x: p.x.wrapping_sub(self.origin.x),
407            y: p.y.wrapping_sub(self.origin.y),
408        })
409    }
410}
411
412impl<V: Vertex> Iterator for LocalIter<'_, V> {
413    type Item = V;
414
415    #[inline]
416    fn next(&mut self) -> Option<V> {
417        for &v in self.verts.by_ref() {
418            if self.dedupe {
419                let p = v.position();
420                if self.last == Some(p) {
421                    continue;
422                }
423                self.last = Some(p);
424            }
425            return Some(self.local(v));
426        }
427        let closing = *self.closing.take()?;
428        Some(self.local(closing))
429    }
430
431    fn size_hint(&self) -> (usize, Option<usize>) {
432        let closing = usize::from(self.closing.is_some());
433        let left = self.verts.len();
434        // Skipped duplicates make a run shorter, but never empty while vertices are left.
435        let low = if self.dedupe {
436            usize::from(left > 0)
437        } else {
438            left
439        };
440        (low + closing, Some(left + closing))
441    }
442}
443
444impl<V: Vertex> FusedIterator for LocalIter<'_, V> {}
445
446/// One run of a line in one tile: a contiguous piece of it, in the tile's local frame.
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub struct LineSlice<'a, V> {
449    tile: TileId,
450    run: Local<'a, V>,
451}
452
453impl<'a, V: Vertex> LineSlice<'a, V> {
454    /// The tile this run belongs to.
455    #[must_use]
456    pub fn tile(&self) -> TileId {
457        self.tile
458    }
459
460    /// The run's vertices, in the tile's local frame (at least two distinct positions).
461    #[must_use]
462    pub fn run(&self) -> Local<'a, V> {
463        self.run
464    }
465}
466
467/// The index of the first vertex after `i` at a different position, if any.
468fn next_distinct<V: Vertex>(pts: &[V], i: usize) -> Option<usize> {
469    let p = pts.get(i)?.position();
470    (i + 1..pts.len()).find(|&j| pts[j].position() != p)
471}
472
473/// The bounding box `(lo, hi)` of every vertex of `pts`, or `None` if there is none. One pass, which
474/// is what lets the iterators skip every per-vertex overflow check afterwards.
475fn bounds<V: Vertex>(pts: &[V]) -> Option<(Coord<i32>, Coord<i32>)> {
476    let (mut lo, mut hi) = (
477        Coord {
478            x: i32::MAX,
479            y: i32::MAX,
480        },
481        Coord {
482            x: i32::MIN,
483            y: i32::MIN,
484        },
485    );
486    for v in pts {
487        let p = v.position();
488        (lo.x, lo.y) = (lo.x.min(p.x), lo.y.min(p.y));
489        (hi.x, hi.y) = (hi.x.max(p.x), hi.y.max(p.y));
490    }
491    (lo.x <= hi.x).then_some((lo, hi))
492}
493
494/// Extend a run that has reached vertex `end` (inside the line `pts`) while the next segment, or the one
495/// after it (a single-segment excursion out and back), touches the box `[min, max]`. Returns the
496/// run's last vertex index.
497fn extend_run<V: Vertex>(pts: &[V], mut end: usize, min: Coord<i32>, max: Coord<i32>) -> usize {
498    while let Some(n1) = next_distinct(pts, end) {
499        let (a, b) = (pts[end].position(), pts[n1].position());
500        if segment_intersects(a, b, min, max) {
501            end = n1;
502            continue;
503        }
504        let Some(n2) = next_distinct(pts, n1) else {
505            break;
506        };
507        if segment_intersects(b, pts[n2].position(), min, max) {
508            end = n2;
509            continue;
510        }
511        break;
512    }
513    end
514}
515
516/// A segment, as its two endpoints.
517type Segment = (Coord<i32>, Coord<i32>);
518
519/// The two segments before the current one in the line, most recent first.
520#[derive(Debug, Clone, Copy, Default)]
521struct Prev([Option<Segment>; 2]);
522
523impl Prev {
524    /// `self` after `seg`, the segment just left, joins it.
525    #[inline]
526    fn pushed(self, seg: Segment) -> Self {
527        Self([Some(seg), self.0[0]])
528    }
529
530    /// Whether the previous segment touches the box `[min, max]`: the current one only continues its
531    /// run there.
532    #[inline]
533    fn continues(self, min: Coord<i32>, max: Coord<i32>) -> bool {
534        self.0[0].is_some_and(|(a, b)| segment_intersects(a, b, min, max))
535    }
536
537    /// Whether a run in the box `[min, max]` already started before the current segment: the previous
538    /// one continues it, and the one before bridges a single missed segment (see [`extend_run`]).
539    #[expect(
540        clippy::inline_always,
541        reason = "run per tile of a walk; left out of line it costs the line benchmarks 3%"
542    )]
543    #[inline(always)]
544    fn run_started(self, min: Coord<i32>, max: Coord<i32>) -> bool {
545        self.0
546            .iter()
547            .flatten()
548            .any(|&(a, b)| segment_intersects(a, b, min, max))
549    }
550}
551
552/// The tiles of the current segment still to be considered.
553#[derive(Debug, Clone)]
554enum SegTiles {
555    /// The whole segment lies in one tile's inner box, so it touches only that tile.
556    One(TileId),
557    /// Any other segment: walked row by row through exactly the tiles it touches.
558    Walk(SegmentTiles),
559    /// Nothing left: every tile is done, or the segment only continues a run already yielded.
560    Done,
561}
562
563/// Position inside a [`LineSlices`] walk.
564#[derive(Debug, Clone)]
565struct LineCursor {
566    /// The current segment, as indices of its two (distinct) vertices.
567    a: usize,
568    c: usize,
569    prev: Prev,
570    tiles: SegTiles,
571    /// The tile owning `c`, carried to the next segment's fast-path test.
572    loc_c: Located,
573}
574
575/// Where the next run lies: its tile, first and last vertex indices, and the tile origin.
576struct RunAt {
577    tile: TileId,
578    first: usize,
579    last: usize,
580    origin: Coord<i32>,
581}
582
583/// A segment inside one tile's inner box touches only that tile; when the previous segment touches it
584/// too, the segment only continues a run already yielded. So do all the following segments that stay
585/// in that inner box: jump past them at once, with one containment test each, and leave the cursor on
586/// the last of them with nothing to yield ([`SegTiles::Done`]).
587///
588/// Only the previous segment is consulted, not the one before: a segment that merely bridges to a
589/// run takes the general path in [`LineSlices::next_run`].
590fn skip_inside<V: Vertex>(grid: Grid, pts: &[V], cur: &mut LineCursor) {
591    let loc = cur.loc_c;
592    let (min, max) = grid.box_of(&loc);
593    if !cur.prev.continues(min, max) {
594        return;
595    }
596    while let Some(n) = next_distinct(pts, cur.c) {
597        if !loc.contains_inner(pts[n].position()) {
598            break;
599        }
600        cur.prev = cur
601            .prev
602            .pushed((pts[cur.a].position(), pts[cur.c].position()));
603        (cur.a, cur.c) = (cur.c, n);
604    }
605    cur.tiles = SegTiles::Done;
606}
607
608/// Lazy iterator over the runs of a line in every tile it touches; see [`Slicer::line`].
609#[derive(Debug, Clone)]
610pub struct LineSlices<'a, V> {
611    grid: Grid,
612    pts: &'a [V],
613    cursor: Option<LineCursor>,
614}
615
616impl<V: Vertex> LineSlices<'_, V> {
617    /// Route the segment `a`–`c`, with `la` the tile owning `a`.
618    fn route(&self, a: usize, c: usize, la: Located) -> (SegTiles, Located) {
619        let (pa, pc) = (self.pts[a].position(), self.pts[c].position());
620        if la.contains_inner(pa) && la.contains_inner(pc) {
621            return (SegTiles::One(la.owner), la);
622        }
623        let loc_c = if la.contains_core(pc) {
624            la
625        } else {
626            self.grid.locate_fit(pc)
627        };
628        (SegTiles::Walk(SegmentTiles::new(self.grid, pa, pc)), loc_c)
629    }
630
631    /// Move to the next segment; `false` when there is none.
632    fn advance(&mut self) -> bool {
633        let pts = self.pts;
634        // The next segment: its first vertex, second vertex, the segments before it, and the tile
635        // owning the first vertex if the walk already knows it (everywhere but at the start).
636        let next = match &self.cursor {
637            None => next_distinct(pts, 0).map(|c| (0, c, Prev::default(), None)),
638            Some(cur) => next_distinct(pts, cur.c).map(|c| {
639                let seg = (pts[cur.a].position(), pts[cur.c].position());
640                (cur.c, c, cur.prev.pushed(seg), Some(cur.loc_c))
641            }),
642        };
643        let Some((a, c, prev, known)) = next else {
644            return false;
645        };
646        let la = known.unwrap_or_else(|| self.grid.locate_fit(pts[a].position()));
647        let (tiles, loc_c) = self.route(a, c, la);
648        let mut cur = LineCursor {
649            a,
650            c,
651            prev,
652            tiles,
653            loc_c,
654        };
655        if matches!(cur.tiles, SegTiles::One(_)) {
656            skip_inside(self.grid, pts, &mut cur);
657        }
658        self.cursor = Some(cur);
659        true
660    }
661
662    /// Where the next run lies, if there is one.
663    #[cfg_attr(feature = "hotpath", hotpath::measure)]
664    fn next_run(&mut self) -> Option<RunAt> {
665        loop {
666            let Some(cur) = &mut self.cursor else {
667                if !self.advance() {
668                    return None;
669                }
670                continue;
671            };
672            // A segment in one tile's inner box touches only that tile, whose box is cached.
673            let (tile, fast) = match &mut cur.tiles {
674                SegTiles::One(tile) => {
675                    let tile = *tile;
676                    cur.tiles = SegTiles::Done;
677                    (Some(tile), true)
678                }
679                SegTiles::Walk(walk) => (walk.next(), false),
680                SegTiles::Done => (None, false),
681            };
682            let Some(tile) = tile else {
683                if !self.advance() {
684                    return None;
685                }
686                continue;
687            };
688            let (min, max) = if fast {
689                self.grid.box_of(&cur.loc_c)
690            } else {
691                self.grid.tile_box(tile)
692            };
693            if cur.prev.run_started(min, max) {
694                continue;
695            }
696            return Some(RunAt {
697                tile,
698                first: cur.a,
699                last: extend_run(self.pts, cur.c, min, max),
700                origin: self.grid.origin_of(min),
701            });
702        }
703    }
704}
705
706impl<'a, V: Vertex> Iterator for LineSlices<'a, V> {
707    type Item = LineSlice<'a, V>;
708
709    fn next(&mut self) -> Option<Self::Item> {
710        let at = self.next_run()?;
711        Some(LineSlice {
712            tile: at.tile,
713            run: Local::run(&self.pts[at.first..=at.last], at.origin),
714        })
715    }
716}
717
718impl<V: Vertex> FusedIterator for LineSlices<'_, V> {}
719
720/// Lazy iterator over the runs of a line in one tile; see [`Slicer::tile_line`].
721#[derive(Debug, Clone)]
722pub struct TileLines<'a, V> {
723    tile: TileId,
724    min: Coord<i32>,
725    max: Coord<i32>,
726    origin: Coord<i32>,
727    pts: &'a [V],
728    /// The vertex the scan resumes from.
729    next: usize,
730}
731
732impl<'a, V: Vertex> Iterator for TileLines<'a, V> {
733    type Item = LineSlice<'a, V>;
734
735    #[cfg_attr(feature = "hotpath", hotpath::measure)]
736    fn next(&mut self) -> Option<Self::Item> {
737        let (pts, min, max) = (self.pts, self.min, self.max);
738        // Find the next segment touching the box; the run starts at its first vertex.
739        let mut a = self.next;
740        while a < pts.len() {
741            let Some(c) = next_distinct(pts, a) else {
742                break;
743            };
744            if segment_intersects(pts[a].position(), pts[c].position(), min, max) {
745                let end = extend_run(pts, c, min, max);
746                self.next = end;
747                return Some(LineSlice {
748                    tile: self.tile,
749                    run: Local::run(&pts[a..=end], self.origin),
750                });
751            }
752            a = c;
753        }
754        self.next = pts.len();
755        None
756    }
757}
758
759impl<V: Vertex> FusedIterator for TileLines<'_, V> {}
760
761/// One axis of a point's tiles: its own, and the neighbor within `buffer` of an edge (the buffer is
762/// under half a tile, so at most one per axis). The point lies `rem` past its own tile's origin; the
763/// tile at offset `d` from its own (`-1`, `0` or `1`) has the point `rem − d · extent` past its
764/// origin.
765#[derive(Debug, Clone, Copy)]
766struct AxisTiles {
767    /// The point's own tile, and its offset from that tile's origin.
768    tile: i32,
769    rem: i32,
770    /// The first offset `d` (`-1` when the previous tile holds the point too, else `0`), and how many
771    /// tiles there are.
772    first: i32,
773    len: u8,
774}
775
776impl AxisTiles {
777    /// One division. A neighbor needs `buffer > 0`, so `extent >= 3` and the neighbor's index stays in
778    /// `i32`; the offsets fit because [`Slicer::point`] proved the neighbor's whole buffered box does.
779    fn new(v: i32, extent: i32, buffer: i32) -> Self {
780        // Floor division with one `/` and `%` (`div_euclid` + `rem_euclid` would divide twice).
781        let (mut tile, mut rem) = (v / extent, v % extent);
782        if rem < 0 {
783            (tile, rem) = (tile - 1, rem + extent);
784        }
785        // In the first `buffer` units of its tile the point is in the previous tile's buffer, in the
786        // last `buffer` units in the next one's (with `buffer == 0` neither).
787        let (before, after) = (rem < buffer, rem >= extent - buffer);
788        Self {
789            tile,
790            rem,
791            first: -i32::from(before),
792            len: 1 + u8::from(before) + u8::from(after),
793        }
794    }
795}
796
797/// A point in one tile, in the tile's local frame.
798#[derive(Debug, Clone, Copy, PartialEq, Eq)]
799pub struct PointSlice<V> {
800    tile: TileId,
801    point: V,
802}
803
804impl<V: Vertex> PointSlice<V> {
805    /// The tile this point lands in.
806    #[must_use]
807    #[inline]
808    pub fn tile(&self) -> TileId {
809        self.tile
810    }
811
812    /// The point in the tile's local frame: `position − tile · extent`, the payload unchanged.
813    #[must_use]
814    #[inline]
815    pub fn point(&self) -> V {
816        self.point
817    }
818}
819
820/// The tiles a point lands in, with the point in each; see [`Slicer::point`]. One to four items, columns
821/// first, computed with a single division per axis.
822#[derive(Debug, Clone)]
823pub struct PointSlices<V> {
824    point: V,
825    extent: i32,
826    x: AxisTiles,
827    y: AxisTiles,
828    /// The next item, `0..x.len * y.len`.
829    next: u8,
830}
831
832impl<V: Vertex> PointSlices<V> {
833    fn new(grid: Grid, point: V) -> Self {
834        let p = point.position();
835        let (extent, buffer) = (grid.extent_i32(), grid.buffer_i32());
836        Self {
837            point,
838            extent,
839            x: AxisTiles::new(p.x, extent, buffer),
840            y: AxisTiles::new(p.y, extent, buffer),
841            next: 0,
842        }
843    }
844}
845
846impl<V: Vertex> Iterator for PointSlices<V> {
847    type Item = PointSlice<V>;
848
849    fn next(&mut self) -> Option<Self::Item> {
850        if self.next >= self.x.len * self.y.len {
851            return None;
852        }
853        // Columns first: the column is `next / y.len` and the row `next % y.len`, with `y.len` 1 or 2.
854        let shift = self.y.len - 1;
855        let (i, j) = (self.next >> shift, self.next & shift);
856        self.next += 1;
857        let (dx, dy) = (self.x.first + i32::from(i), self.y.first + i32::from(j));
858        Some(PointSlice {
859            tile: TileId::new(self.x.tile + dx, self.y.tile + dy),
860            point: self.point.with_position(Coord {
861                x: self.x.rem - dx * self.extent,
862                y: self.y.rem - dy * self.extent,
863            }),
864        })
865    }
866
867    fn size_hint(&self) -> (usize, Option<usize>) {
868        let left = usize::from(self.x.len * self.y.len - self.next);
869        (left, Some(left))
870    }
871}
872
873impl<V: Vertex> ExactSizeIterator for PointSlices<V> {}
874
875impl<V: Vertex> FusedIterator for PointSlices<V> {}