1use core::cmp::Ordering;
4
5use crate::{GeometryError, Point, orientation::orientation_sign};
6
7#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct LineSegment {
10 start: Point,
11 end: Point,
12}
13
14impl LineSegment {
15 pub fn new(start: Point, end: Point) -> Result<Self, GeometryError> {
22 if start == end {
23 return Err(GeometryError::DegenerateSegment);
24 }
25
26 Ok(Self { start, end })
27 }
28
29 #[must_use]
31 pub const fn start(self) -> Point {
32 self.start
33 }
34
35 #[must_use]
37 pub const fn end(self) -> Point {
38 self.end
39 }
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub enum LineSide {
45 Left,
47 On,
49 Right,
51}
52
53#[must_use]
55pub fn line_side(segment: LineSegment, point: Point) -> LineSide {
56 match orientation_sign(segment.start, segment.end, point) {
57 Ordering::Greater => LineSide::Left,
58 Ordering::Equal => LineSide::On,
59 Ordering::Less => LineSide::Right,
60 }
61}
62
63#[must_use]
65pub fn crosses_segment(first: LineSegment, second: LineSegment) -> bool {
66 let first_start_side = line_side(first, second.start);
67 let first_end_side = line_side(first, second.end);
68 let second_start_side = line_side(second, first.start);
69 let second_end_side = line_side(second, first.end);
70
71 if opposite(first_start_side, first_end_side) && opposite(second_start_side, second_end_side) {
72 return true;
73 }
74
75 (first_start_side == LineSide::On && point_on_segment(first.start, first.end, second.start))
76 || (first_end_side == LineSide::On && point_on_segment(first.start, first.end, second.end))
77 || (second_start_side == LineSide::On
78 && point_on_segment(second.start, second.end, first.start))
79 || (second_end_side == LineSide::On
80 && point_on_segment(second.start, second.end, first.end))
81}
82
83#[must_use]
85pub(crate) fn point_on_segment(start: Point, end: Point, point: Point) -> bool {
86 orientation_sign(start, end, point) == Ordering::Equal
87 && point.x() >= start.x().min(end.x())
88 && point.x() <= start.x().max(end.x())
89 && point.y() >= start.y().min(end.y())
90 && point.y() <= start.y().max(end.y())
91}
92
93fn opposite(first: LineSide, second: LineSide) -> bool {
94 matches!(
95 (first, second),
96 (LineSide::Left, LineSide::Right) | (LineSide::Right, LineSide::Left)
97 )
98}