use core::cmp::Ordering;
use core::ops::Range;
use geo_types::Coord;
use crate::TileError;
use crate::clip_polygon::{
ClipScratch, RingClip, clip_ring, close_ring, fill_box, keep_edge, push_distinct, push_fill_box,
};
use crate::clip_polyline::to_local;
use crate::geom::{crossing_x_ceil, ring_orientation};
use crate::grid::{Grid, Seg};
use crate::slicer::Local;
use crate::tile::TileId;
use crate::vertex::{PolyVertex, Vertex};
const CHUNK: usize = 32;
const LONG_EDGE: i64 = 4;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct FillRun {
pub y: i32,
pub x: Range<i32>,
orient: Ordering,
}
#[derive(Debug)]
pub enum PolygonSlice<'a, V, R> {
Piece(PolygonPiece<'a, V, R>),
Run(FillRun),
}
#[derive(Debug)]
pub struct PolygonPiece<'a, V, R> {
tile: TileId,
rings: PieceRings<'a, V, R>,
}
#[derive(Debug)]
enum PieceRings<'a, V, R> {
Built {
verts: &'a [V],
ends: &'a [u32],
origin: Coord<i32>,
},
Whole {
rings: &'a [RingInfo],
input: &'a [R],
copies: &'a [V],
origin: Coord<i32>,
},
}
impl<V, R> Clone for PieceRings<'_, V, R> {
fn clone(&self) -> Self {
*self
}
}
impl<V, R> Copy for PieceRings<'_, V, R> {}
impl<V, R> Clone for PolygonPiece<'_, V, R> {
fn clone(&self) -> Self {
*self
}
}
impl<V, R> Copy for PolygonPiece<'_, V, R> {}
impl<'a, V: Vertex, R: AsRef<[V]>> PolygonPiece<'a, V, R> {
#[must_use]
pub fn tile(&self) -> TileId {
self.tile
}
pub fn rings(&self) -> impl Iterator<Item = Local<'a, V>> + use<'a, V, R> {
let rings = self.rings;
let n = match rings {
PieceRings::Built { ends, .. } => ends.len(),
PieceRings::Whole { rings, .. } => rings.len(),
};
(0..n).map(move |i| match rings {
PieceRings::Built {
verts,
ends,
origin,
} => {
let start = i.checked_sub(1).map_or(0, |p| ends[p] as usize);
Local::ring(&verts[start..ends[i] as usize], origin, true)
}
PieceRings::Whole {
rings,
input,
copies,
origin,
} => Local::ring(rings[i].pts(input, copies), origin, false),
})
}
}
#[derive(Debug, Clone, Copy)]
enum Src {
Input,
Copy(usize),
}
#[derive(Debug, Clone, Copy)]
struct RingInfo {
input: u32,
src: Src,
len: u32,
edge0: u32,
}
impl RingInfo {
fn pts<'a, V, R: AsRef<[V]>>(&self, input: &'a [R], copies: &'a [V]) -> &'a [V] {
let len = self.len as usize;
match self.src {
Src::Input => &input[self.input as usize].as_ref()[..len],
Src::Copy(start) => &copies[start..start + len],
}
}
}
#[derive(Debug, Clone, Copy)]
struct Chunk {
ring: u32,
first: u32,
count: u32,
rows: (i64, i64),
}
#[derive(Debug, Clone, Copy)]
struct Hit {
tx: i32,
edge: u32,
ring: u32,
}
#[derive(Debug, Clone, Copy)]
struct Span {
first: i32,
last: i32,
edge: u32,
ring: u32,
}
#[derive(Debug, Clone, Copy)]
struct Crossing {
edge: u32,
ring: u32,
up: bool,
right_of: i64,
}
impl Crossing {
fn sign(self) -> i64 {
if self.up { 1 } else { -1 }
}
}
fn row_center(ty: i64, extent: i64) -> i64 {
ty * extent + if ty < 0 { extent / 2 } else { (extent - 1) / 2 }
}
fn fenwick_build(tree: &mut Vec<i64>, values: impl Iterator<Item = i64>) {
tree.clear();
tree.extend(values);
for i in 0..tree.len() {
let parent = i | (i + 1);
if parent < tree.len() {
tree[parent] += tree[i];
}
}
}
fn fenwick_add(tree: &mut [i64], mut i: usize, delta: i64) {
while i < tree.len() {
tree[i] += delta;
i |= i + 1;
}
}
fn fenwick_prefix(tree: &[i64], mut end: usize) -> i64 {
let mut sum = 0;
while end > 0 {
sum += tree[end - 1];
end &= end - 1;
}
sum
}
fn sort_hits(hits: &mut [Hit], tmp: &mut Vec<Hit>, counts: &mut Vec<usize>) {
let Some(head) = hits.first() else {
return;
};
let (lo, hi) = hits.iter().fold((head.tx, head.tx), |(lo, hi), h| {
(lo.min(h.tx), hi.max(h.tx))
});
let columns = hi.abs_diff(lo) as usize + 1;
if columns == 1 {
} else if hits.len() < 16 || columns > hits.len().saturating_mul(4) {
hits.sort_unstable_by_key(|h| (h.tx, h.edge));
} else {
counts.clear();
counts.resize(columns, 0);
for h in hits.iter() {
counts[h.tx.abs_diff(lo) as usize] += 1;
}
let mut start = 0;
for c in counts.iter_mut() {
(*c, start) = (start, start + *c);
}
tmp.clear();
tmp.extend_from_slice(hits);
for h in tmp.iter() {
let at = &mut counts[h.tx.abs_diff(lo) as usize];
hits[*at] = *h;
*at += 1;
}
}
debug_assert!(hits.is_sorted_by_key(|h| (h.tx, h.edge)));
}
fn check_local<V: Vertex>(verts: &[V], origin: Coord<i32>) -> Result<(), TileError> {
for &v in verts {
to_local(v, origin)?;
}
Ok(())
}
fn merge_by_edge<T: Copy>(
spans: &mut Vec<T>,
kept: usize,
tmp: &mut Vec<T>,
edge: impl Fn(&T) -> u32,
) {
let added = &mut spans[kept..];
if !added.is_sorted_by_key(&edge) {
added.sort_unstable_by_key(&edge);
}
if kept == 0 || kept == spans.len() || edge(&spans[kept - 1]) < edge(&spans[kept]) {
return;
}
tmp.clear();
let (old, new) = spans.split_at(kept);
let (mut i, mut j) = (0, 0);
while i < old.len() && j < new.len() {
if edge(&old[i]) < edge(&new[j]) {
tmp.push(old[i]);
i += 1;
} else {
tmp.push(new[j]);
j += 1;
}
}
tmp.extend_from_slice(&old[i..]);
tmp.extend_from_slice(&new[j..]);
core::mem::swap(spans, tmp);
}
fn ring_groups(mut hits: &[Hit]) -> impl Iterator<Item = &[Hit]> {
core::iter::from_fn(move || {
let ring = hits.first()?.ring;
let (group, rest) = hits.split_at(hits.partition_point(|x| x.ring == ring));
hits = rest;
Some(group)
})
}
fn excursion_winding(
cs: &[Crossing],
fenwick: &[i64],
ring: RingInfo,
first: usize,
count: usize,
) -> i64 {
let (start, len) = (ring.edge0 as usize, ring.len as usize);
let sum = |lo: usize, hi: usize| {
let i = cs.partition_point(|c| (c.edge as usize) < lo);
let j = cs.partition_point(|c| (c.edge as usize) < hi);
fenwick_prefix(fenwick, j) - fenwick_prefix(fenwick, i)
};
if first + count <= len {
sum(start + first, start + first + count)
} else {
sum(start + first, start + len) + sum(start, start + first + count - len)
}
}
pub(crate) struct PolyScratch<V> {
rings: Vec<RingInfo>,
copies: Vec<V>,
chunks: Vec<Chunk>,
order: Vec<u32>,
active: Vec<u32>,
hits: Vec<Hit>,
long: Vec<Span>,
long_cover: Vec<Span>,
covering: Vec<Hit>,
crossings: Vec<Crossing>,
by_x: Vec<u32>,
hits_tmp: Vec<Hit>,
spans_tmp: Vec<Span>,
counts: Vec<usize>,
fenwick: Vec<i64>,
inside: Vec<bool>,
verts: Vec<V>,
ring_ends: Vec<u32>,
arcs: Vec<(usize, usize)>,
clip: ClipScratch<V>,
}
impl<V> PolyScratch<V> {
pub(crate) const fn new() -> Self {
Self {
rings: Vec::new(),
copies: Vec::new(),
chunks: Vec::new(),
order: Vec::new(),
active: Vec::new(),
hits: Vec::new(),
long: Vec::new(),
long_cover: Vec::new(),
covering: Vec::new(),
crossings: Vec::new(),
by_x: Vec::new(),
hits_tmp: Vec::new(),
spans_tmp: Vec::new(),
counts: Vec::new(),
fenwick: Vec::new(),
inside: Vec::new(),
verts: Vec::new(),
ring_ends: Vec::new(),
arcs: Vec::new(),
clip: ClipScratch::new(),
}
}
}
pub(crate) struct Scratch<V>(Option<Box<PolyState<V>>>);
pub(crate) struct PolyState<V> {
pub(crate) scratch: PolyScratch<V>,
sweep: Option<Sweep>,
}
impl<V> Scratch<V> {
pub(crate) const fn new() -> Self {
Self(None)
}
#[inline]
pub(crate) fn get(&mut self) -> &mut PolyState<V> {
#[cold]
fn alloc<V>() -> Box<PolyState<V>> {
Box::new(PolyState {
scratch: PolyScratch::new(),
sweep: None,
})
}
self.0.get_or_insert_with(alloc)
}
}
impl<V> Clone for Scratch<V> {
fn clone(&self) -> Self {
Self::new()
}
}
impl<V> PartialEq for Scratch<V> {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<V> Eq for Scratch<V> {}
impl<V> core::fmt::Debug for Scratch<V> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PolyScratch").finish_non_exhaustive()
}
}
pub(crate) fn fill_ring<V: PolyVertex>(grid: Grid, run: &FillRun) -> [V; 5] {
let (extent, buffer) = (grid.extent_i32(), grid.buffer_i32());
let sw = Coord {
x: -buffer - 1,
y: -buffer - 1,
};
let ne = Coord {
x: extent + buffer,
y: extent + buffer,
};
fill_box(run.orient, sw, ne).map(V::synthetic_at)
}
enum Event {
Whole {
tile: TileId,
origin: Coord<i32>,
},
Built {
tile: TileId,
origin: Coord<i32>,
},
Run(FillRun),
}
struct Sweep {
row: i64,
last_row: i64,
next_chunk: usize,
in_row: bool,
ty: i32,
k: usize,
h: usize,
lh: usize,
next: i64,
cover: Option<Range<usize>>,
long_last: i64,
holes_in: u32,
pending: Option<(i64, i64)>,
ready: Option<FillRun>,
tile_ready: Option<i32>,
orient: Option<Ordering>,
}
pub struct PolygonSlices<'s, V, R> {
grid: Grid,
rings: &'s [R],
s: &'s mut PolyScratch<V>,
start: Option<Start>,
sweep: &'s mut Option<Sweep>,
done: bool,
}
enum Start {
Whole { tile: TileId, origin: Coord<i32> },
Sweep { row: i64, last_row: i64 },
}
impl<'s, V: PolyVertex, R: AsRef<[V]>> PolygonSlices<'s, V, R> {
#[cfg_attr(feature = "hotpath", hotpath::measure)]
#[inline]
pub(crate) fn new(
grid: Grid,
rings: &'s [R],
state: &'s mut PolyState<V>,
) -> Result<Self, TileError> {
let PolyState { scratch: s, sweep } = state;
*sweep = None;
let start = prepare(grid, rings, s)?;
Ok(Self {
grid,
rings,
s,
start,
sweep,
done: false,
})
}
#[expect(
clippy::should_implement_trait,
reason = "a lending iterator: each item borrows the iterator"
)]
pub fn next(&mut self) -> Option<Result<PolygonSlice<'_, V, R>, TileError>> {
if self.done {
return None;
}
match self.step() {
Ok(Some(event)) => Some(Ok(self.view(event))),
Ok(None) => {
self.done = true;
None
}
Err(e) => {
self.done = true;
Some(Err(e))
}
}
}
#[must_use]
pub fn fill_ring(&self, run: &FillRun) -> [V; 5] {
fill_ring(self.grid, run)
}
fn view(&self, event: Event) -> PolygonSlice<'_, V, R> {
let (tile, rings) = match event {
Event::Run(run) => return PolygonSlice::Run(run),
Event::Built { tile, origin } => (
tile,
PieceRings::Built {
verts: &self.s.verts,
ends: &self.s.ring_ends,
origin,
},
),
Event::Whole { tile, origin } => (
tile,
PieceRings::Whole {
rings: &self.s.rings,
input: self.rings,
copies: &self.s.copies,
origin,
},
),
};
PolygonSlice::Piece(PolygonPiece { tile, rings })
}
fn step(&mut self) -> Result<Option<Event>, TileError> {
match self.start.take() {
Some(Start::Whole { tile, origin }) => return Ok(Some(Event::Whole { tile, origin })),
Some(Start::Sweep { row, last_row }) => *self.sweep = Some(Sweep::new(row, last_row)),
None => {}
}
let Some(sw) = self.sweep.as_mut() else {
return Ok(None);
};
let mut cx = Cx {
grid: self.grid,
s: &mut *self.s,
rings: self.rings,
};
sw.step(&mut cx)
}
}
fn prepare<V: PolyVertex, R: AsRef<[V]>>(
grid: Grid,
rings: &[R],
s: &mut PolyScratch<V>,
) -> Result<Option<Start>, TileError> {
let (extent, buffer) = (i64::from(grid.extent()), i64::from(grid.buffer()));
s.rings.clear();
s.copies.clear();
u32::try_from(rings.len()).map_err(|_| TileError::GeometryTooLarge)?;
let mut bounds: Option<(Coord<i32>, Coord<i32>)> = None;
let mut edges = 0_u32;
for (index, ring) in (0_u32..).zip(rings) {
let Some((info, lo, hi)) = load_ring(s, index, ring.as_ref(), edges)? else {
if index == 0 {
return Ok(None); }
continue;
};
edges = edges
.checked_add(info.len)
.ok_or(TileError::GeometryTooLarge)?;
s.rings.push(info);
bounds = Some(bounds.map_or((lo, hi), |(l, h)| {
(
Coord {
x: l.x.min(lo.x),
y: l.y.min(lo.y),
},
Coord {
x: h.x.max(hi.x),
y: h.y.max(hi.y),
},
)
}));
}
let Some((lo, hi)) = bounds else {
return Ok(None);
};
if let Some((tile, origin)) = grid.inner_tile(lo, hi) {
return Ok(Some(Start::Whole { tile, origin }));
}
grid.check_polygon_bounds(lo, hi)?;
s.chunks.clear();
index_chunks(s, rings, extent, buffer);
s.order.clear();
#[expect(
clippy::cast_possible_truncation,
reason = "at most one chunk per edge"
)]
s.order.extend(0..s.chunks.len() as u32);
let chunks = &s.chunks;
s.order.sort_unstable_by_key(|&c| chunks[c as usize].rows.0);
s.active.clear();
s.inside.clear();
s.inside.resize(s.rings.len(), false);
Ok(Some(Start::Sweep {
row: (i64::from(lo.y) - buffer).div_euclid(extent),
last_row: (i64::from(hi.y) + buffer).div_euclid(extent),
}))
}
struct Cx<'x, V, R> {
grid: Grid,
s: &'x mut PolyScratch<V>,
rings: &'x [R],
}
impl Sweep {
fn new(row: i64, last_row: i64) -> Self {
Self {
row,
last_row,
next_chunk: 0,
in_row: false,
ty: 0,
k: 0,
h: 0,
next: i64::MIN,
holes_in: 0,
pending: None,
ready: None,
tile_ready: None,
orient: None,
lh: 0,
cover: Some(0..0),
long_last: i64::MIN,
}
}
fn step<V: PolyVertex, R: AsRef<[V]>>(
&mut self,
cx: &mut Cx<'_, V, R>,
) -> Result<Option<Event>, TileError> {
loop {
if let Some(run) = self.ready.take() {
return Ok(Some(Event::Run(run)));
}
if let Some(tx) = self.tile_ready.take() {
let tile = TileId::new(tx, self.ty);
if let Some(origin) = self.build_tile(cx, tile)? {
return Ok(Some(Event::Built { tile, origin }));
}
continue;
}
if !self.in_row {
if self.row > self.last_row {
return Ok(None);
}
self.load_row(cx)?;
self.ty = i32::try_from(self.row).map_err(|_| TileError::Overflow)?;
(self.in_row, self.k, self.h, self.lh, self.next) = (true, 0, 0, 0, i64::MIN);
(self.cover, self.long_last) = (Some(0..0), i64::MIN);
continue;
}
let s = &*cx.s;
let bound = s
.by_x
.get(self.k)
.map_or(i64::MAX, |&i| s.crossings[i as usize].right_of);
let tile = if s.long.is_empty() {
s.hits.get(self.h).map(|c| i64::from(c.tx))
} else {
let next = self.next;
let covered =
(self.long_last != i64::MIN && self.long_last >= next).then_some(next);
let short = s.hits.get(self.h).map(|c| i64::from(c.tx));
let long = s.long.get(self.lh).map(|c| i64::from(c.first).max(next));
[covered, short, long].into_iter().flatten().min()
};
if let Some(tx) = tile
&& tx <= bound
{
let tx = i32::try_from(tx).map_err(|_| TileError::Overflow)?;
let s = &mut *cx.s;
let start = self.h;
self.h += s.hits[start..].partition_point(|c| c.tx == tx);
self.cover = Some(start..self.h);
if !s.long.is_empty() {
self.cover_long(s, tx, start);
}
self.fill(cx, self.next, i64::from(tx))?;
self.ready = self.take_pending();
self.tile_ready = Some(tx);
self.next = i64::from(tx) + 1;
continue;
}
if self.k == s.by_x.len() {
debug_assert!(cx.s.inside.iter().all(|&i| !i), "parity ends even");
self.ready = self.take_pending();
(self.in_row, self.row) = (false, self.row + 1);
continue;
}
self.fill(cx, self.next, bound + 1)?;
self.next = bound + 1;
let s = &mut *cx.s;
while let Some(&i) = s.by_x.get(self.k)
&& s.crossings[i as usize].right_of == bound
{
let crossing = s.crossings[i as usize];
let r = crossing.ring as usize;
s.inside[r] = !s.inside[r];
if r != 0 {
if s.inside[r] {
self.holes_in += 1;
} else {
self.holes_in = self.holes_in.saturating_sub(1);
}
}
fenwick_add(&mut s.fenwick, i as usize, -crossing.sign());
self.k += 1;
}
if !(s.inside[0] && self.holes_in == 0) {
self.ready = self.take_pending();
}
}
}
fn cover_long<V>(&mut self, s: &mut PolyScratch<V>, tx: i32, start: usize) {
if self.long_last < i64::from(tx) {
s.long_cover.clear();
} else {
s.long_cover.retain(|c| c.last >= tx);
}
let kept = s.long_cover.len();
while let Some(&span) = s.long.get(self.lh)
&& span.first <= tx
{
s.long_cover.push(span);
self.lh += 1;
}
if s.long_cover.is_empty() {
self.long_last = i64::MIN;
return;
}
merge_by_edge(&mut s.long_cover, kept, &mut s.spans_tmp, |c| c.edge);
self.long_last = s
.long_cover
.iter()
.map(|c| i64::from(c.last))
.max()
.unwrap_or(i64::MIN);
s.covering.clear();
s.covering.extend(s.long_cover.iter().map(|c| Hit {
tx,
edge: c.edge,
ring: c.ring,
}));
let kept = s.covering.len();
s.covering.extend_from_slice(&s.hits[start..self.h]);
merge_by_edge(&mut s.covering, kept, &mut s.hits_tmp, |c| c.edge);
self.cover = None;
}
fn take_pending(&mut self) -> Option<FillRun> {
let (start, end) = self.pending.take()?;
#[expect(
clippy::cast_possible_truncation,
reason = "both ends were checked to fit i32 when the span was recorded"
)]
Some(FillRun {
y: self.ty,
x: start as i32..end as i32,
orient: self.orient.unwrap_or(Ordering::Equal),
})
}
fn fill<V: PolyVertex, R: AsRef<[V]>>(
&mut self,
cx: &mut Cx<'_, V, R>,
start: i64,
end: i64,
) -> Result<(), TileError> {
let covered = cx.s.inside.first().copied().unwrap_or(false) && self.holes_in == 0;
if start >= end || !covered {
return Ok(());
}
i32::try_from(start).map_err(|_| TileError::Overflow)?;
i32::try_from(end).map_err(|_| TileError::Overflow)?;
if self.orient.is_none() {
let ext = cx.s.rings[0].pts(cx.rings, &cx.s.copies);
self.orient = Some(ring_orientation(ext));
}
match &mut self.pending {
Some((_, e)) if *e == start => *e = end,
pending => {
debug_assert!(
pending.is_none(),
"an edge tile or row end yields a span first"
);
*pending = Some((start, end));
}
}
Ok(())
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
#[cfg_attr(
not(feature = "hotpath"),
expect(
clippy::many_single_char_names,
reason = "edge endpoints and the scratch"
)
)]
fn load_row<V: PolyVertex, R: AsRef<[V]>>(
&mut self,
cx: &mut Cx<'_, V, R>,
) -> Result<(), TileError> {
let ty = self.row;
let (extent, buffer) = (i64::from(cx.grid.extent()), i64::from(cx.grid.buffer()));
let s = &mut *cx.s;
let rings = cx.rings;
while let Some(&c) = s.order.get(self.next_chunk)
&& s.chunks[c as usize].rows.0 <= ty
{
let at = s.active.partition_point(|&a| a < c);
s.active.insert(at, c);
self.next_chunk += 1;
}
let chunks = &s.chunks;
s.active.retain(|&c| chunks[c as usize].rows.1 >= ty);
s.hits.clear();
s.long.clear();
s.long_cover.clear();
s.covering.clear();
s.crossings.clear();
let (slab_lo, slab_hi) = (ty * extent - buffer, ty * extent + extent - 1 + buffer);
let cy = row_center(ty, extent);
for &c in &s.active {
let chunk = s.chunks[c as usize];
let info = s.rings[chunk.ring as usize];
let pts = info.pts(rings, &s.copies);
let m = pts.len();
for i in chunk.first as usize..(chunk.first + chunk.count) as usize {
let (a, b) = (pts[i].position(), pts[(i + 1) % m].position());
let (lo, hi) = if a.y <= b.y { (a, b) } else { (b, a) };
if i64::from(hi.y) < slab_lo || i64::from(lo.y) > slab_hi {
continue;
}
#[expect(clippy::cast_possible_truncation, reason = "edge ids fit u32")]
let edge = info.edge0 + i as u32;
let wide = |p: Coord<i32>| Coord {
x: i64::from(p.x),
y: i64::from(p.y),
};
let (first, last) = Seg::new(wide(lo), wide(hi)).row_span(ty, extent, buffer);
let fits = |v: i64| i32::try_from(v).map_err(|_| TileError::Overflow);
if last - first + 1 >= LONG_EDGE {
s.long.push(Span {
first: fits(first)?,
last: fits(last)?,
edge,
ring: chunk.ring,
});
} else {
for tx in first..=last {
s.hits.push(Hit {
tx: fits(tx)?,
edge,
ring: chunk.ring,
});
}
}
if i64::from(lo.y) <= cy && cy < i64::from(hi.y) {
let x =
crossing_x_ceil(a, b, i32::try_from(cy).map_err(|_| TileError::Overflow)?);
s.crossings.push(Crossing {
edge,
ring: chunk.ring,
up: b.y > a.y,
right_of: (x - extent - buffer).div_euclid(extent),
});
}
}
}
sort_hits(&mut s.hits, &mut s.hits_tmp, &mut s.counts);
s.long.sort_unstable_by_key(|c| (c.first, c.edge));
debug_assert!(s.crossings.is_sorted_by_key(|c| c.edge));
s.by_x.clear();
#[expect(clippy::cast_possible_truncation, reason = "one crossing per edge")]
s.by_x.extend(0..s.crossings.len() as u32);
let crossings = &s.crossings;
s.by_x
.sort_unstable_by_key(|&i| crossings[i as usize].right_of);
fenwick_build(&mut s.fenwick, s.crossings.iter().map(|c| c.sign()));
Ok(())
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn build_tile<V: PolyVertex, R: AsRef<[V]>>(
&mut self,
cx: &mut Cx<'_, V, R>,
tile: TileId,
) -> Result<Option<Coord<i32>>, TileError> {
let (min, max) = cx.grid.tile_buffered_bounds(tile)?;
let origin = cx.grid.origin_of(min);
let s = &mut *cx.s;
let rings = cx.rings;
let hits = match &self.cover {
Some(range) => &s.hits[range.clone()],
None => &s.covering[..],
};
s.verts.clear();
s.ring_ends.clear();
let exterior_touched = hits.first().is_some_and(|x| x.ring == 0);
if !exterior_touched && !s.inside[0] {
return Ok(None); }
let holes_in = self.holes_in as usize;
if holes_in > 0
&& holes_in
> ring_groups(hits)
.filter(|g| g[0].ring != 0 && s.inside[g[0].ring as usize])
.count()
{
return Ok(None);
}
if !exterior_touched {
let orient = *self
.orient
.get_or_insert_with(|| ring_orientation(s.rings[0].pts(rings, &s.copies)));
push_fill_box(&mut s.verts, orient, min, max)?;
s.ring_ends.push(offset(s.verts.len())?);
}
for ring_hits in ring_groups(hits) {
let info = s.rings[ring_hits[0].ring as usize];
s.arcs.clear();
for x in ring_hits {
keep_edge(&mut s.arcs, (x.edge - info.edge0) as usize);
}
let (cs, fenwick) = (&s.crossings, &s.fenwick);
let winding =
|first: usize, count: usize| excursion_winding(cs, fenwick, info, first, count);
close_ring(
info.pts(rings, &s.copies),
&mut s.arcs,
min,
max,
winding,
&mut s.verts,
)?;
s.ring_ends.push(offset(s.verts.len())?);
}
Ok(Some(origin))
}
}
type LoadedRing = (RingInfo, Coord<i32>, Coord<i32>);
fn load_ring<V: Vertex>(
s: &mut PolyScratch<V>,
index: u32,
ring: &[V],
edge0: u32,
) -> Result<Option<LoadedRing>, TileError> {
let Some(first) = ring.first().map(Vertex::position) else {
return Ok(None);
};
let mut n = ring.len();
while n >= 2 && ring[n - 1].position() == first {
n -= 1;
}
let (mut lo, mut hi, mut prev, mut distinct) = (first, first, first, true);
for v in &ring[1..n] {
let p = v.position();
(lo.x, lo.y, hi.x, hi.y) = (lo.x.min(p.x), lo.y.min(p.y), hi.x.max(p.x), hi.y.max(p.y));
distinct &= p != prev;
prev = p;
}
let (src, len) = if distinct {
(Src::Input, n)
} else {
let start = s.copies.len();
(Src::Copy(start), push_distinct(ring, &mut s.copies))
};
if len < 3 {
if let Src::Copy(start) = src {
s.copies.truncate(start);
}
return Ok(None);
}
let info = RingInfo {
input: index,
src,
len: u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)?,
edge0,
};
Ok(Some((info, lo, hi)))
}
fn index_chunks<V: Vertex, R: AsRef<[V]>>(
s: &mut PolyScratch<V>,
rings: &[R],
extent: i64,
buffer: i64,
) {
for (ring_id, info) in (0_u32..).zip(&s.rings) {
let pts = info.pts(rings, &s.copies);
for first in (0..pts.len()).step_by(CHUNK) {
let count = CHUNK.min(pts.len() - first);
let (mut y0, mut y1) = (i32::MAX, i32::MIN);
for v in pts[first..first + count]
.iter()
.chain(core::iter::once(&pts[(first + count) % pts.len()]))
{
let y = v.position().y;
(y0, y1) = (y0.min(y), y1.max(y));
}
#[expect(
clippy::cast_possible_truncation,
reason = "within the ring's u32 length"
)]
s.chunks.push(Chunk {
ring: ring_id,
first: first as u32,
count: count as u32,
rows: (
(i64::from(y0) - buffer).div_euclid(extent),
(i64::from(y1) + buffer).div_euclid(extent),
),
});
}
}
}
fn offset(len: usize) -> Result<u32, TileError> {
u32::try_from(len).map_err(|_| TileError::GeometryTooLarge)
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub(crate) fn clip_polygon_to_tile<'s, V: PolyVertex, R: AsRef<[V]>>(
grid: Grid,
tile: TileId,
rings: &[R],
s: &'s mut PolyScratch<V>,
) -> Result<Option<PolygonPiece<'s, V, R>>, TileError> {
let (min, max) = grid.tile_buffered_bounds(tile)?;
let origin = grid.origin_of(min);
s.verts.clear();
s.ring_ends.clear();
let Some((exterior, holes)) = rings.split_first() else {
return Ok(None);
};
if clip_ring(exterior.as_ref(), min, max, &mut s.clip, &mut s.verts)? == RingClip::Outside {
return Ok(None);
}
check_local(&s.verts, origin)?;
s.ring_ends.push(offset(s.verts.len())?);
for hole in holes {
let start = s.verts.len();
match clip_ring(hole.as_ref(), min, max, &mut s.clip, &mut s.verts)? {
RingClip::Covers => return Ok(None),
RingClip::Clipped => {
check_local(&s.verts[start..], origin)?;
s.ring_ends.push(offset(s.verts.len())?);
}
RingClip::Outside => {}
}
}
Ok(Some(PolygonPiece {
tile,
rings: PieceRings::Built {
verts: &s.verts,
ends: &s.ring_ends,
origin,
},
}))
}