1use crate::geometry::{Centroid, Geometry};
6use crate::rectangle::Rectangle;
7use crate::{Point, Point2D, Vector, Vector2D};
8use irox_tools::FloatIsh;
9
10#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
11pub struct LineSegment<T: FloatIsh> {
12 pub start: Point<T>,
13 pub end: Point<T>,
14}
15
16impl<T: FloatIsh> LineSegment<T> {
17 pub fn slope(&self) -> Option<T> {
18 let dx = self.end.x - self.start.x;
19 if dx == T::ZERO {
20 return None;
21 }
22 Some((self.end.y - self.start.y) / dx)
23 }
24
25 pub fn intercept(&self) -> T {
26 let Some(slope) = self.slope() else {
27 return self.start.x;
28 };
29 self.start.y - slope * self.start.x
30 }
31
32 pub fn intersect(&self, other: &Self) -> Option<Point<T>> {
33 let m1 = self.slope();
34 let m2 = other.slope();
35 let b1 = self.intercept();
36 let b2 = other.intercept();
37
38 match (m1, m2) {
39 (Some(m1), Some(m2)) => {
40 let x = (b2 - b1) / (m2 - m1);
41 let y = m1 * x + b1;
42 Some(Point::new_point(x, y))
43 }
44 (Some(m1), None) => {
45 let x = other.start.x;
46 let y = m1 * x + b1;
47 Some(Point::new_point(x, y))
48 }
49 (None, Some(m2)) => {
50 let x = self.start.x;
51 let y = m2 * x + b1;
52 Some(Point::new_point(x, y))
53 }
54 (None, None) => None,
55 }
56 }
57
58 pub fn is_clockwise(&self, point: &Point<T>) -> bool {
59 let dx = self.end.x - self.start.x;
60 let dy = self.end.y - self.start.y;
61 let px = point.x - self.start.x;
62 let py = point.y - self.start.y;
63 (dx * py - dy * px) <= T::ZERO
64 }
65
66 pub fn length(&self) -> T {
67 let dx = self.end.x - self.start.x;
68 let dy = self.end.y - self.start.y;
69 (dx * dx + dy * dy).sqrt()
70 }
71
72 pub fn point_along_length(&self, pct: T) -> Point<T> {
73 let dx = self.end.x - self.start.x;
74 let dy = self.end.y - self.start.y;
75 let proj = Vector::new(dx * pct, dy * pct);
76 self.start + proj
77 }
78
79 pub fn distance_to(&self, point: &Point<T>) -> T {
80 let dx = self.end.x - self.start.x;
81 let dy = self.end.y - self.start.y;
82 let px = point.x - self.start.x;
83 let py = point.y - self.start.y;
84 let len = self.length();
85 let pct = ((px * dx) + (py * dy)) / (len * len);
86 let pct = pct.clamp(T::ZERO, T::ONE);
87 let point_on_segment = self.point_along_length(pct);
88 let v = *point - point_on_segment;
89 v.magnitude()
90 }
91}
92
93impl<T: FloatIsh> Centroid<T> for LineSegment<T> {
94 fn centroid(&self) -> Point<T> {
95 self.point_along_length(T::from_f64(0.5))
96 }
97}
98
99impl<T: FloatIsh> Geometry<T> for LineSegment<T> {
100 fn contains(&self, _point: &Point<T>) -> bool {
101 todo!()
102 }
103
104 fn distance_to(&self, point: &Point<T>) -> T {
105 LineSegment::distance_to(self, point)
106 }
107
108 fn intersects(&self, _point: &Point<T>) -> bool {
109 todo!()
110 }
111
112 fn bounding_rectangle(&self) -> Rectangle<T> {
113 todo!()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::{LineSegment, Point, Point2D};
120 use irox_tools::assert_eq_eps;
121
122 #[test]
123 pub fn test1() {
124 let line = LineSegment {
125 start: Point::new_point(5., 5.),
126 end: Point::new_point(5., 20.),
127 };
128 assert_eq!(None, line.slope());
129 assert!(line.is_clockwise(&Point::new_point(5., 10.)));
130 }
131 #[test]
132 pub fn test2() {
133 let line = LineSegment {
134 start: Point::new_point(1., 1.),
135 end: Point::new_point(10., 10.),
136 };
137 assert_eq!(Some(1.), line.slope());
138 assert!(!line.is_clockwise(&Point::new_point(5., 10.)));
139 assert!(line.is_clockwise(&Point::new_point(5., 5.)));
140 assert!(line.is_clockwise(&Point::new_point(10., 5.)));
141 }
142
143 #[test]
144 pub fn test_distance() {
145 let line = LineSegment {
146 start: Point::new_point(50., 80.),
147 end: Point::new_point(50., -800.),
148 };
149 let d = line.distance_to(&Point::new_point(20., 1000.));
150 assert_eq_eps!(920.4890004280828f64, d, f64::EPSILON);
151
152 let line = LineSegment {
153 start: Point::new_point(0., 0.),
154 end: Point::new_point(10., 0.),
155 };
156 let pnt = line.point_along_length(0.5);
157 assert_eq!(pnt, Point::new_point(5.0, 0.0));
158 let d = line.distance_to(&Point::new_point(0., 10.));
159 assert_eq_eps!(10f64, d, 1e-13);
160 let d = line.distance_to(&Point::new_point(5., 10.));
161 assert_eq_eps!(10f64, d, 1e-13);
162 let d = line.distance_to(&Point::new_point(10., 10.));
163 assert_eq_eps!(10f64, d, 1e-13);
164 let d = line.distance_to(&Point::new_point(10., -10.));
165 assert_eq_eps!(10f64, d, 1e-13);
166 let d = line.distance_to(&Point::new_point(5., -10.));
167 assert_eq_eps!(10f64, d, 1e-13);
168 let d = line.distance_to(&Point::new_point(0., -10.));
169 assert_eq_eps!(10f64, d, 1e-13);
170 let d = line.distance_to(&Point::new_point(-10., 0.));
171 assert_eq_eps!(10f64, d, 1e-13);
172 let d = line.distance_to(&Point::new_point(20., 0.));
173 assert_eq_eps!(10f64, d, 1e-13);
174 }
175}