fart_2d_geom/line.rs
1use crate::area2;
2use euclid::{point2, Point2D};
3use fart_aabb::{Aabb, ToAabb};
4use fart_utils::NoMorePartial;
5use num_traits::{Num, NumCast};
6use partial_min_max::{max, min};
7use std::cmp::Ordering;
8
9/// A line between two points.
10#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
11pub struct Line<T, U> {
12 /// The first point.
13 pub a: Point2D<T, U>,
14 /// The second point.
15 pub b: Point2D<T, U>,
16}
17
18/// The direction a point lies relative to a line. Returned by
19/// `Line::relative_direction_of`.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum RelativeDirection {
22 /// The point lies left relative to the line.
23 Left = 1,
24
25 /// The point is collinear with the line.
26 Collinear = 0,
27
28 /// The point lies right relative to the line.
29 Right = -1,
30}
31
32/// Convenience function for creating lines.
33#[inline]
34pub fn line<T, U>(a: Point2D<T, U>, b: Point2D<T, U>) -> Line<T, U> {
35 Line { a, b }
36}
37
38impl<T, U> Line<T, U>
39where
40 T: Copy + Num + PartialOrd,
41{
42 /// Create a new line between the given points.
43 #[inline]
44 pub fn new(a: Point2D<T, U>, b: Point2D<T, U>) -> Line<T, U> {
45 line(a, b)
46 }
47
48 /// Get the direction of the point relative to this line.
49 #[inline]
50 pub fn relative_direction_of(&self, point: Point2D<T, U>) -> RelativeDirection {
51 let zero = NoMorePartial(T::zero());
52 let det = NoMorePartial(area2(self.a, self.b, point));
53 match det.cmp(&zero) {
54 Ordering::Greater => RelativeDirection::Left,
55 Ordering::Equal => RelativeDirection::Collinear,
56 Ordering::Less => RelativeDirection::Right,
57 }
58 }
59
60 /// Is the given point on the left of this line?
61 ///
62 /// ```
63 /// use euclid::{point2, UnknownUnit};
64 /// use fart_2d_geom::{line, Line};
65 ///
66 /// let l: Line<i32, UnknownUnit> = line(point2(0, 0), point2(1, 1));
67 ///
68 /// assert!(l.is_left(point2(0, 1)));
69 /// assert!(!l.is_left(point2(1, 0)));
70 ///
71 /// // Collinear points are not considered on the left of the line. See
72 /// // also `is_left_or_collinear`.
73 /// assert!(!l.is_left(point2(2, 2)));
74 /// ```
75 #[inline]
76 pub fn is_left(&self, point: Point2D<T, U>) -> bool {
77 self.relative_direction_of(point) == RelativeDirection::Left
78 }
79
80 /// Is the given point on the left of this line or collinear with it?
81 ///
82 /// ```
83 /// use euclid::{point2, UnknownUnit};
84 /// use fart_2d_geom::{line, Line};
85 ///
86 /// let l: Line<i32, UnknownUnit> = line(point2(0, 0), point2(1, 1));
87 ///
88 /// assert!(l.is_left_or_collinear(point2(0, 1)));
89 /// assert!(l.is_left_or_collinear(point2(2, 2)));
90 ///
91 /// assert!(!l.is_left_or_collinear(point2(1, 0)));
92 /// ```
93 #[inline]
94 pub fn is_left_or_collinear(&self, point: Point2D<T, U>) -> bool {
95 match self.relative_direction_of(point) {
96 RelativeDirection::Left | RelativeDirection::Collinear => true,
97 RelativeDirection::Right => false,
98 }
99 }
100
101 /// Is the given point collinear with this line?
102 ///
103 /// ```
104 /// use euclid::{point2, UnknownUnit};
105 /// use fart_2d_geom::{line, Line};
106 ///
107 /// let l: Line<i32, UnknownUnit> = line(point2(0, 0), point2(1, 1));
108 ///
109 /// assert!(l.is_collinear(point2(2, 2)));
110 ///
111 /// assert!(!l.is_collinear(point2(0, 1)));
112 /// assert!(!l.is_collinear(point2(1, 0)));
113 /// ```
114 #[inline]
115 pub fn is_collinear(&self, point: Point2D<T, U>) -> bool {
116 self.relative_direction_of(point) == RelativeDirection::Collinear
117 }
118
119 /// Is the given point on the right of this line?
120 ///
121 /// ```
122 /// use euclid::{point2, UnknownUnit};
123 /// use fart_2d_geom::{line, Line};
124 ///
125 /// let l: Line<i32, UnknownUnit> = line(point2(0, 0), point2(1, 1));
126 ///
127 /// assert!(l.is_right(point2(1, 0)));
128 /// assert!(!l.is_right(point2(0, 1)));
129 ///
130 /// // Collinear points are not considered on the right of the line. See
131 /// // also `is_right_or_collinear`.
132 /// assert!(!l.is_right(point2(2, 2)));
133 /// ```
134 #[inline]
135 pub fn is_right(&self, point: Point2D<T, U>) -> bool {
136 self.relative_direction_of(point) == RelativeDirection::Right
137 }
138
139 /// Is the given point on the right of this line or collinear with it?
140 ///
141 /// ```
142 /// use euclid::{point2, UnknownUnit};
143 /// use fart_2d_geom::{line, Line};
144 ///
145 /// let l: Line<i32, UnknownUnit> = line(point2(0, 0), point2(1, 1));
146 ///
147 /// assert!(l.is_right_or_collinear(point2(1, 0)));
148 /// assert!(l.is_right_or_collinear(point2(2, 2)));
149 ///
150 /// assert!(!l.is_right_or_collinear(point2(0, 1)));
151 /// ```
152 #[inline]
153 pub fn is_right_or_collinear(&self, point: Point2D<T, U>) -> bool {
154 match self.relative_direction_of(point) {
155 RelativeDirection::Right | RelativeDirection::Collinear => true,
156 RelativeDirection::Left => false,
157 }
158 }
159
160 /// Is the given point on this line segment? That is, not just collinear,
161 /// but also between `self.a` and `self.b`?
162 ///
163 /// ```
164 /// use euclid::{point2, UnknownUnit};
165 /// use fart_2d_geom::{line, Line};
166 ///
167 /// let l: Line<i32, UnknownUnit> = line(point2(0, 0), point2(2, 2));
168 ///
169 /// assert!(l.is_on(point2(1, 1)));
170 ///
171 /// assert!(!l.is_on(point2(0, 1)));
172 /// assert!(!l.is_on(point2(1, 0)));
173 ///
174 /// // Inclusive of the line segment's boundaries.
175 /// assert!(l.is_on(l.a));
176 /// assert!(l.is_on(l.b));
177 ///
178 /// // Does not include collinear-but-not-between points.
179 /// assert!(!l.is_on(point2(3, 3)));
180 /// ```
181 pub fn is_on(&self, point: Point2D<T, U>) -> bool {
182 if !self.is_collinear(point) {
183 return false;
184 }
185
186 // If this line segment is vertical, check that point.y is between a.y
187 // and b.y. Otherwise check that point.x is between a.x and b.x.
188 if self.a.x == self.b.x {
189 let min = min(self.a.y, self.b.y);
190 let max = max(self.a.y, self.b.y);
191 min <= point.y && point.y <= max
192 } else {
193 let min = min(self.a.x, self.b.x);
194 let max = max(self.a.x, self.b.x);
195 min <= point.x && point.x <= max
196 }
197 }
198
199 /// Does this line segment (properly) intersect with the other line segment?
200 ///
201 /// ```
202 /// use euclid::{point2, UnknownUnit};
203 /// use fart_2d_geom::{line, Line};
204 ///
205 /// assert!(
206 /// line::<i32, UnknownUnit>(point2(0, 0), point2(1, 1))
207 /// .intersects(&line(point2(0, 1), point2(1, 0)))
208 /// );
209 ///
210 /// assert!(
211 /// !line::<i32, UnknownUnit>(point2(0, 0), point2(1, 1))
212 /// .intersects(&line(point2(1, 0), point2(2, -1)))
213 /// );
214 ///
215 /// // If any end points from one line segment land on the other line
216 /// // segment, `false` is returned because that is not proper intersection.
217 /// assert!(
218 /// !line::<i32, UnknownUnit>(point2(0, 0), point2(2, 2))
219 /// .intersects(&line(point2(1, 1), point2(2, 0)))
220 /// );
221 /// ```
222 #[inline]
223 pub fn intersects(&self, other: &Line<T, U>) -> bool {
224 // If any points from a line segment are collinear with the other line
225 // segment, then they cannot properly intersect.
226 if self.is_collinear(other.a)
227 || self.is_collinear(other.b)
228 || other.is_collinear(self.a)
229 || other.is_collinear(self.b)
230 {
231 return false;
232 }
233
234 (self.is_left(other.a) ^ self.is_left(other.b))
235 && (other.is_left(self.a) ^ other.is_left(self.b))
236 }
237
238 /// Does this line segment improperly intersect with the other line segment?
239 ///
240 /// ```
241 /// use euclid::{point2, UnknownUnit};
242 /// use fart_2d_geom::{line, Line};
243 ///
244 /// assert!(
245 /// line::<i32, UnknownUnit>(point2(0, 0), point2(1, 1))
246 /// .improperly_intersects(&line(point2(0, 1), point2(1, 0)))
247 /// );
248 ///
249 /// assert!(
250 /// !line::<i32, UnknownUnit>(point2(0, 0), point2(1, 1))
251 /// .improperly_intersects(&line(point2(1, 0), point2(2, -1)))
252 /// );
253 ///
254 /// // If any end points from one line segment land on the other line
255 /// // segment, `true` is still returned.
256 /// assert!(
257 /// line::<i32, UnknownUnit>(point2(0, 0), point2(2, 2))
258 /// .improperly_intersects(&line(point2(1, 1), point2(2, 0)))
259 /// );
260 /// ```
261 pub fn improperly_intersects(&self, other: &Line<T, U>) -> bool {
262 self.intersects(other)
263 || self.is_on(other.a)
264 || self.is_on(other.b)
265 || other.is_on(self.a)
266 || other.is_on(self.b)
267 }
268}
269
270impl<T, U> Line<T, U>
271where
272 T: Copy + NumCast,
273{
274 /// Cast from number representation `T` to number representation `V`.
275 ///
276 /// ```
277 /// use euclid::{point2, UnknownUnit};
278 /// use fart_2d_geom::{line, Line};
279 ///
280 /// let l: Line<i64, UnknownUnit> = line(point2(1, 2), point2(3, 4));
281 ///
282 /// let m = l.cast::<f64>();
283 /// assert_eq!(m, line(point2(1.0, 2.0), point2(3.0, 4.0)));
284 /// ```
285 #[inline]
286 pub fn cast<V>(&self) -> Line<V, U>
287 where
288 V: NumCast + Copy,
289 {
290 line(self.a.cast(), self.b.cast())
291 }
292}
293
294impl<T, U> Line<T, U>
295where
296 T: Copy + Num + PartialOrd + euclid::Trig,
297{
298 /// Transform this line with the given linear transformation and return the
299 /// new, transformed line.
300 ///
301 /// ```
302 /// use euclid::{point2, Transform2D, UnknownUnit};
303 /// use fart_2d_geom::{line, Line};
304 ///
305 /// let l: Line<_, UnknownUnit> = line(point2(3.0, 4.0), point2(0.0, 1.0));
306 ///
307 /// let scale = Transform2D::<_, _, UnknownUnit>::create_scale(0.5, 2.0);
308 ///
309 /// let m = l.transform(&scale);
310 ///
311 /// assert_eq!(m, line(point2(1.5, 8.0), point2(0.0, 2.0)));
312 /// ```
313 pub fn transform<V>(&self, transformation: &euclid::Transform2D<T, U, V>) -> Line<T, V> {
314 let a = transformation.transform_point(self.a);
315 let b = transformation.transform_point(self.b);
316 line(a, b)
317 }
318
319 /// Transform this line in place with the given linear transformation.
320 ///
321 /// ```
322 /// use euclid::{point2, Transform2D, UnknownUnit};
323 /// use fart_2d_geom::{line, Line};
324 ///
325 /// let mut l: Line<_, UnknownUnit> = line(point2(3.0, 4.0), point2(0.0, 1.0));
326 ///
327 /// let scale = Transform2D::<_, _, UnknownUnit>::create_scale(0.5, 2.0);
328 ///
329 /// l.transform_in_place(&scale);
330 ///
331 /// assert_eq!(l, line(point2(1.5, 8.0), point2(0.0, 2.0)));
332 /// ```
333 pub fn transform_in_place(&mut self, transformation: &euclid::Transform2D<T, U, U>) {
334 self.a = transformation.transform_point(self.a);
335 self.b = transformation.transform_point(self.b);
336 }
337}
338
339impl<U> Line<f64, U> {
340 /// Get the intersection between two line segments.
341 ///
342 /// The kind of intersection is broken down by whether it is proper,
343 /// improper, or collinear. If you don't care what kind of intersection it
344 /// is, use `LineIntersection::point` to just get the point of intersection,
345 /// if any.
346 ///
347 /// ```
348 /// use euclid::{point2, UnknownUnit};
349 /// use fart_2d_geom::{Line, LineIntersection};
350 ///
351 /// let line = |a, b| -> Line<f64, UnknownUnit> {
352 /// Line::new(a, b)
353 /// };
354 ///
355 /// // No intersection.
356 /// assert_eq!(
357 /// line(
358 /// point2(0.0, 0.0),
359 /// point2(5.0, 5.0),
360 /// ).intersection(&line(
361 /// point2(0.0, 3.0),
362 /// point2(1.0, 3.0),
363 /// )),
364 /// LineIntersection::None,
365 /// );
366 ///
367 /// // Proper intersection.
368 /// assert_eq!(
369 /// line(
370 /// point2(0.0, 0.0),
371 /// point2(5.0, 5.0),
372 /// ).intersection(&line(
373 /// point2(0.0, 2.0),
374 /// point2(2.0, 0.0),
375 /// )),
376 /// LineIntersection::Proper(point2(1.0, 1.0)),
377 /// );
378 ///
379 /// // Improper intersection.
380 /// assert_eq!(
381 /// line(
382 /// point2(0.0, 0.0),
383 /// point2(5.0, 5.0),
384 /// ).intersection(&line(
385 /// point2(5.0, 5.0),
386 /// point2(2.0, 0.0),
387 /// )),
388 /// LineIntersection::Improper(point2(5.0, 5.0)),
389 /// );
390 ///
391 /// // Collinear intersection.
392 /// assert_eq!(
393 /// line(
394 /// point2(0.0, 0.0),
395 /// point2(5.0, 5.0),
396 /// ).intersection(&line(
397 /// point2(3.0, 3.0),
398 /// point2(7.0, 7.0),
399 /// )),
400 /// LineIntersection::Collinear(point2(3.0, 3.0)),
401 /// );
402 ///
403 /// // Don't care what kind, just give me the point!
404 /// assert_eq!(
405 /// line(
406 /// point2(0.0, 0.0),
407 /// point2(5.0, 5.0),
408 /// ).intersection(&line(
409 /// point2(0.0, 3.0),
410 /// point2(1.0, 3.0),
411 /// )).point(),
412 /// None,
413 /// );
414 /// assert_eq!(
415 /// line(
416 /// point2(0.0, 0.0),
417 /// point2(5.0, 5.0),
418 /// ).intersection(&line(
419 /// point2(0.0, 2.0),
420 /// point2(2.0, 0.0),
421 /// )).point(),
422 /// Some(point2(1.0, 1.0)),
423 /// );
424 /// ```
425 pub fn intersection(&self, other: &Line<f64, U>) -> LineIntersection<U> {
426 let denominator = self.a.x * (other.b.y - other.a.y)
427 + self.b.x * (other.a.y - other.b.y)
428 + other.b.x * (self.b.y - self.a.y)
429 + other.a.x * (self.a.y - self.b.y);
430
431 if denominator == 0.0 {
432 return self.parallel_intersection(other);
433 }
434
435 let numerator = self.a.x * (other.b.y - other.a.y)
436 + other.a.x * (self.a.y - other.b.y)
437 + other.b.x * (other.a.y - self.a.y);
438
439 let s = numerator / denominator;
440
441 let numerator = -(self.a.x * (other.a.y - self.b.y)
442 + self.b.x * (self.a.y - other.a.y)
443 + other.a.x * (self.b.y - self.a.y));
444
445 let t = numerator / denominator;
446
447 let p = self.a.lerp(self.b, s);
448
449 if numerator == 0.0 || numerator == denominator {
450 LineIntersection::Improper(p)
451 } else if 0.0 < s && s < 1.0 && 0.0 < t && t < 1.0 {
452 LineIntersection::Proper(p)
453 } else {
454 LineIntersection::None
455 }
456 }
457
458 fn parallel_intersection(&self, other: &Line<f64, U>) -> LineIntersection<U> {
459 let between = |l: &Self, p: euclid::Point2D<f64, U>| {
460 if l.a.x != l.b.x {
461 (l.a.x <= p.x && p.x <= l.b.x) || (l.b.x <= p.x && p.x <= l.a.x)
462 } else {
463 (l.a.y <= p.y && p.y <= l.b.y) || (l.b.y <= p.y && p.y <= l.a.y)
464 }
465 };
466
467 if !self.is_collinear(other.a) {
468 LineIntersection::None
469 } else if between(self, other.a) {
470 LineIntersection::Collinear(other.a)
471 } else if between(self, other.b) {
472 LineIntersection::Collinear(other.a)
473 } else if between(other, self.a) {
474 LineIntersection::Collinear(self.a)
475 } else if between(other, self.b) {
476 LineIntersection::Collinear(self.b)
477 } else {
478 LineIntersection::None
479 }
480 }
481}
482
483/// The result of `Line::intersection` providing the intersection point between
484/// two line segments, if any.
485#[derive(Copy, Clone, Debug, PartialEq)]
486pub enum LineIntersection<U> {
487 /// The line segments do not intersect.
488 None,
489
490 /// The line segments properly intersect at the given point, and are not
491 /// collinear.
492 Proper(euclid::Point2D<f64, U>),
493
494 /// The line segments improperly intersect and are not collinear, with the
495 /// endpoint of one line segment landing on the other.
496 Improper(euclid::Point2D<f64, U>),
497
498 /// The lines are collinear and intersect at the given point (and perhaps
499 /// infinitely many other points as well).
500 Collinear(euclid::Point2D<f64, U>),
501}
502
503impl<U> LineIntersection<U> {
504 /// Is this a `LineIntersection::None`?
505 #[inline]
506 pub fn is_none(&self) -> bool {
507 match self {
508 LineIntersection::None => true,
509 _ => false,
510 }
511 }
512
513 /// Is this a `LineIntersection::Proper`?
514 #[inline]
515 pub fn is_proper(&self) -> bool {
516 match self {
517 LineIntersection::Proper(_) => true,
518 _ => false,
519 }
520 }
521
522 /// Is this a `LineIntersection::Improper`?
523 #[inline]
524 pub fn is_improper(&self) -> bool {
525 match self {
526 LineIntersection::Improper(_) => true,
527 _ => false,
528 }
529 }
530
531 /// Is this a `LineIntersection::Collinear`?
532 #[inline]
533 pub fn is_collinear(&self) -> bool {
534 match self {
535 LineIntersection::Collinear(_) => true,
536 _ => false,
537 }
538 }
539
540 /// Get the intersection point, if any, regardless if this is a proper,
541 /// improper, or collinear intersection.
542 #[inline]
543 pub fn point(&self) -> Option<euclid::Point2D<f64, U>> {
544 match *self {
545 LineIntersection::None => None,
546 LineIntersection::Proper(p)
547 | LineIntersection::Improper(p)
548 | LineIntersection::Collinear(p) => Some(p),
549 }
550 }
551}
552
553impl<T, U> ToAabb<T, U> for Line<T, U>
554where
555 T: Copy + Num + PartialOrd,
556{
557 fn to_aabb(&self) -> Aabb<T, U> {
558 let min = point2(min(self.a.x, self.b.x), min(self.a.y, self.b.y));
559 let max = point2(max(self.a.x, self.b.x), max(self.a.y, self.b.y));
560 Aabb::new(min, max)
561 }
562}