1use crate::prelude::*;
2use crate::shape_box::ShapeBox;
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5use std::mem::swap;
6
7#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
9pub enum LineType {
10 Point,
12 Horizontal,
13 Vertical,
14 Angled,
16}
17
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19#[derive(Debug, Clone, Eq, PartialEq)]
20pub struct Line {
21 start: Coord,
22 end: Coord,
23 len: usize,
24 line_type: LineType,
25 angle: isize,
26}
27
28impl IntersectsContains for Line {}
29
30impl Line {
31 #[must_use]
32 pub fn new<P1: Into<Coord>, P2: Into<Coord>>(start: P1, end: P2) -> Self {
33 let start = start.into();
34 let end = end.into();
35 let line_type = if start == end {
36 LineType::Point
37 } else if start.x == end.x {
38 LineType::Vertical
39 } else if start.y == end.y {
40 LineType::Horizontal
41 } else {
42 LineType::Angled
43 };
44 let len = start.distance(end);
45 let angle = start.angle_to(end);
46 Self {
47 start,
48 end,
49 len,
50 line_type,
51 angle,
52 }
53 }
54}
55
56impl Line {
57 #[allow(clippy::len_without_is_empty)] #[inline]
59 #[must_use]
60 pub fn len(&self) -> usize {
61 self.len
62 }
63
64 #[inline]
66 #[must_use]
67 pub fn angle(&self) -> isize {
68 self.angle
69 }
70
71 #[inline]
72 #[must_use]
73 pub fn start(&self) -> Coord {
74 self.start
75 }
76
77 #[inline]
78 #[must_use]
79 pub fn end(&self) -> Coord {
80 self.end
81 }
82
83 #[inline]
84 #[must_use]
85 pub fn line_type(&self) -> LineType {
86 self.line_type
87 }
88
89 pub fn nearest_point<P: Into<Coord>>(&self, other: P) -> Coord {
90 if self.line_type == LineType::Point {
91 return self.start;
92 }
93 let other = other.into();
94 let ba_x = (self.end.x - self.start.x) as f64;
95 let ba_y = (self.end.y - self.start.y) as f64;
96
97 let len = ba_x.powi(2) + ba_y.powi(2);
98 let t =
99 ((other.x - self.start.x) as f64 * ba_x + (other.y - self.start.y) as f64 * ba_y) / len;
100 let t = t.clamp(0.0, 1.0);
101 coord!(
102 self.start.x as f64 + t * ba_x,
103 self.start.y as f64 + t * ba_y
104 )
105 }
106}
107
108impl Shape for Line {
109 fn from_points(points: &[Coord]) -> Self
110 where
111 Self: Sized,
112 {
113 debug_assert!(points.len() >= 2);
114 Line::new(points[0], points[1])
115 }
116
117 fn rebuild(&self, points: &[Coord]) -> Self
118 where
119 Self: Sized,
120 {
121 Line::from_points(points)
122 }
123
124 fn contains(&self, point: Coord) -> bool {
125 match self.line_type {
126 LineType::Point => self.start == point,
127 LineType::Horizontal => {
128 self.start.y == point.y && (self.left()..=self.right()).contains(&point.x)
129 }
130 LineType::Vertical => {
131 self.start.x == point.x && (self.top()..=self.bottom()).contains(&point.y)
132 }
133 LineType::Angled => {
134 self.start.are_collinear(self.end, point) && point.is_between(self.start, self.end)
135 }
136 }
137 }
138
139 fn points(&self) -> Vec<Coord> {
140 vec![self.start, self.end]
141 }
142
143 fn center(&self) -> Coord {
144 self.start.mid_point(self.end)
145 }
146
147 #[inline]
148 fn left(&self) -> isize {
149 self.start.x.min(self.end.x)
150 }
151
152 #[inline]
153 fn right(&self) -> isize {
154 self.start.x.max(self.end.x)
155 }
156
157 #[inline]
158 fn top(&self) -> isize {
159 self.start.y.min(self.end.y)
160 }
161
162 #[inline]
163 fn bottom(&self) -> isize {
164 self.start.y.max(self.end.y)
165 }
166
167 fn top_left(&self) -> Coord {
170 coord!(self.left(), self.top())
171 }
172
173 fn top_right(&self) -> Coord {
176 coord!(self.right(), self.top())
177 }
178
179 fn bottom_left(&self) -> Coord {
182 coord!(self.left(), self.bottom())
183 }
184
185 fn bottom_right(&self) -> Coord {
188 coord!(self.right(), self.bottom())
189 }
190
191 fn outline_pixels(&self) -> Vec<Coord> {
192 let mut start = self.start;
193 let mut end = self.end;
194 if start.x > end.x || start.y > end.y {
195 swap(&mut start, &mut end);
196 }
197 let mut output = vec![];
198 if start.x == end.x {
199 for y in start.y..=end.y {
200 output.push(coord!(start.x, y));
201 }
202 } else if start.y == end.y {
203 for x in start.x..=end.x {
204 output.push(coord!(x, start.y));
205 }
206 } else {
207 let mut delta = 0;
208 let x1 = start.x;
209 let y1 = start.y;
210 let x2 = end.x;
211 let y2 = end.y;
212 let dx = isize::abs(x2 - x1);
213 let dy = isize::abs(y2 - y1);
214 let dx2 = dx * 2;
215 let dy2 = dy * 2;
216 let ix: isize = if x1 < x2 { 1 } else { -1 };
217 let iy: isize = if y1 < y2 { 1 } else { -1 };
218 let mut x = x1;
219 let mut y = y1;
220 if dx >= dy {
221 loop {
222 output.push(coord!(x, y));
223 if x == x2 {
224 break;
225 }
226 x += ix;
227 delta += dy2;
228 if delta > dx {
229 y += iy;
230 delta -= dx2;
231 }
232 }
233 } else {
234 loop {
235 output.push(coord!(x, y));
236 if y == y2 {
237 break;
238 }
239 y += iy;
240 delta += dx2;
241 if delta > dy {
242 x += ix;
243 delta -= dy2;
244 }
245 }
246 }
247 }
248 output
249 }
250
251 fn filled_pixels(&self) -> Vec<Coord> {
252 self.outline_pixels()
253 }
254
255 fn to_shape_box(&self) -> ShapeBox {
256 ShapeBox::Line(self.clone())
257 }
258}
259
260impl Line {
261 #[must_use]
262 pub fn as_rect(&self) -> Rect {
263 Rect::new(self.start, self.end)
264 }
265
266 #[must_use]
267 pub fn as_circle(&self) -> Circle {
268 Circle::new(self.start, self.start.distance(self.end))
269 }
270}
271
272#[cfg(test)]
273mod test {
274 use crate::line::Line;
275 use crate::Shape;
276
277 #[test]
278 fn len() {
279 assert_eq!(Line::new((10, 10), (20, 10)).len, 10);
280 assert_eq!(Line::new((10, 10), (10, 20)).len, 10);
281 assert_eq!(Line::new((10, 10), (0, 10)).len, 10);
282 assert_eq!(Line::new((10, 10), (10, 0)).len, 10);
283 assert_eq!(Line::new((10, 10), (0, 0)).len, 14);
284 assert_eq!(Line::new((10, 10), (20, 20)).len, 14);
285 assert_eq!(Line::new((10, 10), (0, 20)).len, 14);
286 assert_eq!(Line::new((10, 10), (20, 0)).len, 14);
287 }
288
289 #[test]
290 fn rotate_center() {
291 assert_eq!(
292 Line::new((10, 10), (20, 10)).rotate(90),
293 Line::new((15, 5), (15, 15))
294 );
295 assert_eq!(
296 Line::new((10, 10), (20, 10)).rotate(25),
297 Line::new((10, 8), (20, 12))
298 );
299 }
300
301 #[test]
302 fn nearest() {
303 let line = Line::new((10, 10), (20, 20));
304 let point = line.nearest_point((17, 12));
305 assert!(line.contains(coord!(14, 14)));
306 assert_eq!(point, coord!(14, 14));
307 }
308
309 #[test]
310 fn nearest_line_reversed() {
311 let line = Line::new((110, 100), (40, 30));
312 let point = line.nearest_point((55, 85));
313 assert!(line.contains(coord!(75, 65)));
314 assert_eq!(point, coord!(75, 65));
315 }
316
317 mod contains {
318 use crate::line::Line;
319 use crate::Shape;
320
321 #[test]
322 fn point() {
323 let line = Line::new((10, 10), (10, 10));
324 assert!(line.contains(coord!(10, 10)));
325 assert!(!line.contains(coord!(11, 10)));
326 }
327
328 #[test]
329 fn vert() {
330 let line = Line::new((10, 10), (10, 20));
331 assert!(line.contains(coord!(10, 14)));
332 assert!(!line.contains(coord!(10, 24)));
333 assert!(!line.contains(coord!(11, 14)));
334 }
335
336 #[test]
337 fn horz() {
338 let line = Line::new((10, 10), (0, 10));
339 assert!(line.contains(coord!(5, 10)));
340 assert!(!line.contains(coord!(-1, 10)));
341 assert!(!line.contains(coord!(5, 11)));
342 }
343
344 #[test]
345 fn angle() {
346 let line = Line::new((0, 0), (10, 10));
347 assert!(line.contains(coord!(5, 5)));
348 assert!(!line.contains(coord!(8, 7)));
349 assert!(!line.contains(coord!(11, 11)));
350 }
351 }
352
353 mod outline {
354 use crate::line::Line;
355 use crate::Shape;
356
357 #[test]
358 fn flat_horz_right() {
359 let points = Line::new((0, 0), (6, 0)).outline_pixels();
360 assert_eq!(
361 points,
362 coord_vec![(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0)]
363 );
364 }
365
366 #[test]
367 fn flat_vert_down() {
368 let points = Line::new((0, 0), (0, 6)).outline_pixels();
369 assert_eq!(
370 points,
371 coord_vec![(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6)]
372 );
373 }
374
375 #[test]
376 fn flat_horz_left() {
377 let points = Line::new((0, 0), (-6, 0)).outline_pixels();
378 assert_eq!(
379 points,
380 coord_vec![(-6, 0), (-5, 0), (-4, 0), (-3, 0), (-2, 0), (-1, 0), (0, 0)]
381 );
382 }
383
384 #[test]
385 fn flat_vert_up() {
386 let points = Line::new((0, 0), (0, -6)).outline_pixels();
387 assert_eq!(
388 points,
389 coord_vec![(0, -6), (0, -5), (0, -4), (0, -3), (0, -2), (0, -1), (0, 0)]
390 );
391 }
392 }
393}