use core::cmp::Ordering;
use core::iter;
use geo_types::Coord;
use crate::TileError;
use crate::clip_polyline::line_meets_box;
use crate::geom::{point_in_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,
}
}
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> {
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, min, max)?;
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 push_fill_box<V: PolyVertex>(
out: &mut Vec<V>,
orient: Ordering,
min: Coord<i32>,
max: Coord<i32>,
) -> Result<(), TileError> {
let ring = fill_box(orient, corner(0, min, max)?, corner(4, min, max)?);
out.extend(ring.into_iter().map(V::synthetic_at));
Ok(())
}
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)>,
}
impl<V> ClipScratch<V> {
pub(crate) const fn new() -> Self {
Self {
copy: Vec::new(),
crossings: Vec::new(),
arcs: Vec::new(),
}
}
}
#[inline]
pub(crate) fn close_ring<V: PolyVertex>(
ring: &[V],
arcs: &mut [(usize, usize)],
min: Coord<i32>,
max: Coord<i32>,
mut winding: impl FnMut(usize, usize) -> i64,
out: &mut Vec<V>,
) -> Result<(), TileError> {
let m = ring.len();
let mut first = 0;
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;
first = 1;
}
let arcs = &arcs[first..];
if let [(s, e)] = *arcs
&& e - s + 1 >= m
{
out.extend_from_slice(ring);
out.push(ring[0]);
return Ok(());
}
let cy = center_y(min, max);
let ring_start = out.len();
for (j, &(s, e)) in arcs.iter().enumerate() {
out.extend((s..=e).map(|i| ring[i % m]));
let next = arcs.get(j + 1).map_or(arcs[0].0 + m, |a| a.0);
let (u, v) = (ring[e % m].position(), ring[next % m].position());
let w_exc = winding(e % m, next - e);
match plan_bridge(u, v, w_exc, next - e - 1, min, max, cy)? {
Bridge::Keep => out.extend((e + 1..next).map(|i| ring[i % m])),
Bridge::Detour { start, advance } => {
let before = out.len();
for s in corner_slots(start, advance) {
out.push(V::synthetic_at(corner(s, min, max)?));
}
debug_assert_eq!(
ray_crossings(
iter::once(u)
.chain(out[before..].iter().map(Vertex::position))
.chain(iter::once(v)),
cy,
max.x
),
w_exc,
"detour winding must match the excursion it replaces"
);
}
}
}
out.push(out[ring_start]); Ok(())
}
#[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>,
out: &mut Vec<V>,
) -> Result<RingClip, TileError> {
let ClipScratch {
copy,
crossings,
arcs,
} = 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(RingClip::Outside);
}
u32::try_from(m).map_err(|_| TileError::GeometryTooLarge)?;
if pts.iter().all(|p| outcode(p.position(), min, max) == 0) {
out.reserve(m + 1);
out.extend_from_slice(pts);
out.push(pts[0]);
return Ok(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_ring(min, &pts.iter().map(Vertex::position).collect::<Vec<_>>()),
"ray parity must agree with point-in-ring containment"
);
return if inside {
push_fill_box(out, ring_orientation(pts), min, max)?;
Ok(RingClip::Covers)
} else {
Ok(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
};
close_ring(pts, arcs, min, max, winding, out)?;
Ok(RingClip::Clipped)
}
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)
}
#[cfg(test)]
mod tests {
use geo_types::Coord;
use super::*;
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 out = Vec::new();
let rc = clip_ring(ring, MIN, MAX, &mut ClipScratch::new(), &mut out).unwrap();
(rc, out)
}
#[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 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"
);
}
}