use geometry_cs::Cartesian;
use geometry_model::{Point2D, Polygon, Ring, polygon};
use geometry_overlay::turn::{
Method, OperationType, RingKind, get_turns_polygon_polygon, get_turns_ring_ring,
};
use geometry_trait::Point as _;
type P = Point2D<f64, Cartesian>;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
#[test]
fn overlapping_squares_two_crossings() {
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, "expected two boundary crossings");
for t in &turns {
assert_eq!(t.method, Method::Crosses);
assert!(!t.touch_only);
assert_eq!(t.operations[0].operation, OperationType::Union);
assert_eq!(t.operations[1].operation, OperationType::Intersection);
}
let mut pts: Vec<(f64, f64)> = turns
.iter()
.map(|t| (t.point.get::<0>(), t.point.get::<1>()))
.collect();
pts.sort_by(|x, y| x.partial_cmp(y).unwrap());
assert!(approx(pts[0].0, 1.0) && approx(pts[0].1, 2.0));
assert!(approx(pts[1].0, 2.0) && approx(pts[1].1, 1.0));
}
#[test]
fn corner_touch_is_classified_touch() {
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![[(2.0, 1.0), (4.0, 0.0), (4.0, 2.0), (2.0, 1.0)]];
let turns = get_turns_polygon_polygon(&a, &b);
assert!(!turns.is_empty(), "expected at least the touch turn");
assert!(
turns
.iter()
.any(|t| t.method == Method::Touch && t.touch_only),
"expected a Touch turn at the vertex-on-edge contact"
);
assert!(
turns
.iter()
.any(|t| approx(t.point.get::<0>(), 2.0) && approx(t.point.get::<1>(), 1.0)),
"expected a turn at the contact point (2,1)"
);
}
#[test]
fn disjoint_polygons_empty_graph() {
let a: Polygon<P> = polygon![[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)]];
let b: Polygon<P> = polygon![[(5.0, 5.0), (6.0, 5.0), (6.0, 6.0), (5.0, 6.0), (5.0, 5.0)]];
let turns = get_turns_polygon_polygon(&a, &b);
assert!(turns.is_empty());
}
#[test]
fn every_turn_references_both_inputs() {
let a: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)]];
let b: Polygon<P> = polygon![[(2.0, 2.0), (6.0, 2.0), (6.0, 6.0), (2.0, 6.0), (2.0, 2.0)]];
let turns = get_turns_polygon_polygon(&a, &b);
assert_eq!(turns.len(), 2);
for t in &turns {
assert_eq!(t.operations[0].seg_id.source_index, 0);
assert_eq!(t.operations[1].seg_id.source_index, 1);
}
}
#[test]
fn shared_edges_are_reported_as_collinear_turns() {
let a: Polygon<P> = polygon![[(0.0, 0.0), (3.0, 0.0), (3.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
let b: Polygon<P> = polygon![[(1.0, 0.0), (2.0, 0.0), (2.0, -2.0), (1.0, -2.0), (1.0, 0.0)]];
let turns = get_turns_polygon_polygon(&a, &b);
assert!(turns.iter().any(|turn| turn.method == Method::Collinear));
}
#[test]
fn short_and_open_rings_use_the_public_ring_entry() {
let short: Ring<P> = Ring::from_vec(vec![P::new(0.0, 0.0)]);
let square: Ring<P> = Ring::from_vec(vec![
P::new(0.0, 0.0),
P::new(2.0, 0.0),
P::new(2.0, 2.0),
P::new(0.0, 2.0),
P::new(0.0, 0.0),
]);
assert!(
get_turns_ring_ring(
&short,
0,
RingKind::Exterior,
&square,
1,
RingKind::Exterior,
)
.is_empty()
);
let open: Ring<P> = Ring::from_vec(vec![P::new(-1.0, 1.0), P::new(1.0, 3.0), P::new(3.0, 1.0)]);
assert!(
!get_turns_ring_ring(&open, 0, RingKind::Exterior, &square, 1, RingKind::Exterior,)
.is_empty()
);
}
#[test]
fn polygon_hole_turns_retain_the_interior_ring_identity() {
let donut: Polygon<P> = polygon![
[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
],
[(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
];
let crossing: Polygon<P> =
polygon![[(2.0, 4.0), (8.0, 4.0), (8.0, 6.0), (2.0, 6.0), (2.0, 4.0)]];
let turns = get_turns_polygon_polygon(&donut, &crossing);
assert!(turns.iter().any(|turn| {
turn.operations
.iter()
.any(|operation| operation.seg_id.ring == RingKind::Interior(0))
}));
}