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}