use core::cmp::Ordering;
use core::iter;
use geo_types::Coord;
use crate::TileError;
use crate::clip_polyline::{line_meets_box, segment_intersects};
use crate::geom::{orient, point_in_distinct_ring, ring_orientation};
use crate::vertex::{PolyVertex, Vertex};
const LEFT: u8 = 1;
const RIGHT: u8 = 2;
const BOTTOM: u8 = 4;
const TOP: u8 = 8;
const BOX: u8 = LEFT | RIGHT | BOTTOM | TOP;
const ABOVE: u8 = 16;
fn outcode(p: Coord<i32>, min: Coord<i32>, max: Coord<i32>) -> u8 {
let mut oc = 0;
if p.x < min.x {
oc |= LEFT;
} else if p.x > max.x {
oc |= RIGHT;
}
if p.y < min.y {
oc |= BOTTOM;
} else if p.y > max.y {
oc |= TOP;
}
oc
}
fn slot(oc: u8) -> u8 {
match oc {
_ if oc == BOTTOM | LEFT => 0,
BOTTOM => 1,
_ if oc == BOTTOM | RIGHT => 2,
RIGHT => 3,
_ if oc == RIGHT | TOP => 4,
TOP => 5,
_ if oc == TOP | LEFT => 6,
LEFT => 7,
_ => 0,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct RingPlan<'a> {
pub(crate) deepen: usize,
pub(crate) lane_base: i32,
pub(crate) lane_step: i32,
pub(crate) frame: Option<&'a [Coord<i32>]>,
pub(crate) depths: Option<&'a [(u32, u32)]>,
pub(crate) bridge_base: usize,
}
impl RingPlan<'_> {
pub(crate) const PLAIN: RingPlan<'static> = RingPlan {
deepen: 0,
lane_base: 0,
lane_step: 0,
frame: None,
depths: None,
bridge_base: 0,
};
fn depth(&self, bridge: usize, gap: usize) -> (usize, usize) {
let (head, tail) = match self.depths {
Some(d) => d
.get(bridge)
.map_or((0, 0), |&(h, t)| (h as usize, t as usize)),
None => (self.deepen, self.deepen),
};
let head = head.min(gap);
(head, tail.min(gap - head))
}
}
pub(crate) fn lane_bounds(
min: Coord<i32>,
max: Coord<i32>,
lane: i32,
) -> Result<(Coord<i32>, Coord<i32>), TileError> {
let g = |v: i32, d: i32| v.checked_add(d).ok_or(TileError::Overflow);
Ok((
Coord {
x: g(min.x, -lane)?,
y: g(min.y, -lane)?,
},
Coord {
x: g(max.x, lane)?,
y: g(max.y, lane)?,
},
))
}
#[inline(always)]
#[expect(
clippy::inline_always,
reason = "the plain clip's first pass; out of line it costs more than it does"
)]
fn all_inside<V: Vertex>(pts: &[V], min: Coord<i32>, max: Coord<i32>) -> bool {
let span = |lo: i32, hi: i32| hi.wrapping_sub(lo).cast_unsigned();
let (sx, sy) = (span(min.x, max.x), span(min.y, max.y));
pts.iter().all(|p| {
let p = p.position();
(span(min.x, p.x) <= sx) & (span(min.y, p.y) <= sy)
})
}
fn corner(slot: u8, min: Coord<i32>, max: Coord<i32>) -> Result<Coord<i32>, TileError> {
let lo_x = min.x.checked_sub(1).ok_or(TileError::Overflow)?;
let lo_y = min.y.checked_sub(1).ok_or(TileError::Overflow)?;
let hi_x = max.x.checked_add(1).ok_or(TileError::Overflow)?;
let hi_y = max.y.checked_add(1).ok_or(TileError::Overflow)?;
Ok(match slot {
0 => Coord { x: lo_x, y: lo_y }, 2 => Coord { x: hi_x, y: lo_y }, 4 => Coord { x: hi_x, y: hi_y }, _ => Coord { x: lo_x, y: hi_y }, })
}
fn ray_crossing(a: Coord<i32>, b: Coord<i32>, cy: i32, max_x: i32) -> i64 {
if (a.y > cy) == (b.y > cy) {
return 0;
}
let dy = i128::from(b.y) - i128::from(a.y);
let num = (i128::from(a.x) - i128::from(max_x)) * dy
+ (i128::from(b.x) - i128::from(a.x)) * (i128::from(cy) - i128::from(a.y));
if num.signum() * dy.signum() > 0 {
if b.y > a.y { 1 } else { -1 }
} else {
0
}
}
fn ray_crossings(path: impl IntoIterator<Item = Coord<i32>>, cy: i32, max_x: i32) -> i64 {
let mut path = path.into_iter();
let Some(mut prev) = path.next() else {
return 0;
};
let mut w = 0;
for p in path {
w += ray_crossing(prev, p, cy, max_x);
prev = p;
}
w
}
fn corner_slots(start: u8, advance: i64) -> impl Iterator<Item = u8> {
let step = if advance > 0 { 1 } else { 7 };
let mut s = start;
(1..advance.unsigned_abs()).filter_map(move |_| {
s = (s + step) % 8;
s.is_multiple_of(2).then_some(s)
})
}
fn corner_count(start: u8, advance: i64) -> u64 {
let n = advance.unsigned_abs().saturating_sub(1);
if start.is_multiple_of(2) {
n / 2
} else {
n.div_ceil(2)
}
}
pub(crate) fn center_y(min: Coord<i32>, max: Coord<i32>) -> i32 {
i32::midpoint(min.y, max.y)
}
enum Bridge {
Keep,
Detour { start: u8, advance: i64 },
}
fn plan_bridge(
u: Coord<i32>,
v: Coord<i32>,
w_exc: i64,
gap_len: usize,
min: Coord<i32>,
max: Coord<i32>,
cy: i32,
) -> Result<Bridge, TileError> {
plan_bridge_in(u, v, w_exc, gap_len, (min, max), (min, max), cy)
}
fn plan_bridge_lane(
u: Coord<i32>,
v: Coord<i32>,
w_exc: i64,
gap_len: usize,
bounds: (Coord<i32>, Coord<i32>),
lane: (Coord<i32>, Coord<i32>),
cy: i32,
) -> Result<Bridge, TileError> {
plan_bridge_in(u, v, w_exc, gap_len, bounds, lane, cy)
}
#[inline(always)]
#[expect(
clippy::inline_always,
reason = "each caller is one instance; the lane's arguments cost the plain clip ~1%"
)]
fn plan_bridge_in(
u: Coord<i32>,
v: Coord<i32>,
w_exc: i64,
gap_len: usize,
(min, max): (Coord<i32>, Coord<i32>),
(cmin, cmax): (Coord<i32>, Coord<i32>),
cy: i32,
) -> Result<Bridge, TileError> {
let su = slot(outcode(u, min, max));
let sv = slot(outcode(v, min, max));
let base = i64::from((sv + 8 - su) % 8); let mut w_ref = 0;
let mut prev = u;
for s in corner_slots(su, base) {
let c = corner(s, cmin, cmax)?;
w_ref += ray_crossing(prev, c, cy, max.x);
prev = c;
}
w_ref += ray_crossing(prev, v, cy, max.x);
let advance = (w_exc - w_ref)
.checked_mul(8)
.and_then(|loops| loops.checked_add(base))
.ok_or(TileError::Overflow)?;
let keep = u64::try_from(gap_len).is_ok_and(|g| g <= corner_count(su, advance));
Ok(if keep {
Bridge::Keep
} else {
Bridge::Detour { start: su, advance }
})
}
#[inline]
pub(crate) fn push_distinct<V: Vertex>(ring: &[V], out: &mut Vec<V>) -> usize {
let start = out.len();
for &v in ring {
if out.len() == start || out.last().map(Vertex::position) != Some(v.position()) {
out.push(v);
}
}
while out.len() - start >= 2 && out.last().map(Vertex::position) == Some(out[start].position())
{
out.pop();
}
out.len() - start
}
pub(crate) fn fill_box(orient: Ordering, sw: Coord<i32>, ne: Coord<i32>) -> [Coord<i32>; 5] {
let se = Coord { x: ne.x, y: sw.y };
let nw = Coord { x: sw.x, y: ne.y };
if orient == Ordering::Less {
[sw, nw, ne, se, sw]
} else {
[sw, se, ne, nw, sw]
}
}
#[inline]
pub(crate) fn keep_edge(arcs: &mut Vec<(usize, usize)>, t: usize) {
match arcs.last_mut() {
Some((_, end)) if t <= *end + 1 => *end = t + 1,
_ => arcs.push((t, t + 1)),
}
}
pub(crate) struct ClipScratch<V> {
copy: Vec<V>,
crossings: Vec<(u32, i64)>,
arcs: Vec<(usize, usize)>,
tmp: FrameTmp,
}
impl<V> ClipScratch<V> {
pub(crate) const fn new() -> Self {
Self {
copy: Vec::new(),
crossings: Vec::new(),
arcs: Vec::new(),
tmp: FrameTmp::new(),
}
}
}
impl<V: PolyVertex> ClipScratch<V> {
#[inline]
pub(crate) fn start_attempt(&mut self) {
self.tmp.grown_by = None;
}
#[inline]
pub(crate) fn close_ring(
&mut self,
ring: &[V],
arcs: &mut [(usize, usize)],
(min, max): (Coord<i32>, Coord<i32>),
winding: impl FnMut(usize, usize) -> i64,
piece: &mut PieceBuf<V>,
) -> Result<(), TileError> {
let plain = &RingPlan::PLAIN;
close_ring::<true, V>(ring, arcs, min, max, winding, plain, &mut self.tmp, piece)?;
Ok(())
}
#[inline]
pub(crate) fn close_ring_planned(
&mut self,
ring: &[V],
arcs: &mut [(usize, usize)],
(min, max): (Coord<i32>, Coord<i32>),
winding: impl FnMut(usize, usize) -> i64,
plan: &RingPlan<'_>,
piece: &mut PieceBuf<V>,
) -> Result<Closed, TileError> {
let tmp = &mut self.tmp;
if *plan == RingPlan::PLAIN {
close_ring::<true, V>(ring, arcs, min, max, winding, plan, tmp, piece)
} else {
close_ring::<false, V>(ring, arcs, min, max, winding, plan, tmp, piece)
}
}
}
fn merge_seam(arcs: &mut [(usize, usize)], m: usize) -> usize {
if let (Some(&(s0, e0)), Some(last)) = (arcs.first(), arcs.len().checked_sub(1))
&& last > 0
&& arcs[last].1 + 1 >= s0 + m
{
arcs[last].1 = e0 + m;
return 1;
}
0
}
pub(crate) struct Probe {
pub(crate) pts: Vec<Coord<i32>>,
pub(crate) tips: Vec<(Coord<i32>, u32, u8)>,
pub(crate) bridges: usize,
}
impl Probe {
pub(crate) const fn new() -> Self {
Self {
pts: Vec::new(),
tips: Vec::new(),
bridges: 0,
}
}
pub(crate) fn clear(&mut self) {
self.pts.clear();
self.tips.clear();
self.bridges = 0;
}
}
pub(crate) fn arcs_points<V: Vertex>(
ring: &[V],
arcs: &mut [(usize, usize)],
plan: RingPlan<'_>,
(min, max): (Coord<i32>, Coord<i32>),
probe: &mut Probe,
) {
let m = ring.len();
let first = merge_seam(arcs, m);
let arcs = &arcs[first..];
let Probe { pts, tips, bridges } = probe;
let base = *bridges;
let mut add = |i: usize| {
let p = ring[i % m].position();
if outcode(p, min, max) != 0 {
pts.push(p);
}
};
if let [(s, e)] = *arcs
&& e - s + 1 >= m
{
(0..m).for_each(&mut add); return;
}
for (j, &(s, e)) in arcs.iter().enumerate() {
(s..=e).for_each(&mut add);
let next = arcs.get(j + 1).map_or(arcs[0].0 + m, |a| a.0);
let gap = next - e - 1;
let (head, tail) = plan.depth(plan.bridge_base + j, gap);
(e + 1..=e + head).for_each(&mut add);
(next - tail..next).for_each(&mut add);
if head + tail < gap {
let bridge = u32::try_from(plan.bridge_base + j).unwrap_or(u32::MAX);
tips.push((ring[(e + head) % m].position(), bridge, 0));
tips.push((ring[(next - tail) % m].position(), bridge, 1));
}
}
*bridges = base + arcs.len();
}
struct FrameTmp {
pts: Vec<Coord<i32>>,
expanded: Vec<Coord<i32>>,
grown_by: Option<i32>,
}
impl FrameTmp {
const fn new() -> Self {
Self {
pts: Vec::new(),
expanded: Vec::new(),
grown_by: None,
}
}
}
pub(crate) struct PieceBuf<V> {
verts: Vec<V>,
ring_ends: Vec<u32>,
synthetic: Vec<u32>,
}
impl<V> PieceBuf<V> {
pub(crate) const fn new() -> Self {
Self {
verts: Vec::new(),
ring_ends: Vec::new(),
synthetic: Vec::new(),
}
}
#[cfg(test)]
pub(crate) fn from_parts(verts: Vec<V>, ring_ends: Vec<u32>, synthetic: Vec<u32>) -> Self {
Self {
verts,
ring_ends,
synthetic,
}
}
#[inline]
pub(crate) fn clear(&mut self) {
self.verts.clear();
self.ring_ends.clear();
self.synthetic.clear();
}
pub(crate) fn verts(&self) -> &[V] {
&self.verts
}
pub(crate) fn ring_ends(&self) -> &[u32] {
&self.ring_ends
}
pub(crate) fn synthetic(&self) -> &[u32] {
&self.synthetic
}
pub(crate) fn len(&self) -> usize {
self.verts.len()
}
#[inline]
pub(crate) fn reserve(&mut self, additional: usize) {
self.verts.reserve(additional);
}
}
impl<V: PolyVertex> PieceBuf<V> {
#[inline]
pub(crate) fn extend_input(&mut self, vs: impl IntoIterator<Item = V>) {
self.verts.extend(vs);
}
#[inline]
pub(crate) fn push_whole_ring(&mut self, ring: &[V]) -> Result<(), TileError> {
self.verts.extend_from_slice(ring);
self.end_ring()
}
#[inline]
pub(crate) fn push_connector(
&mut self,
corners: impl IntoIterator<Item = Result<Coord<i32>, TileError>>,
) -> Result<(), TileError> {
let from = self.verts.len() - 1;
for c in corners {
self.verts.push(V::synthetic_at(c?));
}
self.mark(from, self.verts.len() - from);
Ok(())
}
pub(crate) fn push_fill_box(
&mut self,
orient: Ordering,
min: Coord<i32>,
max: Coord<i32>,
) -> Result<(), TileError> {
let ring = fill_box(orient, corner(0, min, max)?, corner(4, min, max)?);
self.mark(self.verts.len(), 4);
self.verts
.extend(ring[..4].iter().copied().map(V::synthetic_at));
self.end_ring()
}
pub(crate) fn grow_fill_box(
&mut self,
min: Coord<i32>,
max: Coord<i32>,
) -> Result<(), TileError> {
let first_hole = self.ring_ends[0] as usize;
let (mut lo, mut hi) = (min, max);
for v in &self.verts[first_hole..] {
let p = v.position();
lo = Coord {
x: lo.x.min(p.x),
y: lo.y.min(p.y),
};
hi = Coord {
x: hi.x.max(p.x),
y: hi.y.max(p.y),
};
}
let (lo, hi) = lane_bounds(lo, hi, 1)?;
let ring = fill_box(ring_orientation(&self.verts[..first_hole]), lo, hi);
debug_assert_eq!(
ring.len(),
first_hole,
"the fill box is the piece's first ring"
);
for (slot, c) in self.verts.iter_mut().zip(ring) {
*slot = V::synthetic_at(c);
}
Ok(())
}
#[inline]
fn end_ring(&mut self) -> Result<(), TileError> {
let start = self.ring_ends.last().map_or(0, |&e| e as usize);
self.verts.push(self.verts[start]);
let end = u32::try_from(self.verts.len()).map_err(|_| TileError::GeometryTooLarge)?;
self.ring_ends.push(end);
Ok(())
}
#[inline]
#[expect(
clippy::cast_possible_truncation,
reason = "a piece's vertex count is checked to fit u32 as its rings end"
)]
fn mark(&mut self, first: usize, count: usize) {
debug_assert!(
self.synthetic.last().is_none_or(|&l| (l as usize) < first),
"marks are written in increasing order"
);
self.synthetic
.extend((first..first + count).map(|i| i as u32));
}
}
pub(crate) fn convex_hull(pts: &mut Vec<Coord<i32>>, hull: &mut Vec<Coord<i32>>) {
hull.clear();
pts.sort_unstable_by_key(|c| (c.x, c.y));
pts.dedup();
if pts.len() < 3 {
hull.extend_from_slice(pts);
return;
}
for pass in 0..2 {
let from = hull.len();
let walk: &mut dyn Iterator<Item = &Coord<i32>> = if pass == 0 {
&mut pts.iter()
} else {
&mut pts.iter().rev()
};
for &p in walk {
while hull.len() >= from + 2
&& orient(hull[hull.len() - 2], hull[hull.len() - 1], p) != Ordering::Greater
{
hull.pop();
}
hull.push(p);
}
hull.pop(); }
}
#[expect(
clippy::too_many_arguments,
clippy::many_single_char_names,
reason = "u and v are the excursion's tips; the rest is the box, hull, lane and buffers it needs"
)]
fn frame_bridge<V: PolyVertex>(
u: Coord<i32>,
v: Coord<i32>,
w_exc: i64,
(min, max): (Coord<i32>, Coord<i32>),
hull: &[Coord<i32>],
lane: i32,
tmp: &mut FrameTmp,
piece: &mut PieceBuf<V>,
) -> Result<bool, TileError> {
let lane = lane.max(1);
let mut grown = core::mem::take(&mut tmp.expanded);
if tmp.grown_by != Some(lane) {
tmp.pts.clear();
let g = |a: i32, d: i32| a.checked_add(d).ok_or(TileError::Overflow);
for c in hull {
for (sx, sy) in [(-lane, -lane), (lane, -lane), (lane, lane), (-lane, lane)] {
tmp.pts.push(Coord {
x: g(c.x, sx)?,
y: g(c.y, sy)?,
});
}
}
convex_hull(&mut tmp.pts, &mut grown);
tmp.grown_by = Some(lane);
}
let result = (|| {
let generated = |t: Coord<i32>| {
grown.iter().position(|e| {
e.x.abs_diff(t.x) == lane.unsigned_abs() && e.y.abs_diff(t.y) == lane.unsigned_abs()
})
};
let (Some(i), Some(j)) = (generated(u), generated(v)) else {
return Ok(false);
};
let n = grown.len();
let cy = center_y(min, max);
let ccw = (0..=(j + n - i) % n).map(|k| grown[(i + k) % n]);
let cw = (0..=(i + n - j) % n).map(|k| grown[(i + n - k) % n]);
let wind = |way: &mut dyn Iterator<Item = Coord<i32>>| {
ray_crossings(iter::once(u).chain(way).chain(iter::once(v)), cy, max.x)
};
let forward = if wind(&mut ccw.clone()) == w_exc {
true
} else if wind(&mut cw.clone()) == w_exc {
false
} else {
return Ok(false);
};
let way: &mut dyn Iterator<Item = Coord<i32>> =
if forward { &mut { ccw } } else { &mut { cw } };
piece.push_connector(way.map(Ok))?;
Ok::<_, TileError>(true)
})();
tmp.expanded = grown;
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Closed {
Ring,
FrameMissed,
}
#[inline]
#[expect(
clippy::too_many_arguments,
reason = "the ring, its arcs, the box, the winding, the plan and the buffers: one hot function"
)]
fn close_ring<const PLAIN: bool, V: PolyVertex>(
ring: &[V],
arcs: &mut [(usize, usize)],
min: Coord<i32>,
max: Coord<i32>,
mut winding: impl FnMut(usize, usize) -> i64,
plan: &RingPlan<'_>,
tmp: &mut FrameTmp,
piece: &mut PieceBuf<V>,
) -> Result<Closed, TileError> {
let m = ring.len();
let first = merge_seam(arcs, m);
let arcs = &arcs[first..];
if let [(s, e)] = *arcs
&& e - s + 1 >= m
{
piece.push_whole_ring(ring)?;
return Ok(Closed::Ring);
}
let cy = center_y(min, max);
for (j, &(s, e)) in arcs.iter().enumerate() {
piece.extend_input((s..=e).map(|i| ring[i % m]));
let next = arcs.get(j + 1).map_or(arcs[0].0 + m, |a| a.0);
let (head, tail) = if PLAIN {
(0, 0)
} else {
plan.depth(plan.bridge_base + j, next - e - 1)
};
if !PLAIN {
if head + tail == next - e - 1 {
piece.extend_input((e + 1..next).map(|i| ring[i % m]));
continue;
}
piece.extend_input((e + 1..=e + head).map(|i| ring[i % m]));
}
let (ui, vi) = (e + head, next - tail);
let (u, v) = (ring[ui % m].position(), ring[vi % m].position());
let w_exc = winding(ui % m, vi - ui);
let (cmin, cmax) = if PLAIN {
(min, max)
} else {
let lane = i32::try_from(j)
.ok()
.and_then(|j| j.checked_mul(plan.lane_step))
.and_then(|l| l.checked_add(plan.lane_base))
.ok_or(TileError::Overflow)?;
if let Some(hull) = plan.frame {
let lane = lane.checked_add(1).ok_or(TileError::Overflow)?;
if !frame_bridge(u, v, w_exc, (min, max), hull, lane, tmp, piece)? {
return Ok(Closed::FrameMissed);
}
piece.extend_input((vi..next).map(|i| ring[i % m]));
continue;
}
lane_bounds(min, max, lane)?
};
let bridge = if PLAIN {
plan_bridge(u, v, w_exc, vi - ui - 1, min, max, cy)?
} else {
plan_bridge_lane(u, v, w_exc, vi - ui - 1, (min, max), (cmin, cmax), cy)?
};
match bridge {
Bridge::Keep => piece.extend_input((ui + 1..vi).map(|i| ring[i % m])),
Bridge::Detour { start, advance } => {
let before = piece.len();
piece
.push_connector(corner_slots(start, advance).map(|s| corner(s, cmin, cmax)))?;
debug_assert_eq!(
ray_crossings(
iter::once(u)
.chain(piece.verts()[before..].iter().map(Vertex::position))
.chain(iter::once(v)),
cy,
max.x
),
w_exc,
"detour winding must match the excursion it replaces"
);
}
}
if !PLAIN {
piece.extend_input((vi..next).map(|i| ring[i % m]));
}
}
piece.end_ring()?;
Ok(Closed::Ring)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RingClip {
Outside,
Covers,
Clipped,
}
pub(crate) fn clip_ring<V: PolyVertex>(
ring: &[V],
min: Coord<i32>,
max: Coord<i32>,
scratch: &mut ClipScratch<V>,
piece: &mut PieceBuf<V>,
) -> Result<RingClip, TileError> {
let clip = clip_ring_by::<true, V>(ring, min, max, scratch, &RingPlan::PLAIN, None, piece)?;
debug_assert!(clip.is_some(), "the plain clip has no frame to miss");
Ok(clip.unwrap_or(RingClip::Outside))
}
pub(crate) fn clip_ring_planned<V: PolyVertex>(
ring: &[V],
min: Coord<i32>,
max: Coord<i32>,
scratch: &mut ClipScratch<V>,
plan: &RingPlan<'_>,
probe: Option<&mut Probe>,
piece: &mut PieceBuf<V>,
) -> Result<Option<RingClip>, TileError> {
clip_ring_by::<false, V>(ring, min, max, scratch, plan, probe, piece)
}
#[inline(always)]
#[expect(
clippy::inline_always,
reason = "each caller is one instance; passing the plan's arguments costs the plain clip ~3%"
)]
fn clip_ring_by<const PLAIN: bool, V: PolyVertex>(
ring: &[V],
min: Coord<i32>,
max: Coord<i32>,
scratch: &mut ClipScratch<V>,
plan: &RingPlan<'_>,
probe: Option<&mut Probe>,
piece: &mut PieceBuf<V>,
) -> Result<Option<RingClip>, TileError> {
let ClipScratch {
copy,
crossings,
arcs,
tmp,
} = scratch;
let pts = if let Some(n) = distinct_prefix(ring) {
&ring[..n]
} else {
copy.clear();
push_distinct(ring, copy);
copy.as_slice()
};
let m = pts.len();
if m < 3 {
return Ok(Some(RingClip::Outside));
}
u32::try_from(m).map_err(|_| TileError::GeometryTooLarge)?;
if all_inside(pts, min, max) {
if !PLAIN && probe.is_some() {
return Ok(Some(RingClip::Clipped)); }
piece.reserve(m + 1);
piece.push_whole_ring(pts)?;
return Ok(Some(RingClip::Clipped));
}
let cy = center_y(min, max);
let code = |p: Coord<i32>| outcode(p, min, max) | if p.y > cy { ABOVE } else { 0 };
arcs.clear();
crossings.clear();
let (mut a, mut ca) = (pts[0].position(), code(pts[0].position()));
for (i, b) in (0..).zip(pts[1..].iter().chain(iter::once(&pts[0]))) {
let b = b.position();
let cb = code(b);
let (oa, ob) = (ca & BOX, cb & BOX);
if oa & ob == 0 && (oa == 0 || ob == 0 || line_meets_box(a, b, min, max)) {
keep_edge(arcs, i as usize);
}
if (ca ^ cb) & ABOVE != 0 {
let w = ray_crossing(a, b, cy, max.x);
if w != 0 {
crossings.push((i, w));
}
}
(a, ca) = (b, cb);
}
if arcs.is_empty() {
let inside = crossings.len() % 2 == 1;
debug_assert_eq!(
inside,
point_in_distinct_ring(min, m, |i| pts[i].position()),
"ray parity must agree with point-in-ring containment"
);
return Ok(Some(if inside {
if PLAIN || probe.is_none() {
piece.push_fill_box(ring_orientation(pts), min, max)?;
}
RingClip::Covers
} else {
RingClip::Outside
}));
}
let sum = |lo: usize, hi: usize| -> i64 {
let i = crossings.partition_point(|c| (c.0 as usize) < lo);
let j = crossings.partition_point(|c| (c.0 as usize) < hi);
crossings[i..j].iter().map(|c| c.1).sum()
};
let winding = |first: usize, count: usize| {
let w = if first + count <= m {
sum(first, first + count)
} else {
sum(first, m) + sum(0, first + count - m)
};
debug_assert_eq!(
w,
ray_crossings(
(first..=first + count).map(|i| pts[i % m].position()),
cy,
max.x
),
"indexed winding must match walking the excursion"
);
w
};
if PLAIN || *plan == RingPlan::PLAIN {
close_ring::<true, V>(pts, arcs, min, max, winding, &RingPlan::PLAIN, tmp, piece)?;
} else if let Some(acc) = probe {
arcs_points(pts, arcs, *plan, (min, max), acc);
} else if close_ring::<false, V>(pts, arcs, min, max, winding, plan, tmp, piece)?
== Closed::FrameMissed
{
return Ok(None);
}
Ok(Some(RingClip::Clipped))
}
pub(crate) fn ring_meets_box<V: Vertex>(ring: &[V], min: Coord<i32>, max: Coord<i32>) -> RingClip {
let cy = center_y(min, max);
let (mut a, mut inside) = (ring[ring.len() - 1].position(), false);
for v in ring {
let b = v.position();
if segment_intersects(a, b, min, max) {
return RingClip::Clipped;
}
inside ^= ray_crossing(a, b, cy, max.x) != 0;
a = b;
}
if inside {
RingClip::Covers
} else {
RingClip::Outside
}
}
pub(crate) fn distinct_prefix<V: Vertex>(ring: &[V]) -> Option<usize> {
let first = ring.first()?.position();
let mut n = ring.len();
while n >= 2 && ring[n - 1].position() == first {
n -= 1;
}
ring[..n]
.windows(2)
.all(|w| w[0].position() != w[1].position())
.then_some(n)
}
pub(crate) fn scan_arcs<V: Vertex>(
ring: &[V],
min: Coord<i32>,
max: Coord<i32>,
store: &mut Vec<(usize, usize)>,
) -> Option<(usize, core::ops::Range<usize>)> {
let m = distinct_prefix(ring)?;
let pts = &ring[..m];
let start = store.len();
if m < 3 || pts.iter().all(|p| outcode(p.position(), min, max) == 0) {
return Some((m, start..start));
}
let (mut a, mut oa) = (pts[0].position(), outcode(pts[0].position(), min, max));
for (i, b) in pts[1..].iter().chain(iter::once(&pts[0])).enumerate() {
let b = b.position();
let ob = outcode(b, min, max);
if oa & ob == 0 && (oa == 0 || ob == 0 || line_meets_box(a, b, min, max)) {
match store[start..].last_mut() {
Some((_, end)) if i <= *end + 1 => *end = i + 1,
_ => store.push((i, i + 1)),
}
}
(a, oa) = (b, ob);
}
Some((m, start..store.len()))
}
#[cfg(test)]
mod tests {
use geo_types::Coord;
use super::*;
use crate::vertex::Measured;
fn c(x: i32, y: i32) -> Coord<i32> {
Coord { x, y }
}
const MIN: Coord<i32> = Coord { x: 0, y: 0 };
const MAX: Coord<i32> = Coord { x: 9, y: 9 };
fn clip(ring: &[Coord<i32>]) -> (RingClip, Vec<Coord<i32>>) {
let mut piece = PieceBuf::new();
let rc = clip_ring(ring, MIN, MAX, &mut ClipScratch::new(), &mut piece).unwrap();
(rc, piece.verts().to_vec())
}
#[test]
fn ring_of_two_distinct_vertices_is_outside() {
let ring = [c(2, 2), c(2, 2), c(7, 7), c(7, 7), c(2, 2)];
assert!(matches!(clip(&ring), (RingClip::Outside, out) if out.is_empty()));
}
#[test]
fn hull_is_strict_and_counter_clockwise() {
let mut pts = vec![
c(0, 0),
c(4, 0),
c(4, 4),
c(0, 4),
c(2, 2),
c(2, 0),
c(4, 4),
];
let mut hull = Vec::new();
convex_hull(&mut pts, &mut hull);
assert_eq!(hull, [c(0, 0), c(4, 0), c(4, 4), c(0, 4)]);
let mut two = vec![c(1, 1), c(1, 1), c(2, 3)];
convex_hull(&mut two, &mut hull);
assert_eq!(hull, [c(1, 1), c(2, 3)]);
}
#[test]
fn marks_are_exactly_the_synthetic_edges() {
let input = |x, y| Measured::new(x, y, 1_u8);
let mut piece = PieceBuf::new();
piece.push_fill_box(Ordering::Less, MIN, MAX).unwrap();
piece.extend_input([input(3, 3), input(3, 12)]);
piece
.push_connector([Ok(c(10, 12)), Ok(c(10, 10))])
.unwrap();
piece.extend_input([input(6, 6), input(12, 3)]);
piece.push_connector([]).unwrap();
piece.end_ring().unwrap();
assert_eq!(piece.ring_ends(), [5, 12]);
let chord = 10;
let marked = |piece: &PieceBuf<Measured<u8>>| {
let v = piece.verts();
(0..v.len() - 1)
.filter(|&i| i + 1 != 5) .filter(|&i| v[i].m == 0 || v[i + 1].m == 0 || i == chord)
.map(|i| u32::try_from(i).unwrap())
.collect::<Vec<_>>()
};
assert_eq!(piece.synthetic(), marked(&piece));
assert_eq!(piece.synthetic(), [0, 1, 2, 3, 6, 7, 8, 10]);
piece.grow_fill_box(MIN, MAX).unwrap();
assert_eq!(piece.verts()[0].position, c(-1, -1));
assert_eq!(piece.verts()[2].position, c(13, 13));
assert_eq!(piece.ring_ends(), [5, 12]);
assert_eq!(piece.synthetic(), marked(&piece));
piece.clear();
assert!(piece.verts().is_empty() && piece.synthetic().is_empty());
}
#[test]
fn defensive_edge_cases() {
assert_eq!(
slot(0),
0,
"an inside point never ends a detour; it maps to a valid slot"
);
assert_eq!(
ray_crossings(iter::empty(), 0, 0),
0,
"an empty path crosses nothing"
);
}
}