From a22faae428f7ff51a0b2eaea902e5b5ed1a7c8a5 Mon Sep 17 00:00:00 2001 From: Arne Dußin Date: Wed, 11 Nov 2020 21:36:44 +0100 Subject: Add polygon that can check if a point is inside ..except for one super super edgy edge case, but I wanted to get this algorithm out into a commit before I ruin it completely (probably) --- src/math/line_segment.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/math/line_segment.rs (limited to 'src/math/line_segment.rs') diff --git a/src/math/line_segment.rs b/src/math/line_segment.rs new file mode 100644 index 0000000..d7bcdb1 --- /dev/null +++ b/src/math/line_segment.rs @@ -0,0 +1,116 @@ +use super::Vec2; +use alga::general::{ClosedMul, ClosedSub}; +use nalgebra::Scalar; +use num_traits::Zero; + +pub struct LineSegment { + pub start: Vec2, + pub end: Vec2, +} + +impl LineSegment { + pub fn new(start: Vec2, end: Vec2) -> Self { + Self { start, end } + } + + /* Helper function to check if this line contains a point. This function is very efficient, but + * must only be used for points that are collinear with the segment. This is checked only by + * assertion, so make sure that everything is ok in release mode. + */ + pub(crate) fn contains_collinear(&self, point: Vec2) -> bool + where + T: PartialOrd, + { + point.x <= super::partial_max(self.start.x, self.end.x) + && point.x >= super::partial_max(self.start.x, self.end.x) + && point.y <= super::partial_min(self.start.y, self.end.y) + && point.y >= super::partial_min(self.start.y, self.end.y) + } + + /// Checks if two segments intersect. This is much more efficient than trying to find the exact + /// point of intersection, so it should be used if it is not strictly necessary to know it. + pub fn intersect(ls1: &LineSegment, ls2: &LineSegment) -> bool + where + T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero, + { + /* This algorithm works by checking the triplet orientation of the first lines points with the + * first point of the second line. After that it does the same for the second point of the + * second line. If the orientations are different, that must mean the second line starts + * "before" the first line and ends "after" it. It does the same, but with the roles of first + * and second line reversed. If both of these conditions are met, it follows that the lines + * must have crossed. + * + * Edge case: If an orientation comes out as collinear, the point of the other line that was + * checked may be on the other line or after/before it (if you extend the segment). If it is on + * the other line, this also means, the lines cross, since one line "stands" on the other. + * If however none of the collinear cases are like this, the lines cannot touch, because line + * segment a point was collinear with was not long enough. + */ + + // Cache the necessary orientations. + let ls1_ls2start_orientation = triplet_orientation(ls1.start, ls1.end, ls2.start); + let ls1_ls2end_orientation = triplet_orientation(ls1.start, ls1.end, ls2.end); + let ls2_ls1start_orientation = triplet_orientation(ls2.start, ls2.end, ls1.start); + let ls2_ls1end_orientation = triplet_orientation(ls2.start, ls2.end, ls1.end); + + // Check for the first case that wase described (general case). + if ls1_ls2start_orientation != ls1_ls2end_orientation + && ls2_ls1start_orientation != ls2_ls1end_orientation + { + return true; + } + + // Check if the start of ls2 lies on ls1 + if ls1_ls2start_orientation == TripletOrientation::Collinear + && ls1.contains_collinear(ls2.start) + { + return true; + } + // Check if the end of ls2 lies on ls1 + if ls1_ls2end_orientation == TripletOrientation::Collinear + && ls1.contains_collinear(ls2.end) + { + return true; + } + + // Check if the start of ls1 lies on ls2 + if ls2_ls1start_orientation == TripletOrientation::Collinear + && ls2.contains_collinear(ls1.start) + { + return true; + } + // Check if the end of ls1 lies on ls2 + if ls2_ls1end_orientation == TripletOrientation::Collinear + && ls2.contains_collinear(ls1.end) + { + return true; + } + + false + } +} + +#[derive(PartialEq, Eq)] +pub(crate) enum TripletOrientation { + Clockwise, + Counterclockwise, + Collinear, +} + +/// Helper function to determine which direction one would turn to traverse from the first point +/// over the second to the third point. The third option is collinear, in which case the three points +/// are on the same line. +pub(crate) fn triplet_orientation(a: Vec2, b: Vec2, c: Vec2) -> TripletOrientation +where + T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero, +{ + /* Check the slopes of the vector from a to b and b to c. If the slope of ab is greater than + * that of bc, the rotation is clockwise. If ab is smaller than bc it's counterclockwise. If + * they are the same it follows that the three points are collinear. + */ + match (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y) { + q if q > T::zero() => TripletOrientation::Clockwise, + q if q < T::zero() => TripletOrientation::Counterclockwise, + _ => TripletOrientation::Collinear, + } +} -- cgit v1.2.3-70-g09d2