use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_model::Segment;
use geometry_trait::{Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait};
use super::info::{Method, Operation, RingKind, SegmentId, Turn};
use crate::predicate::segment_intersection::{SegmentIntersection, segment_intersection};
use crate::turn::classify::set_from_outcome;
pub trait TurnPoint: PointMut + Default + Copy {}
impl<P: PointMut + Default + Copy> TurnPoint for P {}
#[must_use]
pub fn get_turns_ring_ring<R1, R2, P>(
r1: &R1,
source1: usize,
ring1: RingKind,
r2: &R2,
source2: usize,
ring2: RingKind,
) -> Vec<Turn<P>>
where
R1: RingTrait<Point = P>,
R2: RingTrait<Point = P>,
P: TurnPoint,
P::Scalar: CoordinateScalar + Into<f64>,
{
let segs1 = ring_segments(r1);
let segs2 = ring_segments(r2);
let mut turns = Vec::new();
collect_pair(&segs1, source1, ring1, &segs2, source2, ring2, &mut turns);
turns
}
#[must_use]
pub fn get_turns_polygon_polygon<G1, G2, P>(g1: &G1, g2: &G2) -> Vec<Turn<P>>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: TurnPoint,
P::Scalar: CoordinateScalar + Into<f64>,
{
let mut turns = Vec::new();
let rings1 = rings_of(g1);
let rings2 = rings_of(g2);
for &(ring1, r1) in &rings1 {
let segs1 = ring_segments(r1);
for &(ring2, r2) in &rings2 {
let segs2 = ring_segments(r2);
collect_pair(&segs1, 0, ring1, &segs2, 1, ring2, &mut turns);
}
}
turns
}
#[allow(clippy::too_many_arguments)]
fn collect_pair<P>(
segs1: &[(P, P)],
source1: usize,
ring1: RingKind,
segs2: &[(P, P)],
source2: usize,
ring2: RingKind,
out: &mut Vec<Turn<P>>,
) where
P: TurnPoint,
P::Scalar: CoordinateScalar + Into<f64> + PartialEq,
{
for (i1, &(a1, b1)) in segs1.iter().enumerate() {
let s1 = Segment::new(a1, b1);
for (i2, &(a2, b2)) in segs2.iter().enumerate() {
let s2 = Segment::new(a2, b2);
let id1 = SegmentId {
source_index: source1,
ring: ring1,
segment_index: i1,
};
let id2 = SegmentId {
source_index: source2,
ring: ring2,
segment_index: i2,
};
let outcome = segment_intersection::<Segment<P>, P>(&s1, &s2);
match outcome {
SegmentIntersection::Disjoint | SegmentIntersection::OutOfRange => {}
SegmentIntersection::Single(point) => {
out.push(classified(point, id1, id2, &outcome, &a1, &b1, &a2, &b2));
}
SegmentIntersection::Collinear { from, to } => {
out.push(classified(from, id1, id2, &outcome, &a1, &b1, &a2, &b2));
out.push(classified(to, id1, id2, &outcome, &a1, &b1, &a2, &b2));
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn classified<P>(
point: P,
id1: SegmentId,
id2: SegmentId,
outcome: &SegmentIntersection<P>,
a0: &P,
a1: &P,
b0: &P,
b1: &P,
) -> Turn<P>
where
P: Point,
P::Scalar: PartialEq,
{
let mut turn = Turn {
point,
method: Method::None,
operations: [Operation::new(id1), Operation::new(id2)],
touch_only: false,
};
set_from_outcome(&mut turn, outcome, a0, a1, b0, b1);
turn
}
fn ring_segments<R, P>(ring: &R) -> Vec<(P, P)>
where
R: RingTrait<Point = P>,
P: Copy + Point,
{
let pts: Vec<P> = ring.points().copied().collect();
let mut segs = Vec::new();
if pts.len() < 2 {
return segs;
}
for w in pts.windows(2) {
segs.push((w[0], w[1]));
}
let first = pts[0];
let last = pts[pts.len() - 1];
if !same_point(&first, &last) {
segs.push((last, first));
}
segs
}
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>()
}
fn rings_of<G, P>(g: &G) -> Vec<(RingKind, &G::Ring)>
where
G: PolygonTrait<Point = P>,
P: Point,
{
let mut out: Vec<(RingKind, &G::Ring)> = Vec::new();
out.push((RingKind::Exterior, g.exterior()));
for (i, r) in g.interiors().enumerate() {
out.push((RingKind::Interior(i), r));
}
out
}
#[cfg(test)]
mod tests {
use super::{get_turns_polygon_polygon, get_turns_ring_ring};
use crate::turn::info::RingKind;
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Ring, polygon};
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 overlapping_squares_two_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);
}
#[test]
fn disjoint_squares_no_turns() {
let a = square(0.0, 0.0, 1.0);
let b = square(5.0, 5.0, 1.0);
let turns = get_turns_ring_ring(&a, 0, RingKind::Exterior, &b, 1, RingKind::Exterior);
assert!(turns.is_empty());
}
#[test]
fn polygon_pair_two_turns() {
use geometry_model::Polygon;
let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
let b: Polygon<P> = polygon![[(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0), (1.0, 1.0)]];
let turns = get_turns_polygon_polygon(&a, &b);
assert_eq!(turns.len(), 2);
}
#[test]
fn each_turn_names_both_sources() {
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);
for t in &turns {
assert_eq!(t.operations[0].seg_id.source_index, 0);
assert_eq!(t.operations[1].seg_id.source_index, 1);
}
}
}