galileo_types/
segment.rs

1use num_traits::{One, Zero};
2
3use crate::cartesian::{CartesianPoint2d, Orientation};
4
5/// A strait line segment between two points.
6#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7pub struct Segment<Point>(pub Point, pub Point);
8
9impl<P: CartesianPoint2d> Segment<P> {
10    /// Shortest euclidean distance (squared) between a point and the segment:
11    ///
12    /// * if the normal from the point to the segment ends inside the segment, the returned value is the squared length
13    ///   of the normal
14    /// * if the normal from the point to the segment ends outside of the segment, the returned value is the smaller one
15    ///   of the distances between the point and the segment's endpoints
16    pub fn distance_to_point_sq<Point: CartesianPoint2d<Num = P::Num>>(
17        &self,
18        point: &Point,
19    ) -> P::Num {
20        if self.0.equal(&self.1) {
21            return self.0.distance_sq(point);
22        }
23
24        let ds = self.1.sub(&self.0);
25        let dp = point.sub(&self.0);
26        let ds_len = ds.magnitude_sq();
27
28        let r = dp.magnitude_sq() / ds_len;
29        if r <= P::Num::zero() {
30            self.0.distance_sq(point)
31        } else if r >= P::Num::one() {
32            self.1.distance_sq(point)
33        } else {
34            let s = (dp.dy() * ds.dx() - dp.dx() * ds.dy()) / ds_len;
35            (s * s) * ds_len
36        }
37    }
38
39    /// Returns true, if the segment has at least one common point with the `other` segment.
40    pub fn intersects<Point: CartesianPoint2d<Num = P::Num>>(
41        &self,
42        other: &Segment<Point>,
43    ) -> bool {
44        fn on_segment<Num: num_traits::Num + PartialOrd>(
45            p: &impl CartesianPoint2d<Num = Num>,
46            q: &impl CartesianPoint2d<Num = Num>,
47            r: &impl CartesianPoint2d<Num = Num>,
48        ) -> bool {
49            let x_max = if p.x() >= r.x() { p.x() } else { r.x() };
50            let x_min = if p.x() <= r.x() { p.x() } else { r.x() };
51            let y_max = if p.y() >= r.y() { p.x() } else { r.x() };
52            let y_min = if p.y() <= r.y() { p.x() } else { r.x() };
53
54            q.x() <= x_max && q.x() >= x_min && q.y() <= y_max && q.y() >= y_min
55        }
56
57        let o1 = Orientation::triplet(&self.0, &other.0, &self.1);
58        let o2 = Orientation::triplet(&self.0, &other.1, &self.1);
59        let o3 = Orientation::triplet(&other.0, &self.0, &other.1);
60        let o4 = Orientation::triplet(&other.0, &self.1, &other.1);
61
62        if o1 != o2 && o3 != o4 {
63            return true;
64        }
65
66        if o1 == Orientation::Collinear && on_segment(&self.0, &other.0, &self.1) {
67            return true;
68        }
69        if o2 == Orientation::Collinear && on_segment(&self.0, &other.1, &self.1) {
70            return true;
71        }
72        if o3 == Orientation::Collinear && on_segment(&other.0, &self.0, &other.1) {
73            return true;
74        }
75        if o4 == Orientation::Collinear && on_segment(&other.0, &self.1, &other.1) {
76            return true;
77        }
78
79        false
80    }
81}