Skip to main content

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}