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