use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_model::Ring;
use geometry_trait::{Point, PointMut};
use super::enrich::{EnrichedRings, Node};
use crate::turn::info::Turn;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverlayOp {
Intersection,
Union,
Difference,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraversalError {
Unsupported,
}
pub fn traverse<P>(
enriched: &EnrichedRings<P>,
turns: &[Turn<P>],
op: OverlayOp,
) -> Result<Vec<Ring<P>>, TraversalError>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
{
let mut visited = alloc::vec![false; turns.len()];
let mut rings = Vec::new();
for start_turn in 0..turns.len() {
if visited[start_turn] {
continue;
}
match walk_from(enriched, start_turn, op, &mut visited) {
Some(ring) => rings.push(ring),
None => return Err(TraversalError::Unsupported),
}
}
Ok(rings)
}
fn walk_from<P>(
enriched: &EnrichedRings<P>,
start_turn: usize,
op: OverlayOp,
visited: &mut [bool],
) -> Option<Ring<P>>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
{
let (mut ring_index, mut dir) = choose_start_step(enriched, start_turn, op)?;
let mut pos = enriched.locate_turn(ring_index, start_turn)?;
let mut out: Vec<P> = Vec::new();
let total_nodes: usize = enriched.rings[0].len() + enriched.rings[1].len();
let budget = 2 * total_nodes + 2;
let mut closed = false;
for step in 0..budget {
let node = &enriched.rings[ring_index][pos];
out.push(*node.point());
if let Some(turn_id) = node.turn_id() {
visited[turn_id] = true;
if step != 0 {
let (nr, np, nd) = step_at_turn(enriched, ring_index, pos, dir, op)?;
ring_index = nr;
pos = np;
dir = nd;
}
}
pos = step_index(pos, dir, enriched.rings[ring_index].len());
if enriched.rings[ring_index][pos].turn_id() == Some(start_turn) {
closed = true;
break;
}
}
if !closed || out.len() < 3 {
return None;
}
out.push(out[0]);
let ring: Ring<P> = Ring::from_vec(out);
Some(ring)
}
fn step_index(pos: usize, dir: i8, len: usize) -> usize {
if dir >= 0 {
(pos + 1) % len
} else {
(pos + len - 1) % len
}
}
fn step_at_turn<P>(
enriched: &EnrichedRings<P>,
ring_index: usize,
pos: usize,
dir: i8,
op: OverlayOp,
) -> Option<(usize, usize, i8)>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
{
let turn_id = enriched.rings[ring_index][pos].turn_id()?;
let other = 1 - ring_index;
let other_pos = enriched.locate_turn(other, turn_id)?;
let here = *enriched.rings[ring_index][pos].point();
let candidates = [
(other, other_pos, 1i8),
(other, other_pos, -1i8),
(ring_index, pos, dir),
];
for &(r, p, d) in &candidates {
let next = step_index(p, d, enriched.rings[r].len());
let there = *enriched.rings[r][next].point();
let mid = midpoint::<P>(&here, &there);
if segment_in_region(enriched, r, &mid, op) {
return Some((r, p, d));
}
}
None
}
fn segment_in_region<P>(enriched: &EnrichedRings<P>, r: usize, mid: &P, op: OverlayOp) -> bool
where
P: Point,
P::Scalar: CoordinateScalar,
{
let inside_other = point_in_ring(mid, &enriched.rings[1 - r]);
match op {
OverlayOp::Intersection => inside_other,
OverlayOp::Union => !inside_other,
OverlayOp::Difference => {
if r == 0 {
!inside_other
} else {
inside_other
}
}
}
}
fn midpoint<P>(a: &P, b: &P) -> P
where
P: PointMut + Default,
P::Scalar: CoordinateScalar,
{
let two = P::Scalar::ONE + P::Scalar::ONE;
let mut m = P::default();
m.set::<0>((a.get::<0>() + b.get::<0>()) / two);
m.set::<1>((a.get::<1>() + b.get::<1>()) / two);
m
}
fn choose_start_step<P>(
enriched: &EnrichedRings<P>,
start_turn: usize,
op: OverlayOp,
) -> Option<(usize, i8)>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
{
for ring_index in [0usize, 1] {
let at = enriched.locate_turn(ring_index, start_turn)?;
let here = *enriched.rings[ring_index][at].point();
for dir in [1i8, -1] {
let next = step_index(at, dir, enriched.rings[ring_index].len());
let there = *enriched.rings[ring_index][next].point();
let mid = midpoint::<P>(&here, &there);
if segment_in_region(enriched, ring_index, &mid, op) {
return Some((ring_index, dir));
}
}
}
None
}
fn point_in_ring<P>(p: &P, ring: &[Node<P>]) -> bool
where
P: Point,
P::Scalar: CoordinateScalar,
{
let px = p.get::<0>();
let py = p.get::<1>();
let pts: Vec<&P> = ring.iter().map(Node::point).collect();
let n = pts.len();
let mut inside = false;
let mut j = n - 1;
for i in 0..n {
let xi = pts[i].get::<0>();
let yi = pts[i].get::<1>();
let xj = pts[j].get::<0>();
let yj = pts[j].get::<1>();
let crosses = (yi > py) != (yj > py);
if crosses {
let t = (py - yi) / (yj - yi);
let x_at = xi + t * (xj - xi);
if px < x_at {
inside = !inside;
}
}
j = i;
}
inside
}
#[cfg(test)]
mod tests {
use super::{OverlayOp, traverse};
use crate::traverse::enrich::enrich;
use crate::turn::{RingKind, get_turns_ring_ring};
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Ring};
use geometry_trait::{Point as _, Ring as _};
type P = Point2D<f64, Cartesian>;
fn square(x: f64, y: f64, s: f64) -> Ring<P> {
Ring::from_vec(vec![
P::new(x, y),
P::new(x + s, y),
P::new(x + s, y + s),
P::new(x, y + s),
P::new(x, y),
])
}
#[test]
fn intersection_of_offset_squares_is_one_ring() {
let a = square(0.0, 0.0, 2.0);
let b = square(1.0, 1.0, 2.0);
let turns = get_turns_ring_ring(&a, 0, RingKind::Exterior, &b, 1, RingKind::Exterior);
let e = enrich(&a, &b, &turns);
let rings = traverse(&e, &turns, OverlayOp::Intersection).unwrap();
assert_eq!(rings.len(), 1);
let ring = &rings[0];
let pts: Vec<(f64, f64)> = ring
.points()
.map(|p| (p.get::<0>(), p.get::<1>()))
.collect();
for (x, y) in &pts {
assert!((*x - 1.0).abs() < 1e-9 || (*x - 2.0).abs() < 1e-9, "x={x}");
assert!((*y - 1.0).abs() < 1e-9 || (*y - 2.0).abs() < 1e-9, "y={y}");
}
assert_eq!(ring.points().count(), 5);
}
}