iron_shapes/
edge_integer.rs

1// Copyright (c) 2018-2020 Thomas Kramer.
2// SPDX-FileCopyrightText: 2018-2022 Thomas Kramer
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! Edge intersection functions for integer coordinates.
7
8pub use crate::edge::{Edge, EdgeIntersection, LineIntersection};
9use crate::point::Point;
10use crate::redge::{REdge, REdgeIntersection};
11use std::cmp::Ordering;
12
13use crate::CoordinateType;
14
15use crate::traits::BoundingBox;
16use num_traits::{PrimInt, Zero};
17use std::convert::TryFrom;
18use std::fmt::Debug;
19
20impl<T: CoordinateType + PrimInt + Debug> Edge<T> {
21    /// Compute the intersection point of the lines defined by the two edges.
22    /// Coordinates of intersection points are rounded towards zero.
23    ///
24    /// Degenerate lines don't intersect by definition.
25    ///
26    /// Returns `LineIntersection::None` iff the two lines don't intersect.
27    /// Returns `LineIntersection::Collinear` iff both lines are equal.
28    /// Returns `LineIntersection::Point(p,(a,b,c))` iff the lines intersect in exactly one point `p`.
29    /// `f` is a value such that `self.start + self.vector()*a/c == p` and
30    /// `other.start + other.vector()*b/c == p`.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use iron_shapes::point::Point;
36    /// use iron_shapes::edge::*;
37    ///
38    /// let e1 = Edge::new((0, 0), (2, 2));
39    /// let e2 = Edge::new((0, 2), (2, 0));
40    ///
41    /// assert_eq!(e1.line_intersection_rounded(e2),
42    ///     LineIntersection::Point(Point::new(1, 1), (4, 4, 8)));
43    ///
44    /// ```
45    pub fn line_intersection_rounded(&self, other: Edge<T>) -> LineIntersection<T, T> {
46        if self.is_degenerate() || other.is_degenerate() {
47            LineIntersection::None
48        } else {
49            // TODO: faster implementation if both lines are orthogonal
50
51            let ab = self.vector();
52            let cd = other.vector();
53
54            // Assert that the vectors have a non-zero length. This should already be the case
55            // because the degenerate cases are handled before.
56            debug_assert!(!ab.is_zero());
57            debug_assert!(!cd.is_zero());
58
59            // if ab.x.is_zero() {
60            //     // Self is vertical.
61            //     if cd.y.is_zero() {
62            //         // Lines are orthogonal.
63            //         // Get intersection point.
64            //         let p = Point::new(self.start.x, other.start.y);
65            //         unimplemented!()
66            //         // TODO:
67            //     } else if cd.x.is_zero() {
68            //         // Lines are parallel.
69            //         return if self.x == other.x {
70            //             // Lines are collinear.
71            //             LineIntersection::Collinear
72            //         } else {
73            //             LineIntersection::None
74            //         }
75            //     }
76            // } else if ab.y.is_zero() {
77            //     if cd.x.is_zero() {
78            //         // Lines are orthogonal.
79            //         // Get intersection point.
80            //         let p = Point::new(other.start.x, start.start.y);
81            //         unimplemented!()
82            //         // TODO:
83            //     } else if cd.y.is_zero() {
84            //         // Lines are parallel.
85            //         return if self.y == other.y {
86            //             // Lines are collinear.
87            //             LineIntersection::Collinear
88            //         } else {
89            //             LineIntersection::None
90            //         }
91            //     }
92            // }
93
94            let s = ab.cross_prod(cd);
95
96            if s.is_zero() {
97                // Lines are parallel
98                debug_assert!(self.is_parallel(&other));
99
100                // TODO: check more efficiently for collinear lines.
101                if self.line_contains_point(other.start) {
102                    // If the line defined by `self` contains at least one point of `other` then they are equal.
103                    debug_assert!(self.is_collinear(&other));
104                    LineIntersection::Collinear
105                } else {
106                    LineIntersection::None
107                }
108            } else {
109                let ac = other.start - self.start;
110                let ac_cross_cd = ac.cross_prod(cd);
111
112                // let two = T::one() + T::one();
113                // let one_vector = Vector::new(T::one(), T::one());
114
115                // Compute exact solution but scaled by s.
116                let exact_scaled_s = self.start * s + ab * ac_cross_cd;
117
118                // Divide by s and round by truncating towards zero.
119                // Where the result of an integer division is negative it is truncated towards zero.
120                // let p: Point<T> = (exact_scaled_s * two + one_vector * s) / (s * two); // Round to next integer.
121                let p: Point<T> = exact_scaled_s / s;
122
123                // TODO: maybe remove computation of relative positions?
124                let ca_cross_ab = ac.cross_prod(ab);
125                debug_assert!({
126                    let exact_scaled_s = other.start * s + cd * ca_cross_ab;
127                    // let p2: Point<T> = (exact_scaled_s * two + one_vector * s) / (s * two);// Round to next integer.
128                    let p2: Point<T> = exact_scaled_s / s;
129
130                    p == p2
131                });
132
133                let positions = if s < T::zero() {
134                    (
135                        T::zero() - ac_cross_cd,
136                        T::zero() - ca_cross_ab,
137                        T::zero() - s,
138                    )
139                } else {
140                    (ac_cross_cd, ca_cross_ab, s)
141                };
142
143                LineIntersection::Point(p, positions)
144            }
145        }
146    }
147
148    /// Compute the intersection with another edge.
149    /// Coordinates of intersection points are rounded towards zero.
150    ///
151    /// `EdgeIntersection::EndPoint` is returned if and only if the intersection lies exactly on an end point.
152    pub fn edge_intersection_rounded(&self, other: &Edge<T>) -> EdgeIntersection<T, T, Edge<T>> {
153        // Swap direction of other edge such that both have the same direction.
154        let other = if (self.start < self.end) != (other.start < other.end) {
155            other.reversed()
156        } else {
157            *other
158        };
159        debug_assert_eq!(
160            self.start < self.end,
161            other.start < other.end,
162            "Edges should have the same orientation now."
163        );
164
165        // Try to convert the edges into rectilinear edges.
166        if let Ok(a) = REdge::try_from(self) {
167            if let Ok(b) = REdge::try_from(&other) {
168                return match a.edge_intersection(&b) {
169                    REdgeIntersection::None => EdgeIntersection::None,
170                    REdgeIntersection::EndPoint(p) => {
171                        debug_assert!(
172                            p == a.start() || p == a.end() || p == b.start() || p == b.end()
173                        );
174                        EdgeIntersection::EndPoint(p)
175                    }
176                    REdgeIntersection::Point(p) => EdgeIntersection::Point(p),
177                    REdgeIntersection::Overlap(e) => EdgeIntersection::Overlap(e.into()),
178                };
179            }
180        }
181
182        // Check endpoints for coincidence.
183        // This must be handled separately because equality of the intersection point and endpoints
184        // will not necessarily be detected due to rounding errors.
185        let same_start_start = self.start == other.start;
186        let same_start_end = self.start == other.end;
187
188        let same_end_start = self.end == other.start;
189        let same_end_end = self.end == other.end;
190
191        // Are the edges equal but not degenerate?
192        let fully_coincident =
193            (same_start_start & same_end_end) ^ (same_start_end & same_end_start);
194
195        let result = if self.is_degenerate() {
196            // First degenerate case
197            if other.contains_point(self.start).inclusive_bounds() {
198                EdgeIntersection::EndPoint(self.start)
199            } else {
200                EdgeIntersection::None
201            }
202        } else if other.is_degenerate() {
203            // Second degenerate case
204            if self.contains_point(other.start).inclusive_bounds() {
205                EdgeIntersection::EndPoint(other.start)
206            } else {
207                EdgeIntersection::None
208            }
209        } else if fully_coincident {
210            EdgeIntersection::Overlap(*self)
211        } else if !self.bounding_box().touches(&other.bounding_box()) {
212            // If bounding boxes do not touch, then intersection is impossible.
213            EdgeIntersection::None
214        } else {
215            // Compute the intersection of the lines defined by the two edges.
216            let line_intersection = self.line_intersection_rounded(other);
217
218            // Then check if the intersection point is on both edges
219            // or find the intersection if the edges overlap.
220            match line_intersection {
221                LineIntersection::None => EdgeIntersection::None,
222
223                LineIntersection::Point(p, (pos1, pos2, len)) => {
224                    if pos1 >= T::zero() && pos1 <= len && pos2 >= T::zero() && pos2 <= len {
225                        if pos1 == T::zero() || pos1 == len || pos2 == T::zero() || pos2 == len {
226                            EdgeIntersection::EndPoint(p)
227                        } else {
228                            EdgeIntersection::Point(p)
229                        }
230                    } else {
231                        EdgeIntersection::None
232                    }
233                }
234                LineIntersection::Collinear => {
235                    debug_assert!(self.is_collinear(&other));
236
237                    // Project all points of the two edges on the line defined by the first edge
238                    // (scaled by the length of the first edge).
239                    // This allows to calculate the interval of overlap in one dimension.
240
241                    let (pa, pb) = self.into();
242                    let (pc, pd) = other.into();
243
244                    let b = pb - pa;
245                    let c = pc - pa;
246                    let d = pd - pa;
247
248                    let dist_a = T::zero();
249                    let dist_b = b.dot(b);
250
251                    let dist_c = b.dot(c);
252                    let dist_d = b.dot(d);
253
254                    let start1 = (dist_a, pa);
255                    let end1 = (dist_b, pb);
256
257                    // Sort end points of other edge.
258                    let (start2, end2) = if dist_c < dist_d {
259                        ((dist_c, pc), (dist_d, pd))
260                    } else {
261                        ((dist_d, pd), (dist_c, pc))
262                    };
263
264                    // Find maximum by distance.
265                    let start = if start1.0 < start2.0 { start2 } else { start1 };
266
267                    // Find minimum by distance.
268                    let end = if end1.0 < end2.0 { end1 } else { end2 };
269
270                    // Check if the edges overlap in more than one point, in exactly one point or
271                    // in zero points.
272                    match start.0.cmp(&end.0) {
273                        Ordering::Less => EdgeIntersection::Overlap(Edge::new(start.1, end.1)),
274                        Ordering::Equal => EdgeIntersection::EndPoint(start.1),
275                        Ordering::Greater => EdgeIntersection::None,
276                    }
277                }
278            }
279        };
280
281        // Check that the result is consistent with the edge intersection test.
282        debug_assert_eq!(
283            result == EdgeIntersection::None,
284            self.edges_intersect(&other).is_no()
285        );
286
287        result
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_line_intersection_rounded() {
297        let e1 = Edge::new((0, 0), (2, 2));
298        let e2 = Edge::new((0, 2), (2, 0));
299        let intersection = e1.line_intersection_rounded(e2);
300        assert_eq!(
301            intersection,
302            LineIntersection::Point((1, 1).into(), (4, 4, 8))
303        );
304
305        let e1 = Edge::new((0, 1), (1, 1));
306        let e2 = Edge::new((0, 0), (1, 2));
307        let intersection = e1.line_intersection_rounded(e2);
308        match intersection {
309            LineIntersection::Point(p, _) => assert_eq!(p, Point::new(0, 1)),
310            _ => assert!(false),
311        }
312
313        let e1 = Edge::new((0, 1), (1, 1));
314        let e2 = Edge::new((0, 0), (1, 3));
315        let intersection = e1.line_intersection_rounded(e2);
316        match intersection {
317            LineIntersection::Point(p, _) => assert_eq!(p, Point::new(0, 1)),
318            _ => assert!(false),
319        }
320
321        // Test truncation towards zero.
322        let e1 = Edge::new((0i32, 0), (1, 0));
323        let e2 = Edge::new((0, 1), (12345677, -1));
324        let intersection = e1.line_intersection_rounded(e2);
325        match intersection {
326            LineIntersection::Point(p, _) => assert_eq!(p, Point::new(12345677 / 2, 0)),
327            _ => assert!(false),
328        }
329
330        // Test truncation towards zero.
331        let e1 = Edge::new((0i32, 0), (-1, 0));
332        let e2 = Edge::new((0, -1), (-12345677, 1));
333        let intersection = e1.line_intersection_rounded(e2);
334        match intersection {
335            LineIntersection::Point(p, _) => assert_eq!(p, Point::new(-12345677 / 2, 0)),
336            _ => assert!(false),
337        }
338    }
339
340    #[test]
341    fn test_edge_intersection_rounded() {
342        // Intersection inside the edges.
343        let e1 = Edge::new((-10, 0), (10, 0));
344        let e2 = Edge::new((-1, -1), (2, 2));
345        assert_eq!(
346            e1.edge_intersection_rounded(&e2),
347            EdgeIntersection::Point((0, 0).into())
348        );
349        assert_eq!(
350            e2.edge_intersection_rounded(&e1),
351            EdgeIntersection::Point((0, 0).into())
352        );
353
354        // Intersection on an endpoint.
355        let e1 = Edge::new((-10, 0), (10, 0));
356        let e2 = Edge::new((0, 0), (2, 2));
357        assert_eq!(
358            e1.edge_intersection_rounded(&e2),
359            EdgeIntersection::EndPoint((0, 0).into())
360        );
361        assert_eq!(
362            e2.edge_intersection_rounded(&e1),
363            EdgeIntersection::EndPoint((0, 0).into())
364        );
365
366        // Intersection on both endpoint.
367        let e1 = Edge::new((0, 0), (10, 0));
368        let e2 = Edge::new((0, 0), (2, 2));
369        assert_eq!(
370            e1.edge_intersection_rounded(&e2),
371            EdgeIntersection::EndPoint((0, 0).into())
372        );
373        assert_eq!(
374            e2.edge_intersection_rounded(&e1),
375            EdgeIntersection::EndPoint((0, 0).into())
376        );
377
378        // Intersection not on an endpoint but rounded down to an endpoint.
379        // TODO: Rethink what should happen here. EndPoint or Point?
380        let e1 = Edge::new((0, 0), (1, 0));
381        let e2 = Edge::new((0, -1), (1, 10));
382        assert_eq!(
383            e1.edge_intersection_rounded(&e2),
384            EdgeIntersection::Point((0, 0).into())
385        );
386        assert_eq!(
387            e2.edge_intersection_rounded(&e1),
388            EdgeIntersection::Point((0, 0).into())
389        );
390
391        // No intersection.
392        let e1 = Edge::new((-10, 0), (10, 0));
393        let e2 = Edge::new((1, 1), (2, 2));
394        assert_eq!(e1.edge_intersection_rounded(&e2), EdgeIntersection::None);
395        assert_eq!(e2.edge_intersection_rounded(&e1), EdgeIntersection::None);
396    }
397
398    #[test]
399    fn test_end_point_intersection_at_negative_x() {
400        let p = Point::new;
401
402        // Negative coordinates.
403        let e1 = Edge::new(p(-1, 2), p(0, 0));
404        let e2 = Edge::new(p(-1, 2), p(0, 2));
405
406        assert_eq!(
407            e1.edge_intersection_rounded(&e2),
408            EdgeIntersection::EndPoint(p(-1, 2))
409        );
410    }
411}