1use geometry_coords::CoordinateScalar;
35use geometry_cs::{CartesianFamily, CoordinateSystem};
36use geometry_tag::{PointTag, PolygonTag, SameAs, SegmentTag};
37use geometry_trait::{
38 Point as PointTrait, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
39 Segment as SegmentTrait, segment_end, segment_start,
40};
41
42pub trait EqualsStrategy<A, B> {
48 fn equals(&self, a: &A, b: &B) -> bool;
50}
51
52#[derive(Debug, Default, Clone, Copy)]
54pub struct EqPointPoint;
55#[derive(Debug, Default, Clone, Copy)]
57pub struct EqSegmentSegment;
58#[derive(Debug, Default, Clone, Copy)]
60pub struct EqPolygonPolygon;
61
62impl<A, B> EqualsStrategy<A, B> for EqPointPoint
68where
69 A: PointTrait,
70 B: PointTrait<Scalar = A::Scalar>,
71 <A::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
72{
73 #[inline]
74 fn equals(&self, a: &A, b: &B) -> bool {
75 let mut i = 0;
76 while i < A::DIM {
77 let eq = match i {
78 0 => a.get::<0>() == b.get::<0>(),
79 1 => a.get::<1>() == b.get::<1>(),
80 2 => a.get::<2>() == b.get::<2>(),
81 3 => a.get::<3>() == b.get::<3>(),
82 _ => panic!("CartesianEquals: dimension exceeds MAX_DIM (4)"),
83 };
84 if !eq {
85 return false;
86 }
87 i += 1;
88 }
89 true
90 }
91}
92
93impl<A, B, P> EqualsStrategy<A, B> for EqSegmentSegment
100where
101 A: SegmentTrait<Point = P>,
102 B: SegmentTrait<Point = P>,
103 P: PointTrait + PointMut + Default,
104 P::Scalar: CoordinateScalar,
105 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
106{
107 #[inline]
108 fn equals(&self, a: &A, b: &B) -> bool {
109 let (a1, a2) = (segment_start(a), segment_end(a));
110 let (b1, b2) = (segment_start(b), segment_end(b));
111 (point_eq_2d(&a1, &b1) && point_eq_2d(&a2, &b2))
112 || (point_eq_2d(&a1, &b2) && point_eq_2d(&a2, &b1))
113 }
114}
115
116impl<A, B, P> EqualsStrategy<A, B> for EqPolygonPolygon
125where
126 A: PolygonTrait<Point = P>,
127 B: PolygonTrait<Point = P, Ring = A::Ring>,
132 P: PointTrait,
133 P::Scalar: CoordinateScalar,
134 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
135{
136 fn equals(&self, a: &A, b: &B) -> bool {
137 if !rings_equal(a.exterior(), b.exterior()) {
138 return false;
139 }
140 if a.interiors().count() != b.interiors().count() {
141 return false;
142 }
143 let bh: alloc::vec::Vec<&B::Ring> = b.interiors().collect();
147 let mut matched = alloc::vec![false; bh.len()];
148 for ha in a.interiors() {
149 let mut found = false;
150 for (j, hb) in bh.iter().enumerate() {
151 if !matched[j] && rings_equal(ha, *hb) {
152 matched[j] = true;
153 found = true;
154 break;
155 }
156 }
157 if !found {
158 return false;
159 }
160 }
161 true
162 }
163}
164
165#[doc(hidden)]
172pub trait EqualsPairStrategy<K2> {
173 type S: Default;
176}
177
178impl EqualsPairStrategy<PointTag> for PointTag {
179 type S = EqPointPoint;
180}
181impl EqualsPairStrategy<SegmentTag> for SegmentTag {
182 type S = EqSegmentSegment;
183}
184impl EqualsPairStrategy<PolygonTag> for PolygonTag {
185 type S = EqPolygonPolygon;
186}
187
188extern crate alloc;
189
190#[inline]
193fn point_eq_2d<Pa, Pb>(a: &Pa, b: &Pb) -> bool
194where
195 Pa: PointTrait,
196 Pb: PointTrait<Scalar = Pa::Scalar>,
197{
198 a.get::<0>() == b.get::<0>() && a.get::<1>() == b.get::<1>()
199}
200
201fn rings_equal<Ra, Rb>(a: &Ra, b: &Rb) -> bool
208where
209 Ra: RingTrait,
210 Rb: RingTrait,
211 Ra::Point: PointTrait,
212 Rb::Point: PointTrait<Scalar = <Ra::Point as PointTrait>::Scalar>,
213{
214 let av = normalise_ring(a);
215 let bv = normalise_ring(b);
216 if av.len() != bv.len() {
217 return false;
218 }
219 let n = av.len();
220 if n == 0 {
221 return true;
222 }
223 for start in 0..n {
225 if cyclic_match(&av, &bv, start, false) {
226 return true;
227 }
228 if cyclic_match(&av, &bv, start, true) {
229 return true;
230 }
231 }
232 false
233}
234
235fn normalise_ring<R>(r: &R) -> alloc::vec::Vec<&R::Point>
238where
239 R: RingTrait,
240 R::Point: PointTrait,
241{
242 let mut pts: alloc::vec::Vec<&R::Point> = r.points().collect();
243 if pts.len() >= 2 && point_eq_2d(pts[0], pts[pts.len() - 1]) {
244 pts.pop();
245 }
246 pts
247}
248
249fn cyclic_match<Pa, Pb>(a: &[&Pa], b: &[&Pb], start: usize, reverse: bool) -> bool
252where
253 Pa: PointTrait,
254 Pb: PointTrait<Scalar = Pa::Scalar>,
255{
256 let n = a.len();
257 for (i, ai) in a.iter().enumerate() {
258 let j = if reverse {
259 (start + n - i) % n
260 } else {
261 (start + i) % n
262 };
263 if !point_eq_2d(*ai, b[j]) {
264 return false;
265 }
266 }
267 true
268}
269
270#[cfg(test)]
271mod tests {
272 use super::{EqPointPoint, EqPolygonPolygon, EqSegmentSegment, EqualsStrategy};
273 use geometry_cs::Cartesian;
274 use geometry_model::{Point2D, Polygon, Segment, polygon};
275
276 type P = Point2D<f64, Cartesian>;
277
278 fn pt(x: f64, y: f64) -> P {
279 Point2D::new(x, y)
280 }
281
282 #[test]
283 fn equals_same_point() {
284 assert!(EqPointPoint.equals(&pt(1.0, 2.0), &pt(1.0, 2.0)));
285 assert!(!EqPointPoint.equals(&pt(1.0, 2.0), &pt(1.0, 2.1)));
286 }
287
288 #[test]
289 fn equals_segment_either_direction() {
290 let a = Segment::new(pt(0.0, 0.0), pt(1.0, 1.0));
291 let b = Segment::new(pt(1.0, 1.0), pt(0.0, 0.0));
292 assert!(EqSegmentSegment.equals(&a, &b));
293 let c = Segment::new(pt(0.0, 0.0), pt(1.0, 2.0));
294 assert!(!EqSegmentSegment.equals(&a, &c));
295 }
296
297 #[test]
298 fn equals_polygon_rotated_start() {
299 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)]];
300 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)]];
302 assert!(EqPolygonPolygon.equals(&a, &b));
303 }
304
305 #[test]
306 fn equals_polygon_reversed_direction() {
307 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)]];
308 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)]];
309 assert!(EqPolygonPolygon.equals(&a, &b));
310 }
311
312 #[test]
313 fn polygon_not_equals_different_shape() {
314 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)]];
315 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)]];
316 assert!(!EqPolygonPolygon.equals(&a, &b));
317 }
318
319 fn _accepts_readonly_point<A, B, S>(s: &S, a: &A, b: &B) -> bool
323 where
324 A: geometry_trait::Point,
325 B: geometry_trait::Point,
326 S: EqualsStrategy<A, B>,
327 {
328 s.equals(a, b)
329 }
330
331 #[test]
334 #[allow(
335 clippy::used_underscore_items,
336 reason = "the test exists to run the compile-time witness's body"
337 )]
338 fn readonly_witness_computes_equality() {
339 assert!(_accepts_readonly_point(
340 &EqPointPoint,
341 &pt(1.0, 1.0),
342 &pt(1.0, 1.0)
343 ));
344 assert!(!_accepts_readonly_point(
345 &EqPointPoint,
346 &pt(1.0, 1.0),
347 &pt(2.0, 2.0)
348 ));
349 }
350}