1#[cfg(feature = "serialize")]
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5use super::line_interval::LineInterval;
6use super::line_iterator::{plot_line, LinePixelIterator};
7use crate::{distance_squared_to_line, distance_to_line, irect_edges, Direction};
8use bevy_math::{ivec2, IRect, IVec2, Vec2};
9
10#[inline]
12pub fn iline<P>(start: P, end: P) -> ILine
13where
14 P: Into<IVec2>,
15{
16 ILine::new(start, end)
17}
18
19#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
21#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
22pub struct ILine {
23 start: IVec2,
24 end: IVec2,
25}
26
27impl ILine {
28 pub const ZERO: Self = Self {
29 start: IVec2::ZERO,
30 end: IVec2::ZERO,
31 };
32
33 #[inline]
35 #[must_use]
36 pub fn new<P>(start: P, end: P) -> Self
37 where
38 P: Into<IVec2>,
39 {
40 Self {
41 start: start.into(),
42 end: end.into(),
43 }
44 }
45
46 #[inline]
48 #[must_use]
49 pub fn start(&self) -> IVec2 {
50 self.start
51 }
52
53 #[inline]
55 #[must_use]
56 pub fn end(&self) -> IVec2 {
57 self.end
58 }
59
60 #[inline]
62 #[must_use]
63 pub fn length_squared(&self) -> f32 {
64 self.start.as_vec2().distance_squared(self.end.as_vec2())
65 }
66
67 #[inline]
69 #[must_use]
70 pub fn length(&self) -> f32 {
71 self.start.as_vec2().distance(self.end.as_vec2())
72 }
73
74 #[inline]
75 #[must_use]
76 pub fn distance_squared_to_point(&self, p: Vec2) -> f32 {
77 distance_squared_to_line(p, &self.into())
78 }
79
80 #[inline]
81 #[must_use]
82 pub fn distance_to_point(&self, p: Vec2) -> f32 {
83 distance_to_line(p, &self.into())
84 }
85
86 #[inline]
88 #[must_use]
89 pub fn rotate(&self, radians: f32) -> Self {
90 self.rotate_around(self.start, radians)
91 }
92
93 #[inline]
95 #[must_use]
96 pub fn rotate_around(&self, center: IVec2, radians: f32) -> Self {
97 let cos_theta = f32::cos(radians);
98 let sin_theta = f32::sin(radians);
99
100 let start_x_diff = self.start.x as f32 - center.x as f32;
101 let start_y_diff = self.start.y as f32 - center.y as f32;
102
103 let end_x_diff = self.end.x as f32 - center.x as f32;
104 let end_y_diff = self.end.y as f32 - center.y as f32;
105
106 let x0 = cos_theta * start_x_diff - sin_theta * start_y_diff + center.x as f32;
107 let y0 = sin_theta * start_x_diff + cos_theta * start_y_diff + center.y as f32;
108
109 let x1 = cos_theta * end_x_diff - sin_theta * end_y_diff + center.x as f32;
110 let y1 = sin_theta * end_x_diff + cos_theta * end_y_diff + center.y as f32;
111
112 Self::new((x0 as i32, y0 as i32), (x1 as i32, y1 as i32))
113 }
114
115 #[inline]
118 #[must_use]
119 pub fn flip(&self) -> Self {
120 Self::new(self.end, self.start)
121 }
122
123 #[inline]
125 #[must_use]
126 pub fn contains<P>(&self, point: P) -> bool
127 where
128 P: Into<Vec2>,
129 {
130 let point = point.into();
131 let d_sp = self.start.as_vec2().distance(point);
132 let d_pe = point.distance(self.end.as_vec2());
133 let d = d_sp + d_pe - self.length();
134 -f32::EPSILON < d && d < f32::EPSILON
135 }
136
137 #[inline]
138 #[must_use]
139 pub fn is_vertical(&self) -> bool {
140 self.start.x == self.end.x
141 }
142
143 #[inline]
144 pub fn is_horizontal(&self) -> bool {
145 self.start.y == self.end.y
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn is_axis_aligned(&self) -> bool {
152 self.is_horizontal() || self.is_vertical()
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn aabb(&self) -> IRect {
159 IRect::from_corners(self.start, self.end)
160 }
161
162 #[inline]
164 #[must_use]
165 pub fn axis_alignment(&self) -> Option<Direction> {
166 if self.start.x == self.end.x {
167 if self.start.y < self.end.y {
168 Some(Direction::North)
169 } else {
170 Some(Direction::South)
171 }
172 } else if self.start.y == self.end.y {
173 if self.start.x > self.end.x {
174 Some(Direction::West)
175 } else {
176 Some(Direction::East)
177 }
178 } else {
179 None
180 }
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn diagonal_axis_alignment(&self) -> Option<Direction> {
187 let dx = self.end.x - self.start.x;
188 let dy = self.end.y - self.start.y;
189 if dx == dy {
190 if dx > 0 {
191 Some(Direction::NorthEast)
192 } else {
193 Some(Direction::SouthWest)
194 }
195 } else if dx == -dy {
196 if dx > 0 {
197 Some(Direction::SouthEast)
198 } else {
199 Some(Direction::NorthWest)
200 }
201 } else {
202 None
203 }
204 }
205
206 #[inline]
208 #[must_use]
209 pub fn intersects_line(&self, other: &ILine) -> Option<IVec2> {
210 let seg1 = LineInterval::line_segment(*self);
211 let seg2 = LineInterval::line_segment(*other);
212 seg1.relate(&seg2).unique_intersection()
213 }
214
215 #[inline]
217 #[must_use]
218 pub fn intersects_rect(&self, rect: &IRect) -> bool {
219 if rect.contains(self.start) || rect.contains(self.end) {
220 return true;
221 }
222 for edge in irect_edges(rect) {
223 if self.intersects_line(&edge).is_some() {
224 return true;
225 }
226 }
227 false
228 }
229
230 #[inline]
233 #[must_use]
234 pub fn axis_aligned_intersect_rect(&self, rect: &IRect) -> Option<ILine> {
235 if self.is_vertical() {
236 let (start, end, flipped) = if self.start.y < self.end.y {
238 (self.start, self.end, false)
239 } else {
240 (self.end, self.start, true)
241 };
242
243 if start.x < rect.min.x || start.x > rect.max.x {
245 return None;
246 }
247
248 let start_y = start.y.max(rect.min.y).min(rect.max.y);
250 let end_y = end.y.max(rect.min.y).min(rect.max.y);
251
252 if start_y <= end_y {
253 let result = iline((start.x, start_y), (start.x, end_y));
254 if flipped {
255 Some(result.flip())
256 } else {
257 Some(result)
258 }
259 } else {
260 None
261 }
262 } else if self.is_horizontal() {
263 let (start, end, flipped) = if self.start.x < self.end.x {
265 (self.start, self.end, false)
266 } else {
267 (self.end, self.start, true)
268 };
269
270 if start.y < rect.min.y || start.y > rect.max.y {
272 return None;
273 }
274
275 let start_x = start.x.max(rect.min.x).min(rect.max.x);
277 let end_x = end.x.max(rect.min.x).min(rect.max.x);
278
279 if start_x <= end_x {
280 let result = iline((start_x, start.y), (end_x, start.y));
281 if flipped {
282 Some(result.flip())
283 } else {
284 Some(result)
285 }
286 } else {
287 None
288 }
289 } else {
290 None
291 }
292 }
293
294 #[inline]
297 #[must_use]
298 pub fn overlap(&self, other: &ILine) -> Option<ILine> {
299 if self.start == other.start
301 || self.start == other.end
302 || self.end == other.start
303 || self.end == other.end
304 {
305 let self_length = self.length_squared();
307 let other_length = other.length_squared();
308 return if self_length < other_length {
309 Some(*self)
310 } else {
311 Some(*other)
312 };
313 }
314
315 let min_x1 = self.start.x.min(self.end.x);
317 let max_x1 = self.start.x.max(self.end.x);
318 let min_y1 = self.start.y.min(self.end.y);
319 let max_y1 = self.start.y.max(self.end.y);
320
321 let min_x2 = other.start.x.min(other.end.x);
322 let max_x2 = other.start.x.max(other.end.x);
323 let min_y2 = other.start.y.min(other.end.y);
324 let max_y2 = other.start.y.max(other.end.y);
325
326 if max_x1 < min_x2 || min_x1 > max_x2 || max_y1 < min_y2 || min_y1 > max_y2 {
327 return None;
329 }
330
331 let overlap_start_x = max_x1.min(max_x2);
333 let overlap_start_y = max_y1.min(max_y2);
334 let overlap_end_x = min_x1.max(min_x2);
335 let overlap_end_y = min_y1.max(min_y2);
336
337 let overlap_start = ivec2(overlap_start_x, overlap_start_y);
338 let overlap_end = ivec2(overlap_end_x, overlap_end_y);
339
340 Some(iline(overlap_start, overlap_end))
341 }
342
343 #[inline]
345 pub fn visit_points<F>(&self, visitor: F)
346 where
347 F: FnMut(i32, i32),
348 {
349 plot_line(self.start.x, self.start.y, self.end.x, self.end.y, visitor);
350 }
351
352 #[inline]
353 #[must_use]
354 pub fn pixels(&self) -> LinePixelIterator {
355 LinePixelIterator::new(self)
356 }
357}
358
359impl From<&ILine> for [Vec2; 2] {
360 fn from(value: &ILine) -> Self {
361 [value.start.as_vec2(), value.end.as_vec2()]
362 }
363}
364
365impl From<ILine> for [Vec2; 2] {
366 fn from(value: ILine) -> Self {
367 [value.start.as_vec2(), value.end.as_vec2()]
368 }
369}
370
371impl From<&ILine> for [IVec2; 2] {
372 fn from(value: &ILine) -> Self {
373 [value.start, value.end]
374 }
375}
376
377impl From<ILine> for [IVec2; 2] {
378 fn from(value: ILine) -> Self {
379 [value.start, value.end]
380 }
381}
382
383impl fmt::Display for ILine {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 write!(f, "[{}, {}]", self.start, self.end)
386 }
387}
388
389impl fmt::Debug for ILine {
390 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
391 fmt.debug_tuple(stringify!(ILine))
392 .field(&self.start)
393 .field(&self.end)
394 .finish()
395 }
396}
397
398#[cfg(test)]
399mod test {
400 use super::*;
401
402 #[test]
403 fn test_contains() {
404 let line = iline((0, 0), (10, 10));
405 assert!(line.contains((5., 5.)));
406 assert!(!line.contains((5., 6.)));
407 assert!(!line.contains((6., 5.)));
408 }
409
410 #[test]
411 fn test_aabb() {
412 let line = iline((0, 0), (10, 10));
413 let aabb = line.aabb();
414 assert_eq!(aabb.min.x, 0);
415 assert_eq!(aabb.min.y, 0);
416 assert_eq!(aabb.width(), 10);
417 assert_eq!(aabb.height(), 10);
418 }
419
420 #[test]
421 fn test_axis_alignment() {
422 let line = iline((0, 0), (10, 10));
423 assert_eq!(line.axis_alignment(), None);
424 let line = iline((0, 0), (10, 0));
425 assert_eq!(line.axis_alignment(), Some(Direction::East));
426 let line = iline((0, 0), (0, 10));
427 assert_eq!(line.axis_alignment(), Some(Direction::North));
428 let line = iline((10, 0), (0, 0));
429 assert_eq!(line.axis_alignment(), Some(Direction::West));
430 let line = iline((0, 10), (0, 0));
431 assert_eq!(line.axis_alignment(), Some(Direction::South));
432 let line = iline((0, 10), (1, 0));
433 assert_eq!(line.axis_alignment(), None);
434 }
435
436 #[test]
437 fn test_diag_axis_alignment() {
438 let line = iline((0, 0), (9, 10));
439 assert_eq!(line.diagonal_axis_alignment(), None);
440 let line = iline((0, 0), (10, 10));
441 assert_eq!(line.diagonal_axis_alignment(), Some(Direction::NorthEast));
442 let line = iline((0, 10), (10, 0));
443 assert_eq!(line.diagonal_axis_alignment(), Some(Direction::SouthEast));
444 let line = iline((10, 0), (0, 10));
445 assert_eq!(line.diagonal_axis_alignment(), Some(Direction::NorthWest));
446 let line = iline((10, 10), (0, 0));
447 assert_eq!(line.diagonal_axis_alignment(), Some(Direction::SouthWest));
448 }
449
450 #[test]
451 fn test_intersects_rect() {
452 let rect = IRect::from_corners((0, 0).into(), (4, 4).into());
453
454 let intersecting_lines = vec![
455 iline((-1, 2), (2, 2)), iline((3, 3), (3, 5)), iline((3, 3), (5, 3)), iline((2, 2), (2, -2)), iline((-2, 2), (6, 2)), iline((-1, 2), (0, 2)), iline((2, 5), (2, 4)), iline((5, 1), (4, 1)), iline((2, -1), (2, 0)), iline((-1, 5), (1, 3)), iline((3, 3), (5, 5)), iline((3, 1), (5, -1)), iline((-1, -1), (1, 1)), iline((0, 4), (-1, 5)), iline((4, 4), (5, 5)), iline((-1, -1), (0, 0)), iline((4, -1), (4, 0)), iline((0, 0), (0, 4)), iline((0, 4), (4, 4)), iline((0, 0), (4, 0)), iline((4, 0), (4, 4)), iline((1, 1), (3, 3)), iline((3, 3), (1, 3)), ];
484
485 for line in intersecting_lines {
486 assert!(
487 line.intersects_rect(&rect),
488 "expected intersection: {:?}",
489 line
490 );
491 }
492
493 let outlying_lines = vec![
494 iline((-2, 1), (-1, 3)),
495 iline((2, 5), (2, 6)),
496 iline((5, 1), (7, 1)),
497 iline((3, -1), (4, -2)),
498 ];
499
500 for line in outlying_lines {
501 assert!(
502 !line.intersects_rect(&rect),
503 "expected no intersection: {:?}",
504 line
505 );
506 }
507 }
508}