mutils/geom/
line.rs

1use std::iter::IntoIterator;
2use std::ops::Add;
3use std::ops::AddAssign;
4use std::ops::Div;
5use std::ops::DivAssign;
6use std::ops::Mul;
7use std::ops::MulAssign;
8use std::ops::Neg;
9use std::ops::Rem;
10use std::ops::Shl;
11use std::ops::ShlAssign;
12use std::ops::Shr;
13use std::ops::ShrAssign;
14use std::ops::Sub;
15use std::ops::SubAssign;
16
17use num_traits::sign::Signed;
18
19use crate::num::FromRounded;
20use crate::num::INum;
21use crate::num::Num;
22use crate::num::NumIdentity;
23use crate::num::ToRounded;
24use crate::num::ToSignedClamped;
25
26use crate::geom::Circle;
27use crate::geom::Point;
28use crate::geom::Rect;
29use crate::geom::Size;
30
31use crate::geom::PointPosition;
32use crate::geom::Transform;
33
34use crate::Random;
35
36mod line_iterator;
37pub use self::line_iterator::LineIterator;
38
39#[derive(Copy, Clone, Debug, PartialEq)]
40pub struct Line<N: Num = f32>(pub Point<N>, pub Point<N>);
41
42impl<N: Num> Line<N> {
43    pub fn new_from_points(points: &[N]) -> Self {
44        if points.len() != 4 {
45            panic!(
46                "Given points slice which isn't exactly 4 elements, length given ... {}",
47                points.len()
48            );
49        }
50
51        Line(Point(points[0], points[1]), Point(points[2], points[3]))
52    }
53
54    pub fn new_zero_value() -> Self {
55        Line(Point::new_zero_value(), Point::new_zero_value())
56    }
57
58    #[inline(always)]
59    pub fn start(self) -> Point<N> {
60        self.0
61    }
62
63    #[inline(always)]
64    pub fn end(self) -> Point<N> {
65        self.1
66    }
67
68    pub fn min_point(self) -> Point<N> {
69        self.start().min(self.end())
70    }
71
72    pub fn max_point(self) -> Point<N> {
73        self.start().max(self.end())
74    }
75
76    pub fn left_x(self) -> N {
77        self.start().x().min(self.end().x())
78    }
79
80    pub fn right_x(self) -> N {
81        self.start().x().max(self.end().x())
82    }
83
84    pub fn bottom_y(self) -> N {
85        self.start().y().min(self.end().y())
86    }
87
88    pub fn top_y(self) -> N {
89        self.start().y().max(self.end().y())
90    }
91
92    pub fn is_empty(self) -> bool {
93        self.start() == self.end()
94    }
95
96    pub fn is_horizontal(self) -> bool {
97        self.start().y() == self.end().y()
98    }
99
100    pub fn is_vertical(self) -> bool {
101        self.start().x() == self.end().x()
102    }
103
104    pub fn is_straight(self) -> bool {
105        self.is_horizontal() || self.is_vertical()
106    }
107
108    /// This is to allow creating a new Point, with a new type, from the type given.
109    /// i.e. `Point::new(1 as u8, 1 as u8)::to::<u32>()`
110    pub fn to<T: Num + From<N>>(&self) -> Line<T> {
111        Line(self.start().to::<T>(), self.end().to::<T>())
112    }
113
114    pub fn to_rect(self) -> Rect<N> {
115        self.start().rect_to(self.end())
116    }
117
118    /// Where `n` is a valid from 0.0 to 1.0,
119    /// this will return a point on the length of the line.
120    pub fn interpolation_point(self, n: N) -> Point<N> {
121        self.start() + self.diff_as_point() * n
122    }
123
124    fn diff_as_point(self) -> Point<N> {
125        self.end() - self.start()
126    }
127
128    /// This swaps the start and end points.
129    pub fn reverse(self) -> Self {
130        Self(self.end(), self.start())
131    }
132
133    pub fn is_direction_negative_x(self) -> bool {
134        self.end().x() < self.start().x()
135    }
136
137    pub fn is_direction_positive_x(self) -> bool {
138        self.start().x() < self.end().x()
139    }
140
141    pub fn is_direction_negative_y(self) -> bool {
142        self.end().y() < self.start().y()
143    }
144
145    pub fn is_direction_positive_y(self) -> bool {
146        self.start().y() < self.end().y()
147    }
148
149    pub fn abs(&self) -> Self {
150        Self(self.start().abs(), self.end().abs())
151    }
152
153    pub fn min(self, other: Self) -> Self {
154        Self(self.start().min(other.start()), self.end().min(other.end()))
155    }
156
157    pub fn max(self, other: Self) -> Self {
158        Self(self.start().max(other.start()), self.end().max(other.end()))
159    }
160
161    pub fn min_x(self) -> N {
162        self.start().x().min(self.end().x())
163    }
164
165    pub fn min_y(self) -> N {
166        self.start().y().min(self.end().y())
167    }
168
169    pub fn max_x(self) -> N {
170        self.start().x().max(self.end().x())
171    }
172
173    pub fn max_y(self) -> N {
174        self.start().y().max(self.end().y())
175    }
176
177    pub fn overlaps_line(self, other: Line<N>) -> bool {
178        self.intersect_line(other).is_some()
179    }
180
181    pub fn overlaps_circle(self, other: Circle<N>) -> bool {
182        other.overlaps_line(self)
183    }
184
185    pub fn intersect_line(self, other: Line<N>) -> Option<Point<N>> {
186        let self_rounded = self.to_rounded();
187        let other_rounded = other.to_rounded();
188
189        let size_1 = self_rounded.diff();
190        let size_2 = other_rounded.diff();
191        let start_dist = self_rounded.start() - other_rounded.start();
192
193        let s = (-size_1.height() * start_dist.x() + size_1.width() * start_dist.y())
194            / (-size_2.width() * size_1.height() + size_1.width() * size_2.height());
195        let t = (size_2.width() * start_dist.y() - size_2.height() * start_dist.x())
196            / (-size_2.width() * size_1.height() + size_1.width() * size_2.height());
197
198        if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
199            let intersection_x = self_rounded.start().x() + (t * size_1.width());
200            let intersection_y = self_rounded.start().y() + (t * size_1.height());
201            let intersection = Point(intersection_x, intersection_y).from_f32();
202            return Some(intersection);
203        }
204
205        None
206    }
207
208    /**
209     * Calculates if this overlaps another rectangle.
210     * If it does, it will return the part of the line that intersects
211     * within that rectangle.
212     *
213     * `None` is returned when no intersection is found.
214     */
215    pub fn intersect_rect(self, rect: Rect<N>) -> Option<Line<N>> {
216        let mut position = rect.line_position(self);
217
218        // No clippin needed.
219        if position.is_entirely_inside() {
220            return Some(self);
221        }
222
223        if position.is_entirely_outside() {
224            return None;
225        }
226
227        // Lines left either ...
228        //  - Need Clipping
229        //    - Start outside, and end inside.
230        //    - Start inside, and end outside.
231        //    - Start and end outside, and cross the inside.
232        //  - Are also excluded
233        //    - Start and end outside, but don't quite cross the inside. They came close.
234
235        // Should only iterate twice, at most.
236        let mut line_f32 = self.to_f32();
237        let rect_f32 = rect.to_f32();
238        loop {
239            // Case 1:
240            // both endpoints are within the clipping region
241            if position.is_entirely_inside() {
242                return Some(line_f32.from_f32());
243            }
244
245            // Starts and ends in the same space, and this is outside.
246            if position.is_within_same_space() && position.start().is_entirely_outside() {
247                return None;
248            }
249
250            // Case 3:
251            // The endpoints are in different regions, and the segment is partially within the clipping rectangle
252
253            // Select one of the endpoints outside the clipping rectangle
254            let point_position = if position.start().is_outside() {
255                position.start()
256            } else {
257                position.end()
258            };
259
260            // Calculate the intersection of the line with the clipping rectangle.
261            match line_f32.calculate_intersection(rect_f32, point_position) {
262                None => return None,
263                Some(new_point) => {
264                    // Update the point after clipping and recalculate outcode.
265                    if point_position == position.start() {
266                        line_f32 = Line(new_point, line_f32.end());
267                        position = rect_f32.line_position(line_f32);
268                    } else {
269                        line_f32 = Line(line_f32.start(), new_point);
270                        position = rect_f32.line_position(line_f32);
271                    }
272
273                    // This case happens when there are lines that go close to the rectangle,
274                    // but don't quite clip it.
275                    //
276                    // i.e.
277                    //
278                    //         /
279                    //        /
280                    //       /
281                    //      / -------
282                    //     /  |
283                    //    /   |
284                    //   /    |
285                    //        |
286                    //
287                    if rect_f32.intersect_rect(line_f32.to_rect()) == None {
288                        return None;
289                    }
290                }
291            }
292        }
293    }
294
295    /**
296     * Returns the atan2( y dist, x dist )
297     */
298    pub fn angle(self) -> N {
299        let self_f32 = self.to_f32();
300        let y_diff = self_f32.y_diff();
301        let x_diff = self_f32.x_diff();
302        let angle = y_diff.atan2(x_diff);
303        FromRounded::from_rounded(angle)
304    }
305
306    pub fn direction(self) -> Size<f32> {
307        let angle = self.to_f32().angle();
308        Size(angle.cos(), angle.sin())
309    }
310
311    pub fn hypot(self) -> N {
312        self.start().hypot_to(self.end())
313    }
314
315    pub fn hypot_sqrd(self) -> N {
316        self.start().distance_to(self.end()).hypot_sqrd()
317    }
318
319    pub fn rotate(self, angle: f32) -> Self {
320        let self_f32 = self.to_f32();
321        let midpoint = self_f32.midpoint();
322        let half_len = self_f32.hypot() / 2.0;
323
324        let angle = self_f32.angle() - angle;
325        let cos = angle.cos();
326        let sin = angle.sin();
327
328        let start = midpoint - Point(half_len * cos, half_len * sin);
329        let end = midpoint + Point(half_len * cos, half_len * sin);
330
331        Line(start, end).from_f32()
332    }
333
334    pub fn rotate_around_zero(self, angle: f32) -> Self {
335        Line(
336            self.start().rotate_around_zero(angle),
337            self.end().rotate_around_zero(angle),
338        )
339    }
340
341    pub fn rotate_around_point(self, angle: f32, target: Point<N>) -> Self {
342        Line(
343            self.start().rotate_around_point(angle, target),
344            self.end().rotate_around_point(angle, target),
345        )
346    }
347
348    pub fn rotate_around_start(self, angle: f32) -> Self {
349        Line(
350            self.start(),
351            self.end().rotate_around_point(angle, self.start()),
352        )
353    }
354
355    pub fn rotate_around_end(self, angle: f32) -> Self {
356        Line(
357            self.start().rotate_around_point(angle, self.end()),
358            self.end(),
359        )
360    }
361
362    pub fn midpoint(self) -> Point<N> {
363        ((self.start() + self.end()).to_rounded() / 2.0).from_f32()
364    }
365
366    pub(crate) fn to_f32(self) -> Line<f32> {
367        self.to_rounded()
368    }
369
370    /// Returns a line where the start is at the end,
371    /// and the end is at the start.
372    pub fn flip(self) -> Self {
373        Self(self.end(), self.start())
374    }
375
376    pub fn into_iter_with_step(self, step: N) -> LineIterator<N> {
377        LineIterator::new(self, step, true)
378    }
379
380    pub fn into_iter_inclusive(self) -> LineIterator<N> {
381        LineIterator::new(self, <N as NumIdentity>::one(), false)
382    }
383
384    pub fn into_iter_inclusive_with_step(self, step: N) -> LineIterator<N> {
385        LineIterator::new(self, step, false)
386    }
387
388    pub fn into_slice(self) -> [N; 4] {
389        [
390            self.start().x(),
391            self.start().y(),
392            self.end().x(),
393            self.end().y(),
394        ]
395    }
396}
397
398impl<N: Num + Signed> Line<N> {
399    pub fn direction_sign(&self) -> Point<N> {
400        self.diff_as_point().sign()
401    }
402}
403
404impl<N: Num + ToSignedClamped> Line<N>
405where
406    <N as ToSignedClamped>::Output: Signed,
407{
408    pub fn to_signed_clamped(self) -> Line<<N as ToSignedClamped>::Output> {
409        Line(
410            self.start().to_signed_clamped(),
411            self.end().to_signed_clamped(),
412        )
413    }
414
415    pub fn diff(self) -> Size<<N as ToSignedClamped>::Output> {
416        Size(self.x_diff(), self.y_diff())
417    }
418
419    pub fn x_diff(self) -> <N as ToSignedClamped>::Output {
420        self.end().x().to_signed_clamped() - self.start().x().to_signed_clamped()
421    }
422
423    pub fn y_diff(self) -> <N as ToSignedClamped>::Output {
424        self.end().y().to_signed_clamped() - self.start().y().to_signed_clamped()
425    }
426
427    pub fn slope(self) -> <N as ToSignedClamped>::Output {
428        let diff = self.diff();
429        diff.height() / diff.width()
430    }
431
432    pub fn inverse_slope(self) -> <N as ToSignedClamped>::Output {
433        let diff = self.diff();
434        diff.width() / diff.height()
435    }
436
437    pub fn step_direction(self) -> Point<<N as ToSignedClamped>::Output> {
438        self.diff().to_point().step()
439    }
440}
441
442impl Line<f32> {
443    pub(crate) fn from_f32<N: Num>(self) -> Line<N> {
444        Line(self.start().from_f32(), self.end().from_f32())
445    }
446
447    fn calculate_intersection(self, rect: Rect<f32>, clip_to: PointPosition) -> Option<Point<f32>> {
448        let p1 = self.start();
449        let slope = self.slope();
450        let inverse_slope = self.inverse_slope();
451
452        if clip_to.is_above() {
453            let new_x = p1.x() + inverse_slope * (rect.top_y() - p1.y());
454            let new_y = rect.top_y();
455
456            return Some(Point(new_x, new_y));
457        }
458
459        if clip_to.is_below() {
460            let new_x = p1.x() + inverse_slope * (rect.bottom_y() - p1.y());
461            let new_y = rect.bottom_y();
462
463            return Some(Point(new_x, new_y));
464        }
465
466        if clip_to.is_right() {
467            let new_x = rect.right_x();
468            let new_y = p1.y() + slope * (rect.right_x() - p1.x());
469
470            return Some(Point(new_x, new_y));
471        }
472
473        if clip_to.is_left() {
474            let new_x = rect.left_x();
475            let new_y = p1.y() + slope * (rect.left_x() - p1.x());
476
477            return Some(Point(new_x, new_y));
478        }
479
480        None
481    }
482}
483
484impl<O: Num, N: Num + ToRounded<O>> ToRounded<Line<O>> for Line<N> {
485    fn to_rounded(self) -> Line<O> {
486        Line(self.start().to_rounded(), self.end().to_rounded())
487    }
488}
489
490impl Add<Random> for Line {
491    type Output = Self;
492
493    fn add(self, rnd: Random) -> Self {
494        rnd + self
495    }
496}
497
498impl<N: Num> Add<Point<N>> for Line<N> {
499    type Output = Self;
500
501    fn add(self, other: Point<N>) -> Self {
502        Self(self.0 + other, self.1 + other)
503    }
504}
505
506impl<N: Num> AddAssign<Point<N>> for Line<N> {
507    fn add_assign(&mut self, other: Point<N>) {
508        self.0 += other;
509        self.1 += other;
510    }
511}
512
513impl<N: Num> Sub<Point<N>> for Line<N> {
514    type Output = Self;
515
516    fn sub(self, other: Point<N>) -> Self {
517        Self(self.0 - other, self.1 - other)
518    }
519}
520
521impl<N: Num> SubAssign<Point<N>> for Line<N> {
522    fn sub_assign(&mut self, other: Point<N>) {
523        self.0 -= other;
524        self.1 -= other;
525    }
526}
527
528impl<N: Num> Mul<Point<N>> for Line<N> {
529    type Output = Self;
530
531    fn mul(self, other: Point<N>) -> Self {
532        Self(self.0 * other, self.1 * other)
533    }
534}
535
536impl<N: Num> MulAssign<Point<N>> for Line<N> {
537    fn mul_assign(&mut self, other: Point<N>) {
538        self.0 *= other;
539        self.1 *= other;
540    }
541}
542
543impl<N: Num> Div<Point<N>> for Line<N> {
544    type Output = Self;
545
546    fn div(self, other: Point<N>) -> Self {
547        Self(self.0 / other, self.1 / other)
548    }
549}
550
551impl<N: Num> DivAssign<Point<N>> for Line<N> {
552    fn div_assign(&mut self, other: Point<N>) {
553        self.0 /= other;
554        self.1 /= other;
555    }
556}
557
558impl<N: Num> Rem<Point<N>> for Line<N> {
559    type Output = Self;
560
561    fn rem(self, other: Point<N>) -> Self {
562        Self(self.0 % other, self.1 % other)
563    }
564}
565
566impl<N: Num> Add<Line<N>> for Line<N> {
567    type Output = Self;
568
569    fn add(self, other: Line<N>) -> Self {
570        Self(self.0 + other.0, self.1 + other.1)
571    }
572}
573
574impl<N: Num> AddAssign<Line<N>> for Line<N> {
575    fn add_assign(&mut self, other: Line<N>) {
576        self.0 += other.0;
577        self.1 += other.1;
578    }
579}
580
581impl<N: Num> Sub<Line<N>> for Line<N> {
582    type Output = Self;
583
584    fn sub(self, other: Line<N>) -> Self {
585        Self(self.0 - other.0, self.1 - other.1)
586    }
587}
588
589impl<N: Num> SubAssign<Line<N>> for Line<N> {
590    fn sub_assign(&mut self, other: Line<N>) {
591        self.0 -= other.0;
592        self.1 -= other.1;
593    }
594}
595
596impl<N: Num> Mul<Line<N>> for Line<N> {
597    type Output = Self;
598
599    fn mul(self, other: Line<N>) -> Self {
600        Self(self.0 * other.0, self.1 * other.1)
601    }
602}
603
604impl<N: Num> MulAssign<Line<N>> for Line<N> {
605    fn mul_assign(&mut self, other: Line<N>) {
606        self.0 *= other.0;
607        self.1 *= other.1;
608    }
609}
610
611impl<N: Num> Div<Line<N>> for Line<N> {
612    type Output = Self;
613
614    fn div(self, other: Line<N>) -> Self {
615        Self(self.0 / other.0, self.1 / other.1)
616    }
617}
618
619impl<N: Num> DivAssign<Line<N>> for Line<N> {
620    fn div_assign(&mut self, other: Line<N>) {
621        self.0 /= other.0;
622        self.1 /= other.1;
623    }
624}
625
626impl<N: Num> Rem<Line<N>> for Line<N> {
627    type Output = Self;
628
629    fn rem(self, other: Line<N>) -> Self {
630        Self(self.0 % other.0, self.1 % other.1)
631    }
632}
633
634impl<N: Num> Mul<Size<N>> for Line<N> {
635    type Output = Self;
636
637    fn mul(self, other: Size<N>) -> Self {
638        Self(self.0 * other, self.1 * other)
639    }
640}
641
642impl<N: Num> MulAssign<Size<N>> for Line<N> {
643    fn mul_assign(&mut self, other: Size<N>) {
644        self.0 *= other;
645        self.1 *= other;
646    }
647}
648
649impl<N: Num> Div<Size<N>> for Line<N> {
650    type Output = Self;
651
652    fn div(self, other: Size<N>) -> Self {
653        Self(self.0 / other, self.1 / other)
654    }
655}
656
657impl<N: Num> DivAssign<Size<N>> for Line<N> {
658    fn div_assign(&mut self, other: Size<N>) {
659        self.0 /= other;
660        self.1 /= other;
661    }
662}
663
664impl<N: Num> Mul<N> for Line<N> {
665    type Output = Self;
666
667    fn mul(self, other: N) -> Self {
668        Self(self.0 * other, self.1 * other)
669    }
670}
671
672impl<N: Num> MulAssign<N> for Line<N> {
673    fn mul_assign(&mut self, other: N) {
674        self.0 *= other;
675        self.1 *= other;
676    }
677}
678
679impl<N: Num> Div<N> for Line<N> {
680    type Output = Self;
681
682    fn div(self, other: N) -> Self {
683        Self(self.0 / other, self.1 / other)
684    }
685}
686
687impl<N: Num> DivAssign<N> for Line<N> {
688    fn div_assign(&mut self, other: N) {
689        self.0 /= other;
690        self.1 /= other;
691    }
692}
693
694impl<N: INum> Shl<N> for Line<N> {
695    type Output = Self;
696
697    fn shl(self, other: N) -> Self {
698        Self(self.0 << other, self.1 << other)
699    }
700}
701
702impl<N: INum> ShlAssign<N> for Line<N> {
703    fn shl_assign(&mut self, other: N) {
704        *self = *self << other;
705    }
706}
707
708impl<N: INum> Shr<N> for Line<N> {
709    type Output = Self;
710
711    fn shr(self, other: N) -> Self {
712        Self(self.0 >> other, self.1 >> other)
713    }
714}
715
716impl<N: INum> ShrAssign<N> for Line<N> {
717    fn shr_assign(&mut self, other: N) {
718        *self = *self >> other;
719    }
720}
721
722impl<N: Num + Signed> Neg for Line<N> {
723    type Output = Self;
724
725    fn neg(self) -> Self::Output {
726        Self(-self.0, -self.1)
727    }
728}
729
730impl<N> Add<Transform<N>> for Line<N>
731where
732    N: Num,
733{
734    type Output = Line<N>;
735
736    #[inline(always)]
737    fn add(self, transform: Transform<N>) -> Self::Output {
738        transform + self
739    }
740}
741
742impl<N: Num> From<(Point<N>, Point<N>)> for Line<N> {
743    fn from((start, end): (Point<N>, Point<N>)) -> Self {
744        Self(start, end)
745    }
746}
747
748impl<N: Num> Into<(Point<N>, Point<N>)> for Line<N> {
749    fn into(self) -> (Point<N>, Point<N>) {
750        (self.0, self.1)
751    }
752}
753
754impl<N: Num> IntoIterator for Line<N> {
755    type Item = Point<N>;
756    type IntoIter = LineIterator<N>;
757
758    fn into_iter(self) -> Self::IntoIter {
759        LineIterator::new(self, <N as NumIdentity>::one(), true)
760    }
761}
762
763#[cfg(test)]
764mod test_into_iter {
765    use super::*;
766    use crate::geom::testing_utils::assert_approx_points_vec_eq;
767
768    #[test]
769    fn it_should_iterate_horizontally() {
770        let line = Line::<u32>(Point(0, 0), Point(4, 0));
771        let points = line.into_iter().collect::<Vec<_>>();
772
773        assert_eq!(
774            points,
775            [Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0),]
776        );
777    }
778
779    #[test]
780    fn it_should_iterate_horizontally_in_reverse() {
781        let line = Line::<u32>(Point(4, 0), Point(0, 0));
782        let points = line.into_iter().collect::<Vec<_>>();
783
784        assert_eq!(
785            points,
786            [Point(4, 0), Point(3, 0), Point(2, 0), Point(1, 0),]
787        );
788    }
789
790    #[test]
791    fn it_should_iterate_vertically() {
792        let line = Line::<u32>(Point(0, 0), Point(0, 4));
793        let points = line.into_iter().collect::<Vec<_>>();
794
795        assert_eq!(
796            points,
797            [Point(0, 0), Point(0, 1), Point(0, 2), Point(0, 3),]
798        );
799    }
800
801    #[test]
802    fn it_should_iterate_vertically_in_reverse() {
803        let line = Line::<u32>(Point(0, 4), Point(0, 0));
804        let points = line.into_iter().collect::<Vec<_>>();
805
806        assert_eq!(
807            points,
808            [Point(0, 4), Point(0, 3), Point(0, 2), Point(0, 1),]
809        );
810    }
811
812    #[test]
813    fn it_should_iterate_diagonally() {
814        let line = Line::<u32>(Point(0, 0), Point(4, 4));
815        let points = line.into_iter().collect::<Vec<_>>();
816
817        assert_eq!(
818            points,
819            [Point(0, 0), Point(1, 1), Point(2, 2), Point(3, 3),]
820        );
821    }
822
823    #[test]
824    fn it_should_return_one_point_when_start_and_finish_are_same() {
825        let line: Line<f32> = Line(Point(10.0, 20.0), Point(10.0, 20.0));
826        let points: Vec<Point<f32>> = line.into_iter().collect();
827
828        assert_eq!(points, vec![]);
829    }
830
831    #[test]
832    fn it_should_iterate_all_points_from_start_to_finish() {
833        let line: Line<f32> = Line(Point(10.0, 20.0), Point(15.0, 24.0));
834        let points: Vec<Point<f32>> = line.into_iter().collect();
835
836        #[rustfmt::skip]
837        assert_approx_points_vec_eq(points, vec![
838            Point(10.0, 20.0),
839            Point(10.780869, 20.624695),
840            Point(11.561737, 21.24939),
841            Point(12.342606, 21.874084),
842            Point(13.123474, 22.49878),
843            Point(13.904343, 23.123474),
844            Point(14.685211, 23.748169),
845        ]);
846    }
847
848    #[test]
849    fn it_should_iterate_all_points_from_start_to_finish_in_reverse() {
850        let line: Line<f32> = Line(Point(10.0, 20.0), Point(15.0, 24.0)).reverse();
851        let points: Vec<Point<f32>> = line.into_iter().collect();
852
853        #[rustfmt::skip]
854        assert_approx_points_vec_eq(points, vec![
855            Point(15.0, 24.0),
856            Point(14.219131, 23.375305),
857            Point(13.438263, 22.75061),
858            Point(12.657394, 22.125916),
859            Point(11.876526, 21.50122),
860            Point(11.095657, 20.876526),
861            Point(10.314789, 20.251831),
862        ]);
863    }
864}
865
866#[cfg(test)]
867mod test_into_iter_inclusive {
868    use super::*;
869    use crate::geom::testing_utils::assert_approx_points_vec_eq;
870
871    #[test]
872    fn it_should_return_one_point_when_start_and_finish_are_same() {
873        let line: Line<f32> = Line(Point(10.0, 20.0), Point(10.0, 20.0));
874        let points: Vec<Point<f32>> = line.into_iter_inclusive().collect();
875
876        #[rustfmt::skip]
877        assert_approx_points_vec_eq(points, vec![
878            Point(10.0, 20.0),
879        ]);
880    }
881
882    #[test]
883    fn it_should_iterate_all_points_from_start_to_finish_inclusive() {
884        let line: Line<f32> = Line(Point(10.0, 20.0), Point(15.0, 24.0));
885        let points: Vec<Point<f32>> = line.into_iter_inclusive().collect();
886
887        #[rustfmt::skip]
888        assert_approx_points_vec_eq(points, vec![
889            Point(10.0, 20.0),
890            Point(10.780869, 20.624695),
891            Point(11.561737, 21.24939),
892            Point(12.342606, 21.874084),
893            Point(13.123474, 22.49878),
894            Point(13.904343, 23.123474),
895            Point(14.685211, 23.748169),
896            Point(15.0, 24.0),
897        ]);
898    }
899
900    #[test]
901    fn it_should_iterate_all_points_from_start_to_finish_in_reverse_inclusive() {
902        let line: Line<f32> = Line(Point(10.0, 20.0), Point(15.0, 24.0)).reverse();
903        let points: Vec<Point<f32>> = line.into_iter_inclusive().collect();
904
905        #[rustfmt::skip]
906        assert_approx_points_vec_eq(points, vec![
907            Point(15.0, 24.0),
908            Point(14.219131, 23.375305),
909            Point(13.438263, 22.75061),
910            Point(12.657394, 22.125916),
911            Point(11.876526, 21.50122),
912            Point(11.095657, 20.876526),
913            Point(10.314789, 20.251831),
914            Point(10.0, 20.0),
915        ]);
916    }
917
918    #[test]
919    fn it_should_iterate_diagonally() {
920        let line = Line::<u32>(Point(0, 0), Point(4, 4));
921        let points = line.into_iter_inclusive().collect::<Vec<_>>();
922
923        assert_eq!(
924            points,
925            [
926                Point(0, 0),
927                Point(1, 1),
928                Point(2, 2),
929                Point(3, 3),
930                Point(4, 4)
931            ]
932        );
933    }
934}
935
936#[cfg(test)]
937mod is_horizontal {
938    use super::*;
939
940    #[test]
941    fn it_should_be_true_when_horizontal() {
942        let line = Line(Point(5, 9), Point(8, 9));
943        assert_eq!(line.is_horizontal(), true);
944    }
945
946    #[test]
947    fn it_should_be_false_when_not_horizontal() {
948        let line = Line(Point(5, 9), Point(8, 10));
949        assert_eq!(line.is_horizontal(), false);
950    }
951}
952
953#[cfg(test)]
954mod is_vertical {
955    use super::*;
956
957    #[test]
958    fn it_should_be_true_when_vertical() {
959        let line = Line(Point(5, 9), Point(5, 19));
960        assert_eq!(line.is_vertical(), true);
961    }
962
963    #[test]
964    fn it_should_be_false_when_not_vertical() {
965        let line = Line(Point(5, 9), Point(8, 10));
966        assert_eq!(line.is_vertical(), false);
967    }
968}
969
970#[cfg(test)]
971mod is_straight {
972    use super::*;
973
974    #[test]
975    fn it_should_be_true_when_vertical() {
976        let line = Line(Point(5, 9), Point(5, 19));
977        assert_eq!(line.is_vertical(), true);
978    }
979
980    #[test]
981    fn it_should_be_true_when_horizontal() {
982        let line = Line(Point(5, 9), Point(8, 9));
983        assert_eq!(line.is_horizontal(), true);
984    }
985
986    #[test]
987    fn it_should_be_false_when_angled() {
988        let line = Line(Point(5, 9), Point(8, 10));
989        assert_eq!(line.is_vertical(), false);
990    }
991}
992
993#[cfg(test)]
994mod diff {
995    use super::*;
996
997    #[test]
998    fn it_should_be_end_minus_start() {
999        let diff: Line<i32> = Line(Point(5, 9), Point(24, -3));
1000        assert_eq!(diff.diff(), Size(19, -12));
1001    }
1002}
1003
1004#[cfg(test)]
1005mod intersect {
1006    use super::*;
1007
1008    #[test]
1009    fn it_should_ignore_lines_fully_above() {
1010        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1011        let line = Line(Point(5.0, 5.0), Point(25.0, 5.0));
1012        assert_eq!(line.intersect_rect(rect), None);
1013    }
1014
1015    #[test]
1016    fn it_should_ignore_lines_fully_left() {
1017        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1018        let line = Line(Point(5.0, 5.0), Point(5.0, 25.0));
1019        assert_eq!(line.intersect_rect(rect), None);
1020    }
1021
1022    #[test]
1023    fn it_should_ignore_lines_fully_right() {
1024        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1025        let line = Line(Point(25.0, 5.0), Point(25.0, 25.0));
1026        assert_eq!(line.intersect_rect(rect), None);
1027    }
1028
1029    #[test]
1030    fn it_should_ignore_lines_fully_below() {
1031        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1032        let line = Line(Point(5.0, 25.0), Point(25.0, 25.0));
1033        assert_eq!(line.intersect_rect(rect), None);
1034    }
1035
1036    /// This tests for when lines go very close to a corner,
1037    /// but don't quite clip it.
1038    ///
1039    /// i.e.
1040    ///
1041    ///         /
1042    ///        /
1043    ///       /
1044    ///      / -------
1045    ///     /  |
1046    ///    /   |
1047    ///   /    |
1048    ///        |
1049    ///
1050    #[test]
1051    fn it_should_ignore_lines_crossing_a_corner() {
1052        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1053        let line = Line(Point(5.0, 12.0), Point(12.0, 5.0));
1054        assert_eq!(line.intersect_rect(rect), None);
1055    }
1056
1057    #[test]
1058    fn it_should_not_clip_lines_fully_inside() {
1059        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1060
1061        let l1 = Line(Point(12.0, 12.0), Point(18.0, 18.0));
1062        let l2 = Line(Point(12.0, 18.0), Point(18.0, 12.0));
1063        let l3 = Line(Point(18.0, 18.0), Point(12.0, 12.0));
1064
1065        assert_eq!(l1.intersect_rect(rect), Some(l1));
1066        assert_eq!(l2.intersect_rect(rect), Some(l2));
1067        assert_eq!(l3.intersect_rect(rect), Some(l3));
1068    }
1069
1070    #[test]
1071    fn it_should_clip_lines_from_the_left() {
1072        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1073        let line = Line(Point(7.0, 11.0), Point(16.0, 17.0));
1074
1075        assert_eq!(
1076            line.intersect_rect(rect),
1077            Some(Line(Point(10.0, 13.0), Point(16.0, 17.0)))
1078        );
1079    }
1080
1081    #[test]
1082    fn it_should_clip_lines_from_the_bottom_right() {
1083        let rect = Rect(Point(10.0, 10.0), Size(10.0, 10.0));
1084        let line = Line(Point(26.0, 29.0), Point(18.0, 17.0));
1085
1086        assert_eq!(
1087            line.intersect_rect(rect),
1088            Some(Line(Point(20.0, 20.0), Point(18.0, 17.0))),
1089        );
1090    }
1091
1092    #[test]
1093    fn it_should_clip_lines_fully_crossing() {
1094        let rect = Rect(Point(10.0, 10.0), Size(8.0, 9.0));
1095        let line = Line(Point(12.0, 7.0), Point(24.0, 25.0));
1096
1097        assert_eq!(
1098            line.intersect_rect(rect),
1099            Some(Line(Point(14.0, 10.0), Point(18.0, 16.0)))
1100        );
1101    }
1102
1103    #[test]
1104    fn it_should_clip_u32_lines() {
1105        let rect: Rect<u32> = Rect(Point(10, 10), Size(10, 10));
1106        let line: Line<u32> = Line(Point(7, 11), Point(16, 17));
1107
1108        assert_eq!(
1109            line.intersect_rect(rect),
1110            Some(Line(Point(10, 13), Point(16, 17)))
1111        );
1112
1113        let rect: Rect<u32> = Rect(Point(10, 10), Size(8, 9));
1114        let line: Line<u32> = Line(Point(12, 7), Point(24, 25));
1115
1116        assert_eq!(
1117            line.intersect_rect(rect),
1118            Some(Line(Point(14, 10), Point(18, 16)))
1119        );
1120    }
1121}
1122
1123#[cfg(test)]
1124mod interpolation_point {
1125    use super::*;
1126
1127    #[test]
1128    fn it_should_return_start_when_zero() {
1129        let line = Line(Point(3.0, 4.0), Point(14.0, 19.0));
1130        assert_eq!(line.interpolation_point(0.0), Point(3.0, 4.0));
1131    }
1132
1133    #[test]
1134    fn it_should_return_end_when_one() {
1135        let line = Line(Point(3.0, 4.0), Point(14.0, 19.0));
1136        assert_eq!(line.interpolation_point(1.0), Point(14.0, 19.0));
1137    }
1138
1139    #[test]
1140    fn it_should_return_middle_when_half() {
1141        let line = Line(Point(3.0, 4.0), Point(14.0, 19.0));
1142        assert_eq!(line.interpolation_point(0.5), Point(8.5, 11.5));
1143    }
1144}
1145
1146#[cfg(test)]
1147mod rotate {
1148    use super::*;
1149    use std::f32::consts::TAU;
1150
1151    #[test]
1152    fn it_should_be_level_after_45_rotation() {
1153        let line = Line(Point(0.0, 0.0), Point(10.0, 10.0));
1154        let rotation: Line<i32> = line.rotate(TAU / 8.0).to_rounded();
1155        assert_eq!(rotation, Line(Point(-2, 5), Point(12, 5)));
1156    }
1157
1158    #[test]
1159    fn it_should_be_flipped_with_180_rotation() {
1160        let line = Line(Point(1.0, 5.0), Point(15.0, 20.0));
1161        let rotation: Line<i32> = line.rotate(TAU / 2.0).to_rounded();
1162        assert_eq!(rotation, line.reverse().to_rounded());
1163    }
1164
1165    #[test]
1166    fn it_should_be_the_same_with_360_rotation() {
1167        let line = Line(Point(0.0, 5.0), Point(15.0, 20.0));
1168        let rotation: Line<i32> = line.rotate(TAU).to_rounded();
1169        assert_eq!(rotation, line.to_rounded());
1170    }
1171}
1172
1173#[cfg(test)]
1174mod rotate_around_point {
1175    use super::*;
1176    use std::f32::consts::TAU;
1177
1178    #[test]
1179    fn it_should_be_rotated_with_90_rotation() {
1180        let line = Line(Point(0.0, 5.0), Point(5.0, 0.0));
1181        let rotation: Line<i32> = line
1182            .rotate_around_point(TAU * 0.25, Point(0.0, 0.0))
1183            .to_rounded();
1184        assert_eq!(rotation, Line(Point(5, 0), Point(0, -5)));
1185    }
1186
1187    #[test]
1188    fn it_should_be_flipped_with_180_rotation() {
1189        let line = Line(Point(0.0, 5.0), Point(5.0, 0.0));
1190        let rotation: Line<i32> = line
1191            .rotate_around_point(TAU * 0.5, Point(0.0, 0.0))
1192            .to_rounded();
1193        assert_eq!(rotation, Line(Point(0, -5), Point(-5, -0)));
1194    }
1195
1196    #[test]
1197    fn it_should_be_rotated_with_90_rotation_around_point() {
1198        let line = Line(Point(8.0, 15.0), Point(13.0, 10.0));
1199        let rotation: Line<i32> = line
1200            .rotate_around_point(TAU * 0.25, Point(8.0, 10.0))
1201            .to_rounded();
1202        assert_eq!(rotation, Line(Point(13, 10), Point(8, 5)));
1203    }
1204
1205    #[test]
1206    fn it_should_be_flipped_with_180_rotation_around_point() {
1207        let line = Line(Point(8.0, 15.0), Point(13.0, 10.0));
1208        let rotation: Line<i32> = line
1209            .rotate_around_point(TAU * 0.5, Point(8.0, 10.0))
1210            .to_rounded();
1211        assert_eq!(rotation, Line(Point(8, 5), Point(3, 10)));
1212    }
1213}
1214
1215#[cfg(test)]
1216mod direction_sign {
1217    use super::*;
1218
1219    #[test]
1220    fn it_should_be_positive_when_end_is_larger() {
1221        let line = Line(Point(0.0, 0.0), Point(100.0, 100.0));
1222
1223        assert_eq!(line.direction_sign(), Point(1.0, 1.0));
1224    }
1225
1226    #[test]
1227    fn it_should_be_positive_when_end_is_the_same() {
1228        let line = Line(Point(0.0, 0.0), Point(0.0, 0.0));
1229
1230        assert_eq!(line.direction_sign(), Point(1.0, 1.0));
1231    }
1232
1233    #[test]
1234    fn it_should_be_negative_when_end_is_smaller() {
1235        let line = Line(Point(0.0, 0.0), Point(-100.0, -100.0));
1236
1237        assert_eq!(line.direction_sign(), Point(-1.0, -1.0));
1238    }
1239
1240    #[test]
1241    fn it_should_be_mix_when_end_is_smaller_and_larger() {
1242        let line = Line(Point(0.0, 0.0), Point(100.0, -100.0));
1243
1244        assert_eq!(line.direction_sign(), Point(1.0, -1.0));
1245    }
1246}
1247
1248#[cfg(test)]
1249mod hypot {
1250    use super::*;
1251
1252    #[test]
1253    fn it_should_be_correct_for_positive() {
1254        let line = Line(Point(2.0, 2.0), Point(5.0, 6.0));
1255
1256        assert_eq!(5.0, line.hypot());
1257    }
1258}
1259
1260#[cfg(test)]
1261mod hypot_sqrd {
1262    use super::*;
1263
1264    #[test]
1265    fn it_should_be_correct_for_positive() {
1266        let line = Line(Point(2.0, 2.0), Point(5.0, 6.0));
1267
1268        assert_eq!(25.0, line.hypot_sqrd());
1269    }
1270}
1271
1272#[cfg(test)]
1273mod intersect_line {
1274    use super::*;
1275
1276    #[test]
1277    fn it_should_intersect_lines() {
1278        let line_1 = Line(Point(0.0, 0.0), Point(10.0, 10.0));
1279        let line_2 = Line(Point(0.0, 10.0), Point(10.0, 0.0));
1280
1281        let intersection = line_1.intersect_line(line_2);
1282        assert_eq!(intersection, Some(Point(5.0, 5.0)));
1283    }
1284
1285    #[test]
1286    fn it_should_have_intersections_working_in_both_directions() {
1287        let line_1 = Line(Point(0.0, 0.0), Point(10.0, 10.0));
1288        let line_2 = Line(Point(0.0, 10.0), Point(10.0, 0.0));
1289
1290        let intersection_1 = line_1.intersect_line(line_2);
1291        let intersection_2 = line_1.intersect_line(line_2);
1292        assert_eq!(intersection_1, intersection_2);
1293    }
1294
1295    #[test]
1296    fn it_should_not_intersect_parallel_lines() {
1297        let line_1 = Line(Point(0.0, 0.0), Point(10.0, 2.0));
1298        let line_2 = Line(Point(0.0, 2.0), Point(10.0, 4.0));
1299
1300        let intersection = line_1.intersect_line(line_2);
1301        assert_eq!(intersection, None);
1302    }
1303}
1304
1305#[cfg(test)]
1306mod flip {
1307    use super::*;
1308
1309    #[test]
1310    fn it_should_swap_start_and_end() {
1311        let line: Line<i32> = Line(Point(123, 456), Point(987, 654));
1312        assert_eq!(line.flip(), Line(Point(987, 654), Point(123, 456)))
1313    }
1314}
1315
1316#[cfg(test)]
1317mod step_direction {
1318    use super::*;
1319
1320    #[test]
1321    fn it_should_return_zero_for_no_direction() {
1322        let point: Point<f32> = Point(0.0, 0.0);
1323        let line = Line(point, point);
1324
1325        let step_direction = line.step_direction();
1326        assert_eq!(Point(0.0_f32, 0.0_f32), step_direction);
1327    }
1328}
1329
1330#[cfg(test)]
1331mod into_slice {
1332    use super::*;
1333
1334    #[test]
1335    fn it_should_return_start_and_end_in_slice() {
1336        let line: Line<u32> = Line(Point(1, 2), Point(3, 4));
1337        assert_eq!(line.into_slice(), [1, 2, 3, 4]);
1338    }
1339}