use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_trait::{Point, Ring as RingTrait};
use crate::turn::info::Turn;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Node<P> {
Vertex(P),
Turn {
point: P,
turn_id: usize,
},
}
impl<P: Point> Node<P> {
#[must_use]
pub fn point(&self) -> &P {
match self {
Node::Vertex(p) | Node::Turn { point: p, .. } => p,
}
}
#[must_use]
pub fn turn_id(&self) -> Option<usize> {
match self {
Node::Turn { turn_id, .. } => Some(*turn_id),
Node::Vertex(_) => None,
}
}
}
#[derive(Debug, Clone)]
pub struct EnrichedRings<P> {
pub rings: [Vec<Node<P>>; 2],
}
impl<P: Point> EnrichedRings<P> {
#[must_use]
pub fn locate_turn(&self, ring_index: usize, turn_id: usize) -> Option<usize> {
self.rings[ring_index]
.iter()
.position(|n| n.turn_id() == Some(turn_id))
}
}
#[must_use]
pub fn enrich<R1, R2, P>(r1: &R1, r2: &R2, turns: &[Turn<P>]) -> EnrichedRings<P>
where
R1: RingTrait<Point = P>,
R2: RingTrait<Point = P>,
P: Point + Copy,
P::Scalar: CoordinateScalar,
{
let ring0 = enrich_one(&ring_vertices(r1), turns, 0);
let ring1 = enrich_one(&ring_vertices(r2), turns, 1);
EnrichedRings {
rings: [ring0, ring1],
}
}
fn enrich_one<P>(vertices: &[P], turns: &[Turn<P>], operation_index: usize) -> Vec<Node<P>>
where
P: Point + Copy,
P::Scalar: CoordinateScalar,
{
let n = vertices.len();
if n < 2 {
return vertices.iter().map(|&p| Node::Vertex(p)).collect();
}
let closed = same_point(&vertices[0], &vertices[n - 1]);
let seg_count = if closed { n - 1 } else { n };
let mut out = Vec::new();
for seg in 0..seg_count {
let start = vertices[seg];
let end = vertices[(seg + 1) % n];
out.push(Node::Vertex(start));
let mut on_seg: Vec<(P::Scalar, usize)> = turns
.iter()
.enumerate()
.filter(|(_, t)| t.operations[operation_index].seg_id.segment_index == seg)
.map(|(id, t)| (dist_sq(&start, &t.point), id))
.collect();
on_seg.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
for (_, turn_id) in on_seg {
if same_point(&turns[turn_id].point, &start) {
continue;
}
if same_point(&turns[turn_id].point, &end) {
continue;
}
out.push(Node::Turn {
point: turns[turn_id].point,
turn_id,
});
}
}
out
}
fn ring_vertices<R, P>(ring: &R) -> Vec<P>
where
R: RingTrait<Point = P>,
P: Copy + Point,
{
ring.points().copied().collect()
}
fn dist_sq<P>(a: &P, b: &P) -> P::Scalar
where
P: Point,
P::Scalar: CoordinateScalar,
{
let dx = a.get::<0>() - b.get::<0>();
let dy = a.get::<1>() - b.get::<1>();
dx * dx + dy * dy
}
fn same_point<P: Point>(a: &P, b: &P) -> bool
where
P::Scalar: PartialEq,
{
a.get::<0>() == b.get::<0>() && a.get::<1>() == b.get::<1>()
}
#[cfg(test)]
mod tests {
use super::{Node, enrich};
use crate::turn::{RingKind, get_turns_ring_ring};
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Ring};
use geometry_trait::Point 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 both_rings_get_both_turns() {
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);
assert_eq!(turns.len(), 2);
let e = enrich(&a, &b, &turns);
for turn_id in 0..turns.len() {
assert!(
e.locate_turn(0, turn_id).is_some(),
"ring0 missing turn {turn_id}"
);
assert!(
e.locate_turn(1, turn_id).is_some(),
"ring1 missing turn {turn_id}"
);
}
}
#[test]
fn short_rings_preserve_their_available_vertices() {
let one: Ring<P> = Ring::from_vec(vec![P::new(1.0, 2.0)]);
let empty = Ring::<P>::new();
let enriched = enrich(&one, &empty, &[]);
assert_eq!(enriched.rings[0], vec![Node::Vertex(P::new(1.0, 2.0))]);
assert!(enriched.rings[1].is_empty());
}
#[test]
fn enriched_ring_has_vertices_and_turns() {
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 vertices = e.rings[0]
.iter()
.filter(|n| matches!(n, Node::Vertex(_)))
.count();
let turn_nodes = e.rings[0]
.iter()
.filter(|n| matches!(n, Node::Turn { .. }))
.count();
assert_eq!(vertices, 4);
assert_eq!(turn_nodes, 2);
}
#[test]
fn turns_ordered_along_segment() {
let strip: Ring<P> = Ring::from_vec(vec![
P::new(0.0, 0.0),
P::new(4.0, 0.0),
P::new(4.0, 2.0),
P::new(0.0, 2.0),
P::new(0.0, 0.0),
]);
let crosser: Ring<P> = Ring::from_vec(vec![
P::new(1.0, -1.0),
P::new(3.0, -1.0),
P::new(3.0, 1.0),
P::new(1.0, 1.0),
P::new(1.0, -1.0),
]);
let turns = get_turns_ring_ring(
&strip,
0,
RingKind::Exterior,
&crosser,
1,
RingKind::Exterior,
);
let e = enrich(&strip, &crosser, &turns);
let xs: Vec<f64> = e.rings[0]
.iter()
.filter_map(|n| match n {
Node::Turn { point, .. } => Some(point.get::<0>()),
Node::Vertex(_) => None,
})
.collect();
assert!(
xs.windows(2)
.all(|w| w[0] <= w[1] || (w[0] - w[1]).abs() > 1.5)
);
}
}