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