use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_tag::{PointTag, PolygonTag, SameAs, SegmentTag};
use geometry_trait::{
Point as PointTrait, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
Segment as SegmentTrait, segment_end, segment_start,
};
pub trait EqualsStrategy<A, B> {
fn equals(&self, a: &A, b: &B) -> bool;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct EqPointPoint;
#[derive(Debug, Default, Clone, Copy)]
pub struct EqSegmentSegment;
#[derive(Debug, Default, Clone, Copy)]
pub struct EqPolygonPolygon;
impl<A, B> EqualsStrategy<A, B> for EqPointPoint
where
A: PointTrait,
B: PointTrait<Scalar = A::Scalar>,
<A::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn equals(&self, a: &A, b: &B) -> bool {
let mut i = 0;
while i < A::DIM {
let eq = match i {
0 => a.get::<0>() == b.get::<0>(),
1 => a.get::<1>() == b.get::<1>(),
2 => a.get::<2>() == b.get::<2>(),
3 => a.get::<3>() == b.get::<3>(),
_ => panic!("CartesianEquals: dimension exceeds MAX_DIM (4)"),
};
if !eq {
return false;
}
i += 1;
}
true
}
}
impl<A, B, P> EqualsStrategy<A, B> for EqSegmentSegment
where
A: SegmentTrait<Point = P>,
B: SegmentTrait<Point = P>,
P: PointTrait + PointMut + Default,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn equals(&self, a: &A, b: &B) -> bool {
let (a1, a2) = (segment_start(a), segment_end(a));
let (b1, b2) = (segment_start(b), segment_end(b));
(point_eq_2d(&a1, &b1) && point_eq_2d(&a2, &b2))
|| (point_eq_2d(&a1, &b2) && point_eq_2d(&a2, &b1))
}
}
impl<A, B, P> EqualsStrategy<A, B> for EqPolygonPolygon
where
A: PolygonTrait<Point = P>,
B: PolygonTrait<Point = P, Ring = A::Ring>,
P: PointTrait,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
fn equals(&self, a: &A, b: &B) -> bool {
if !rings_equal(a.exterior(), b.exterior()) {
return false;
}
if a.interiors().count() != b.interiors().count() {
return false;
}
let bh: alloc::vec::Vec<&B::Ring> = b.interiors().collect();
let mut matched = alloc::vec![false; bh.len()];
for ha in a.interiors() {
let mut found = false;
for (j, hb) in bh.iter().enumerate() {
if !matched[j] && rings_equal(ha, *hb) {
matched[j] = true;
found = true;
break;
}
}
if !found {
return false;
}
}
true
}
}
#[doc(hidden)]
pub trait EqualsPairStrategy<K2> {
type S: Default;
}
impl EqualsPairStrategy<PointTag> for PointTag {
type S = EqPointPoint;
}
impl EqualsPairStrategy<SegmentTag> for SegmentTag {
type S = EqSegmentSegment;
}
impl EqualsPairStrategy<PolygonTag> for PolygonTag {
type S = EqPolygonPolygon;
}
extern crate alloc;
#[inline]
fn point_eq_2d<Pa, Pb>(a: &Pa, b: &Pb) -> bool
where
Pa: PointTrait,
Pb: PointTrait<Scalar = Pa::Scalar>,
{
a.get::<0>() == b.get::<0>() && a.get::<1>() == b.get::<1>()
}
fn rings_equal<Ra, Rb>(a: &Ra, b: &Rb) -> bool
where
Ra: RingTrait,
Rb: RingTrait,
Ra::Point: PointTrait,
Rb::Point: PointTrait<Scalar = <Ra::Point as PointTrait>::Scalar>,
{
let av = normalise_ring(a);
let bv = normalise_ring(b);
if av.len() != bv.len() {
return false;
}
let n = av.len();
if n == 0 {
return true;
}
for start in 0..n {
if cyclic_match(&av, &bv, start, false) {
return true;
}
if cyclic_match(&av, &bv, start, true) {
return true;
}
}
false
}
fn normalise_ring<R>(r: &R) -> alloc::vec::Vec<&R::Point>
where
R: RingTrait,
R::Point: PointTrait,
{
let mut pts: alloc::vec::Vec<&R::Point> = r.points().collect();
if pts.len() >= 2 && point_eq_2d(pts[0], pts[pts.len() - 1]) {
pts.pop();
}
pts
}
fn cyclic_match<Pa, Pb>(a: &[&Pa], b: &[&Pb], start: usize, reverse: bool) -> bool
where
Pa: PointTrait,
Pb: PointTrait<Scalar = Pa::Scalar>,
{
let n = a.len();
for (i, ai) in a.iter().enumerate() {
let j = if reverse {
(start + n - i) % n
} else {
(start + i) % n
};
if !point_eq_2d(*ai, b[j]) {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::{EqPointPoint, EqPolygonPolygon, EqSegmentSegment, EqualsStrategy};
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Polygon, Segment, polygon};
type P = Point2D<f64, Cartesian>;
fn pt(x: f64, y: f64) -> P {
Point2D::new(x, y)
}
#[test]
fn equals_same_point() {
assert!(EqPointPoint.equals(&pt(1.0, 2.0), &pt(1.0, 2.0)));
assert!(!EqPointPoint.equals(&pt(1.0, 2.0), &pt(1.0, 2.1)));
}
#[test]
fn equals_segment_either_direction() {
let a = Segment::new(pt(0.0, 0.0), pt(1.0, 1.0));
let b = Segment::new(pt(1.0, 1.0), pt(0.0, 0.0));
assert!(EqSegmentSegment.equals(&a, &b));
let c = Segment::new(pt(0.0, 0.0), pt(1.0, 2.0));
assert!(!EqSegmentSegment.equals(&a, &c));
}
#[test]
fn equals_polygon_rotated_start() {
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![[(4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0), (4.0, 0.0)]];
assert!(EqPolygonPolygon.equals(&a, &b));
}
#[test]
fn equals_polygon_reversed_direction() {
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![[(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0), (0.0, 0.0)]];
assert!(EqPolygonPolygon.equals(&a, &b));
}
#[test]
fn polygon_not_equals_different_shape() {
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![[(0.0, 0.0), (5.0, 0.0), (5.0, 5.0), (0.0, 5.0), (0.0, 0.0)]];
assert!(!EqPolygonPolygon.equals(&a, &b));
}
fn _accepts_readonly_point<A, B, S>(s: &S, a: &A, b: &B) -> bool
where
A: geometry_trait::Point,
B: geometry_trait::Point,
S: EqualsStrategy<A, B>,
{
s.equals(a, b)
}
#[test]
#[allow(
clippy::used_underscore_items,
reason = "the test exists to run the compile-time witness's body"
)]
fn readonly_witness_computes_equality() {
assert!(_accepts_readonly_point(
&EqPointPoint,
&pt(1.0, 1.0),
&pt(1.0, 1.0)
));
assert!(!_accepts_readonly_point(
&EqPointPoint,
&pt(1.0, 1.0),
&pt(2.0, 2.0)
));
}
}