use core::iter::FusedIterator;
use geo_types::Coord;
use crate::TileError;
use crate::clip_polyline::segment_intersects;
use crate::grid::{Grid, Located, SegmentTiles};
use crate::polygon::{
FillRun, PolygonPiece, PolygonSlices, Scratch, clip_polygon_to_tile, fill_ring,
};
use crate::tile::TileId;
use crate::vertex::{PolyVertex, Vertex};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Slicer<V: Vertex = Coord<i32>> {
grid: Grid,
poly: Scratch<V>,
}
impl<V: Vertex> Slicer<V> {
pub fn new(extent: u32, buffer: u16) -> Result<Self, TileError> {
Ok(Self {
grid: Grid::new(extent, buffer)?,
poly: Scratch::new(),
})
}
#[must_use]
pub fn extent(&self) -> u32 {
self.grid.extent()
}
#[must_use]
pub fn buffer(&self) -> u16 {
self.grid.buffer()
}
pub fn shrink_to_fit(&mut self) {
self.poly = Scratch::new();
}
pub fn line<'a>(&self, line: &'a [V]) -> Result<LineSlices<'a, V>, TileError> {
if let Some((lo, hi)) = bounds(line) {
self.grid.check_bounds(lo, hi)?;
}
Ok(LineSlices {
grid: self.grid,
pts: line,
cursor: None,
})
}
pub fn tile_line<'a>(
&self,
tile: TileId,
line: &'a [V],
) -> Result<TileLines<'a, V>, TileError> {
let (min, max) = self.grid.tile_buffered_bounds(tile)?;
let origin = self.grid.origin_of(min);
let mut next = 0;
match bounds(line) {
Some((lo, hi)) if hi.x >= min.x && lo.x <= max.x && hi.y >= min.y && lo.y <= max.y => {
let fits = |p: Coord<i32>| {
p.x.checked_sub(origin.x).is_some() && p.y.checked_sub(origin.y).is_some()
};
if !fits(lo) || !fits(hi) {
return Err(TileError::Overflow);
}
}
_ => next = line.len(),
}
Ok(TileLines {
min,
max,
origin,
tile,
pts: line,
next,
})
}
pub fn point(&self, point: V) -> Result<PointSlices<V>, TileError> {
let p = point.position();
self.grid.check_tiles(p, p)?;
Ok(PointSlices::new(self.grid, point))
}
pub fn tile_point(&self, tile: TileId, point: V) -> Result<Option<V>, TileError> {
let (extent, buffer) = (i64::from(self.grid.extent()), i64::from(self.grid.buffer()));
let p = point.position();
let axis = |tile: i32, v: i32| {
let base = i64::from(tile) * extent;
let (lo, hi) = (base - buffer, base + extent - 1 + buffer);
if lo < i64::from(i32::MIN) || hi > i64::from(i32::MAX) {
return Err(TileError::Overflow);
}
let v = i64::from(v);
#[expect(clippy::cast_possible_truncation, reason = "within the box, see above")]
Ok((lo..=hi).contains(&v).then_some((v - base) as i32))
};
let (x, y) = (axis(tile.x, p.x)?, axis(tile.y, p.y)?);
Ok(x.zip(y).map(|(x, y)| point.with_position(Coord { x, y })))
}
}
impl<V: PolyVertex> Slicer<V> {
#[inline]
pub fn polygon<'s, R: AsRef<[V]>>(
&'s mut self,
rings: &'s [R],
) -> Result<PolygonSlices<'s, V, R>, TileError> {
PolygonSlices::new(self.grid, rings, self.poly.get())
}
pub fn tile_polygon<'s, R: AsRef<[V]>>(
&'s mut self,
tile: TileId,
rings: &'s [R],
) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
u32::try_from(rings.len()).map_err(|_| TileError::GeometryTooLarge)?;
clip_polygon_to_tile(self.grid, tile, rings, &mut self.poly.get().scratch)
}
#[must_use]
pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
fill_ring(self.grid, run)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Local<'a, V> {
verts: &'a [V],
origin: Coord<i32>,
shape: Shape,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Shape {
Run,
Ring,
OpenRing,
}
impl<'a, V: Vertex> Local<'a, V> {
pub(crate) fn run(verts: &'a [V], origin: Coord<i32>) -> Self {
Self {
verts,
origin,
shape: Shape::Run,
}
}
pub(crate) fn ring(verts: &'a [V], origin: Coord<i32>, is_closed: bool) -> Self {
Self {
verts,
origin,
shape: if is_closed {
Shape::Ring
} else {
Shape::OpenRing
},
}
}
#[must_use]
#[inline]
pub fn iter(&self) -> LocalIter<'a, V> {
LocalIter {
verts: self.verts.iter(),
origin: self.origin,
dedupe: self.shape == Shape::Run,
last: None,
closing: if self.shape == Shape::OpenRing {
self.verts.first()
} else {
None
},
}
}
#[must_use]
pub fn to_vec(self) -> Vec<V> {
self.iter().collect()
}
#[must_use]
pub fn global(&self) -> &'a [V] {
self.verts
}
#[must_use]
pub fn origin(&self) -> Coord<i32> {
self.origin
}
}
impl<'a, V: Vertex> IntoIterator for &Local<'a, V> {
type Item = V;
type IntoIter = LocalIter<'a, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, V: Vertex> IntoIterator for Local<'a, V> {
type Item = V;
type IntoIter = LocalIter<'a, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone)]
pub struct LocalIter<'a, V> {
verts: core::slice::Iter<'a, V>,
origin: Coord<i32>,
dedupe: bool,
last: Option<Coord<i32>>,
closing: Option<&'a V>,
}
impl<V: Vertex> LocalIter<'_, V> {
#[inline]
fn local(&self, v: V) -> V {
let p = v.position();
v.with_position(Coord {
x: p.x.wrapping_sub(self.origin.x),
y: p.y.wrapping_sub(self.origin.y),
})
}
}
impl<V: Vertex> Iterator for LocalIter<'_, V> {
type Item = V;
#[inline]
fn next(&mut self) -> Option<V> {
for &v in self.verts.by_ref() {
if self.dedupe {
let p = v.position();
if self.last == Some(p) {
continue;
}
self.last = Some(p);
}
return Some(self.local(v));
}
let closing = *self.closing.take()?;
Some(self.local(closing))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let closing = usize::from(self.closing.is_some());
let left = self.verts.len();
let low = if self.dedupe {
usize::from(left > 0)
} else {
left
};
(low + closing, Some(left + closing))
}
}
impl<V: Vertex> FusedIterator for LocalIter<'_, V> {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LineSlice<'a, V> {
tile: TileId,
run: Local<'a, V>,
}
impl<'a, V: Vertex> LineSlice<'a, V> {
#[must_use]
pub fn tile(&self) -> TileId {
self.tile
}
#[must_use]
pub fn run(&self) -> Local<'a, V> {
self.run
}
}
fn next_distinct<V: Vertex>(pts: &[V], i: usize) -> Option<usize> {
let p = pts.get(i)?.position();
(i + 1..pts.len()).find(|&j| pts[j].position() != p)
}
fn bounds<V: Vertex>(pts: &[V]) -> Option<(Coord<i32>, Coord<i32>)> {
let (mut lo, mut hi) = (
Coord {
x: i32::MAX,
y: i32::MAX,
},
Coord {
x: i32::MIN,
y: i32::MIN,
},
);
for v in pts {
let p = v.position();
(lo.x, lo.y) = (lo.x.min(p.x), lo.y.min(p.y));
(hi.x, hi.y) = (hi.x.max(p.x), hi.y.max(p.y));
}
(lo.x <= hi.x).then_some((lo, hi))
}
fn extend_run<V: Vertex>(pts: &[V], mut end: usize, min: Coord<i32>, max: Coord<i32>) -> usize {
while let Some(n1) = next_distinct(pts, end) {
let (a, b) = (pts[end].position(), pts[n1].position());
if segment_intersects(a, b, min, max) {
end = n1;
continue;
}
let Some(n2) = next_distinct(pts, n1) else {
break;
};
if segment_intersects(b, pts[n2].position(), min, max) {
end = n2;
continue;
}
break;
}
end
}
type Segment = (Coord<i32>, Coord<i32>);
#[derive(Debug, Clone, Copy, Default)]
struct Prev([Option<Segment>; 2]);
impl Prev {
#[inline]
fn pushed(self, seg: Segment) -> Self {
Self([Some(seg), self.0[0]])
}
#[inline]
fn continues(self, min: Coord<i32>, max: Coord<i32>) -> bool {
self.0[0].is_some_and(|(a, b)| segment_intersects(a, b, min, max))
}
#[expect(
clippy::inline_always,
reason = "run per tile of a walk; left out of line it costs the line benchmarks 3%"
)]
#[inline(always)]
fn run_started(self, min: Coord<i32>, max: Coord<i32>) -> bool {
self.0
.iter()
.flatten()
.any(|&(a, b)| segment_intersects(a, b, min, max))
}
}
#[derive(Debug, Clone)]
enum SegTiles {
One(TileId),
Walk(SegmentTiles),
Done,
}
#[derive(Debug, Clone)]
struct LineCursor {
a: usize,
c: usize,
prev: Prev,
tiles: SegTiles,
loc_c: Located,
}
struct RunAt {
tile: TileId,
first: usize,
last: usize,
origin: Coord<i32>,
}
fn skip_inside<V: Vertex>(grid: Grid, pts: &[V], cur: &mut LineCursor) {
let loc = cur.loc_c;
let (min, max) = grid.box_of(&loc);
if !cur.prev.continues(min, max) {
return;
}
while let Some(n) = next_distinct(pts, cur.c) {
if !loc.contains_inner(pts[n].position()) {
break;
}
cur.prev = cur
.prev
.pushed((pts[cur.a].position(), pts[cur.c].position()));
(cur.a, cur.c) = (cur.c, n);
}
cur.tiles = SegTiles::Done;
}
#[derive(Debug, Clone)]
pub struct LineSlices<'a, V> {
grid: Grid,
pts: &'a [V],
cursor: Option<LineCursor>,
}
impl<V: Vertex> LineSlices<'_, V> {
fn route(&self, a: usize, c: usize, la: Located) -> (SegTiles, Located) {
let (pa, pc) = (self.pts[a].position(), self.pts[c].position());
if la.contains_inner(pa) && la.contains_inner(pc) {
return (SegTiles::One(la.owner), la);
}
let loc_c = if la.contains_core(pc) {
la
} else {
self.grid.locate_fit(pc)
};
(SegTiles::Walk(SegmentTiles::new(self.grid, pa, pc)), loc_c)
}
fn advance(&mut self) -> bool {
let pts = self.pts;
let next = match &self.cursor {
None => next_distinct(pts, 0).map(|c| (0, c, Prev::default(), None)),
Some(cur) => next_distinct(pts, cur.c).map(|c| {
let seg = (pts[cur.a].position(), pts[cur.c].position());
(cur.c, c, cur.prev.pushed(seg), Some(cur.loc_c))
}),
};
let Some((a, c, prev, known)) = next else {
return false;
};
let la = known.unwrap_or_else(|| self.grid.locate_fit(pts[a].position()));
let (tiles, loc_c) = self.route(a, c, la);
let mut cur = LineCursor {
a,
c,
prev,
tiles,
loc_c,
};
if matches!(cur.tiles, SegTiles::One(_)) {
skip_inside(self.grid, pts, &mut cur);
}
self.cursor = Some(cur);
true
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn next_run(&mut self) -> Option<RunAt> {
loop {
let Some(cur) = &mut self.cursor else {
if !self.advance() {
return None;
}
continue;
};
let (tile, fast) = match &mut cur.tiles {
SegTiles::One(tile) => {
let tile = *tile;
cur.tiles = SegTiles::Done;
(Some(tile), true)
}
SegTiles::Walk(walk) => (walk.next(), false),
SegTiles::Done => (None, false),
};
let Some(tile) = tile else {
if !self.advance() {
return None;
}
continue;
};
let (min, max) = if fast {
self.grid.box_of(&cur.loc_c)
} else {
self.grid.tile_box(tile)
};
if cur.prev.run_started(min, max) {
continue;
}
return Some(RunAt {
tile,
first: cur.a,
last: extend_run(self.pts, cur.c, min, max),
origin: self.grid.origin_of(min),
});
}
}
}
impl<'a, V: Vertex> Iterator for LineSlices<'a, V> {
type Item = LineSlice<'a, V>;
fn next(&mut self) -> Option<Self::Item> {
let at = self.next_run()?;
Some(LineSlice {
tile: at.tile,
run: Local::run(&self.pts[at.first..=at.last], at.origin),
})
}
}
impl<V: Vertex> FusedIterator for LineSlices<'_, V> {}
#[derive(Debug, Clone)]
pub struct TileLines<'a, V> {
tile: TileId,
min: Coord<i32>,
max: Coord<i32>,
origin: Coord<i32>,
pts: &'a [V],
next: usize,
}
impl<'a, V: Vertex> Iterator for TileLines<'a, V> {
type Item = LineSlice<'a, V>;
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn next(&mut self) -> Option<Self::Item> {
let (pts, min, max) = (self.pts, self.min, self.max);
let mut a = self.next;
while a < pts.len() {
let Some(c) = next_distinct(pts, a) else {
break;
};
if segment_intersects(pts[a].position(), pts[c].position(), min, max) {
let end = extend_run(pts, c, min, max);
self.next = end;
return Some(LineSlice {
tile: self.tile,
run: Local::run(&pts[a..=end], self.origin),
});
}
a = c;
}
self.next = pts.len();
None
}
}
impl<V: Vertex> FusedIterator for TileLines<'_, V> {}
#[derive(Debug, Clone, Copy)]
struct AxisTiles {
tile: i32,
rem: i32,
first: i32,
len: u8,
}
impl AxisTiles {
fn new(v: i32, extent: i32, buffer: i32) -> Self {
let (mut tile, mut rem) = (v / extent, v % extent);
if rem < 0 {
(tile, rem) = (tile - 1, rem + extent);
}
let (before, after) = (rem < buffer, rem >= extent - buffer);
Self {
tile,
rem,
first: -i32::from(before),
len: 1 + u8::from(before) + u8::from(after),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PointSlice<V> {
tile: TileId,
point: V,
}
impl<V: Vertex> PointSlice<V> {
#[must_use]
#[inline]
pub fn tile(&self) -> TileId {
self.tile
}
#[must_use]
#[inline]
pub fn point(&self) -> V {
self.point
}
}
#[derive(Debug, Clone)]
pub struct PointSlices<V> {
point: V,
extent: i32,
x: AxisTiles,
y: AxisTiles,
next: u8,
}
impl<V: Vertex> PointSlices<V> {
fn new(grid: Grid, point: V) -> Self {
let p = point.position();
let (extent, buffer) = (grid.extent_i32(), grid.buffer_i32());
Self {
point,
extent,
x: AxisTiles::new(p.x, extent, buffer),
y: AxisTiles::new(p.y, extent, buffer),
next: 0,
}
}
}
impl<V: Vertex> Iterator for PointSlices<V> {
type Item = PointSlice<V>;
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.x.len * self.y.len {
return None;
}
let shift = self.y.len - 1;
let (i, j) = (self.next >> shift, self.next & shift);
self.next += 1;
let (dx, dy) = (self.x.first + i32::from(i), self.y.first + i32::from(j));
Some(PointSlice {
tile: TileId::new(self.x.tile + dx, self.y.tile + dy),
point: self.point.with_position(Coord {
x: self.x.rem - dx * self.extent,
y: self.y.rem - dy * self.extent,
}),
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = usize::from(self.x.len * self.y.len - self.next);
(left, Some(left))
}
}
impl<V: Vertex> ExactSizeIterator for PointSlices<V> {}
impl<V: Vertex> FusedIterator for PointSlices<V> {}