use core::cmp::Ordering;
use crate::{GeometryError, Point, orientation::orientation_sign};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LineSegment {
start: Point,
end: Point,
}
impl LineSegment {
pub fn new(start: Point, end: Point) -> Result<Self, GeometryError> {
if start == end {
return Err(GeometryError::DegenerateSegment);
}
Ok(Self { start, end })
}
#[must_use]
pub const fn start(self) -> Point {
self.start
}
#[must_use]
pub const fn end(self) -> Point {
self.end
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LineSide {
Left,
On,
Right,
}
#[must_use]
pub fn line_side(segment: LineSegment, point: Point) -> LineSide {
match orientation_sign(segment.start, segment.end, point) {
Ordering::Greater => LineSide::Left,
Ordering::Equal => LineSide::On,
Ordering::Less => LineSide::Right,
}
}
#[must_use]
pub fn crosses_segment(first: LineSegment, second: LineSegment) -> bool {
let first_start_side = line_side(first, second.start);
let first_end_side = line_side(first, second.end);
let second_start_side = line_side(second, first.start);
let second_end_side = line_side(second, first.end);
if opposite(first_start_side, first_end_side) && opposite(second_start_side, second_end_side) {
return true;
}
(first_start_side == LineSide::On && point_on_segment(first.start, first.end, second.start))
|| (first_end_side == LineSide::On && point_on_segment(first.start, first.end, second.end))
|| (second_start_side == LineSide::On
&& point_on_segment(second.start, second.end, first.start))
|| (second_end_side == LineSide::On
&& point_on_segment(second.start, second.end, first.end))
}
#[must_use]
pub(crate) fn point_on_segment(start: Point, end: Point, point: Point) -> bool {
orientation_sign(start, end, point) == Ordering::Equal
&& point.x() >= start.x().min(end.x())
&& point.x() <= start.x().max(end.x())
&& point.y() >= start.y().min(end.y())
&& point.y() <= start.y().max(end.y())
}
fn opposite(first: LineSide, second: LineSide) -> bool {
matches!(
(first, second),
(LineSide::Left, LineSide::Right) | (LineSide::Right, LineSide::Left)
)
}