1use crate::geometry::{
2 check_dimensions_match, min_distance_point_line, Point, Region, Shape, Shapelike,
3 ShapelikeError,
4};
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct LineSegment {
8 start: Point,
9 end: Point,
10}
11
12impl LineSegment {
13 pub fn new(start: Point, end: Point) -> Self {
14 assert_eq!(start.get_dimension(), end.get_dimension());
16
17 Self { start, end }
18 }
19
20 #[inline(always)]
21 pub fn get_coordinate(&self, index: usize) -> (f64, f64) {
22 (
23 self.start.get_coordinate(index),
24 self.end.get_coordinate(index),
25 )
26 }
27
28 pub fn get_points(&self) -> (&Point, &Point) {
29 (&self.start, &self.end)
30 }
31
32 pub fn coordinate_iter(&self) -> impl Iterator<Item = (f64, f64)> + '_ {
33 self.start.coordinate_iter().zip(self.end.coordinate_iter())
34 }
35
36 #[inline(always)]
38 fn double_area_triangle(a: &Point, b: &Point, c: &Point) -> f64 {
39 ((b.get_coordinate(0) - a.get_coordinate(0)) * (c.get_coordinate(1) - a.get_coordinate(1)))
40 - ((c.get_coordinate(0) - a.get_coordinate(0))
41 * (b.get_coordinate(1) - a.get_coordinate(1)))
42 }
43
44 #[inline(always)]
46 fn left_of(a: &Point, b: &Point, c: &Point) -> bool {
47 Self::double_area_triangle(a, b, c) > 0.0
48 }
49
50 #[inline(always)]
52 fn collinear(a: &Point, b: &Point, c: &Point) -> bool {
53 Self::double_area_triangle(a, b, c) == 0.0
54 }
55
56 #[inline(always)]
58 fn intersects_proper(a: &Point, b: &Point, c: &Point, d: &Point) -> bool {
59 if Self::collinear(a, b, c)
60 || Self::collinear(a, b, d)
61 || Self::collinear(c, d, a)
62 || Self::collinear(c, d, b)
63 {
64 return false;
65 }
66
67 (Self::left_of(a, b, c) ^ Self::left_of(a, b, d))
68 && (Self::left_of(c, d, a) ^ Self::left_of(c, d, b))
69 }
70
71 fn between(a: &Point, b: &Point, c: &Point) -> bool {
73 fn _between(x1: f64, x2: f64, x3: f64) -> bool {
74 ((x1 <= x3) && (x3 <= x2)) || ((x1 >= x3) && (x3 >= x2))
75 }
76
77 if !Self::collinear(a, b, c) {
78 return false;
79 }
80
81 if (a.get_coordinate(0) - b.get_coordinate(0)).abs() > std::f64::EPSILON {
82 _between(
84 a.get_coordinate(0),
85 b.get_coordinate(0),
86 c.get_coordinate(0),
87 )
88 } else {
89 _between(
91 a.get_coordinate(1),
92 b.get_coordinate(1),
93 c.get_coordinate(1),
94 )
95 }
96 }
97
98 #[inline(always)]
99 fn intersects(a: &Point, b: &Point, c: &Point, d: &Point) -> bool {
100 Self::intersects_proper(a, b, c, d)
101 || Self::between(a, b, c)
102 || Self::between(a, b, d)
103 || Self::between(c, d, a)
104 || Self::between(c, d, b)
105 }
106}
107
108impl Shapelike for LineSegment {
109 fn get_center(&self) -> Point {
110 let mut coordinates = Vec::with_capacity(self.get_dimension());
111
112 for (start_coord, end_coord) in self.coordinate_iter() {
113 coordinates
114 .push(((start_coord - end_coord).abs() / 2.0) + f64::min(start_coord, end_coord));
115 }
116
117 Point::new(coordinates)
118 }
119
120 fn get_dimension(&self) -> usize {
121 self.start.get_dimension()
122 }
123
124 fn get_min_bounding_region(&self) -> Region {
125 let mut coordinates = Vec::with_capacity(self.get_dimension());
126
127 for (start_coord, end_coord) in self.coordinate_iter() {
128 coordinates.push((
129 f64::min(start_coord, end_coord),
130 f64::max(start_coord, end_coord),
131 ));
132 }
133
134 Region::new(coordinates)
135 }
136
137 fn get_area(&self) -> f64 {
138 0.0
139 }
140
141 fn get_min_distance(&self, other: &Shape) -> Result<f64, ShapelikeError> {
142 check_dimensions_match(self, other)?;
143
144 match other {
145 Shape::Point(point) => min_distance_point_line(point, self),
146 Shape::LineSegment(_) => Err(ShapelikeError::UnsupportedOperation),
147 Shape::Region(_) => Err(ShapelikeError::UnsupportedOperation),
148 }
149 }
150
151 fn intersects_line_segment(&self, line: &LineSegment) -> Result<bool, ShapelikeError> {
152 if self.get_dimension() != 2 {
153 return Err(ShapelikeError::UnexpectedDimension(self.get_dimension(), 2));
154 }
155
156 check_dimensions_match(self, line)?;
157
158 Ok(Self::intersects(
159 &self.start,
160 &self.end,
161 &line.start,
162 &line.end,
163 ))
164 }
165
166 fn intersects_region(&self, region: &Region) -> Result<bool, ShapelikeError> {
167 if self.get_dimension() != 2 {
168 return Err(ShapelikeError::UnexpectedDimension(self.get_dimension(), 2));
169 }
170
171 check_dimensions_match(self, region)?;
172
173 region.intersects_line_segment(self)
175 }
176}