aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Dußin2020-11-18 22:01:04 +0100
committerArne Dußin2020-11-18 22:01:04 +0100
commitf62dabcb390d4808739745c050dfba8e2826b214 (patch)
tree5cfce91911416b5e05a22cf33fd5775683b4c19d
parent761c8624521db5bf338c9e7e3520085820113f28 (diff)
parent8d99501918393ea0c046d721e6fa6015fe43b70f (diff)
downloadgraf_karto-f62dabcb390d4808739745c050dfba8e2826b214.tar.gz
graf_karto-f62dabcb390d4808739745c050dfba8e2826b214.zip
Merge branch 'polygon'
-rw-r--r--src/math/line_segment.rs287
-rw-r--r--src/math/mod.rs55
-rw-r--r--src/math/polygon.rs169
-rw-r--r--src/math/polygon_graph.rs466
-rw-r--r--src/math/vec2.rs2
5 files changed, 976 insertions, 3 deletions
diff --git a/src/math/line_segment.rs b/src/math/line_segment.rs
new file mode 100644
index 0000000..e6ca70f
--- /dev/null
+++ b/src/math/line_segment.rs
@@ -0,0 +1,287 @@
+use super::{Rect, Vec2};
+use alga::general::{ClosedDiv, ClosedMul, ClosedSub};
+use nalgebra::{RealField, Scalar};
+use num_traits::Zero;
+use std::cmp::Ordering;
+
+#[derive(Debug, Clone)]
+pub struct LineSegment<T: Scalar + Copy> {
+ pub start: Vec2<T>,
+ pub end: Vec2<T>,
+}
+
+impl<T: Scalar + Copy> LineSegment<T> {
+ pub fn new(start: Vec2<T>, end: Vec2<T>) -> 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<T>) -> bool
+ where
+ T: PartialOrd,
+ {
+ point.x <= super::partial_max(self.start.x, self.end.x)
+ && point.x >= super::partial_min(self.start.x, self.end.x)
+ && point.y <= super::partial_max(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<T>, ls2: &LineSegment<T>) -> 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
+ }
+
+ pub fn intersection(line_a: &LineSegment<T>, line_b: &LineSegment<T>) -> Option<Vec2<T>>
+ where
+ T: ClosedSub + ClosedMul + ClosedDiv + Zero + RealField,
+ {
+ // Calculate the differences of each line value from starting point to end point
+ // coordinate.
+ let dax = line_a.start.x - line_a.end.x;
+ let dbx = line_b.start.x - line_b.end.x;
+ let day = line_a.start.y - line_a.end.y;
+ let dby = line_b.start.y - line_b.end.y;
+
+ // Calculate determinant to see, if the lines are parallel or not
+ // TODO: Collinearity check?
+ let d = (dax * dby) - (day * dbx);
+ if d == T::zero() {
+ None
+ } else {
+ let ad = (line_a.start.x * line_a.end.y) - (line_a.start.y * line_a.end.x);
+ let bd = (line_b.start.x * line_b.end.y) - (line_b.start.y * line_b.end.x);
+
+ let out = Vec2::new(((ad * dbx) - (dax * bd)) / d, ((ad * dby) - (day * bd)) / d);
+
+ /* Since the line intersection, not the line segment intersection is calculated, a
+ * bounding check is necessary, to see if the intersection still lies inside both of
+ * the segments. We know it's on the lines, so checking with the lines bounding box is
+ * faster than checking where on the line exactly it would be.
+ */
+ if Rect::bounding_rect(line_a.start, line_a.end).contains(out)
+ && Rect::bounding_rect(line_b.start, line_b.end).contains(out)
+ {
+ Some(out)
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Find all segments, into which this LineSegment would be splitted, when the points provided
+ /// would cut the segment. The points must be on the line, otherwise this does not make sense.
+ /// Also, no segments of length zero (start point = end point) will be created.
+ pub fn segments(&self, split_points: &[Vec2<T>]) -> Vec<Vec2<T>>
+ where
+ T: ClosedSub + ClosedMul + PartialOrd + Zero,
+ {
+ // Make sure all segments are collinear with the segment and actually on it
+ assert_eq!(
+ split_points
+ .iter()
+ .find(|&p| triplet_orientation(self.start, self.end, *p)
+ != TripletOrientation::Collinear),
+ None
+ );
+ assert_eq!(
+ split_points.iter().find(|&p| !self.contains_collinear(*p)),
+ None
+ );
+
+ let mut segments = Vec::with_capacity(split_points.len() + 2);
+ segments.push(self.start);
+ segments.push(self.end);
+ for point in split_points {
+ segments.push(*point);
+ }
+
+ segments.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater));
+ segments.dedup();
+ if self.start > self.end {
+ segments.reverse();
+ }
+
+ segments
+ }
+
+ /// Checks if this line segment and the other line segment are the same, ignoring the direction
+ /// in which the lines are going, in other words, which of the vectors the line starts at and which
+ /// vector the line ends at is irrelevant.
+ pub fn eq_ignore_dir(&self, other: &LineSegment<T>) -> bool {
+ (self.start == other.start && self.end == other.end)
+ || (self.end == other.start && self.start == other.end)
+ }
+}
+
+#[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<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> 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::Counterclockwise,
+ q if q < T::zero() => TripletOrientation::Clockwise,
+ _ => TripletOrientation::Collinear,
+ }
+}
+
+/// Calculate the angle between three points. Guaranteed to have 0 <= angle < 2Pi. The angle is
+/// calculated as the angle between a line from a to b and then from b to c, increasing
+/// counterclockwise.
+///
+/// # Panics
+/// If the length from a to b or the length from b to c is zero.
+pub(crate) fn triplet_angle<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> T
+where
+ T: Scalar + Copy + ClosedSub + RealField + Zero,
+{
+ assert!(a != b);
+ assert!(b != c);
+
+ // Handle the extreme 0 and 180 degree cases
+ let orientation = triplet_orientation(a, b, c);
+ if orientation == TripletOrientation::Collinear {
+ if LineSegment::new(a, b).contains_collinear(c)
+ || LineSegment::new(b, c).contains_collinear(a)
+ {
+ return T::zero();
+ } else {
+ return T::pi();
+ }
+ }
+
+ // Calculate the vectors out of the points
+ let ba = a - b;
+ let bc = c - b;
+
+ // Calculate the angle between 0 and Pi.
+ let angle = ((ba * bc) / (ba.length() * bc.length())).acos();
+
+ // Make angle into a full circle angle by looking at the orientation of the triplet.
+ let angle = match orientation {
+ TripletOrientation::Counterclockwise => T::pi() + (T::pi() - angle),
+ _ => angle,
+ };
+
+ angle
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::f64::consts::PI;
+
+ #[test]
+ fn contains_collinear() {
+ let segment = LineSegment::new(Vec2::new(0., 0.), Vec2::new(2., 2.));
+
+ assert!(segment.contains_collinear(Vec2::new(0., 0.)));
+ assert!(segment.contains_collinear(Vec2::new(1., 1.)));
+ assert!(segment.contains_collinear(Vec2::new(2., 2.)));
+
+ assert!(!segment.contains_collinear(Vec2::new(-1., -1.)));
+ assert!(!segment.contains_collinear(Vec2::new(3., 3.)));
+ assert!(!segment.contains_collinear(Vec2::new(-1., 0.)));
+ }
+
+ #[test]
+ fn triplet_angle() {
+ assert_eq!(
+ super::triplet_angle(Vec2::new(0., 0.), Vec2::new(0., -1.), Vec2::new(-1., -1.)),
+ 1.5 * PI
+ );
+ assert_eq!(
+ super::triplet_angle(Vec2::new(2., 2.), Vec2::new(0., 0.), Vec2::new(-1., -1.)),
+ PI
+ );
+ assert_eq!(
+ super::triplet_angle(Vec2::new(3., 1.), Vec2::new(0., 0.), Vec2::new(6., 2.)),
+ 0.
+ );
+ assert_eq!(
+ super::triplet_angle(Vec2::new(3., 1.), Vec2::new(0., 0.), Vec2::new(-6., -2.)),
+ PI
+ );
+ assert_eq!(
+ super::triplet_angle(Vec2::new(0., 1.), Vec2::new(0., 2.), Vec2::new(-3., 2.)),
+ 0.5 * PI
+ );
+ assert_eq!(
+ super::triplet_angle(Vec2::new(3., 1.), Vec2::new(2., 2.), Vec2::new(0., 2.)),
+ 0.75 * PI
+ );
+ }
+}
diff --git a/src/math/mod.rs b/src/math/mod.rs
index 07d518e..0b591d7 100644
--- a/src/math/mod.rs
+++ b/src/math/mod.rs
@@ -1,9 +1,17 @@
+pub mod line_segment;
+pub mod polygon;
+pub mod polygon_graph;
pub mod rect;
-pub use self::rect::*;
-
pub mod vec2;
+
+pub use self::line_segment::*;
+pub use self::polygon::*;
+pub use self::polygon_graph::*;
+pub use self::rect::*;
pub use self::vec2::*;
+use std::cmp::Ordering;
+
/// Round a floating point number to the nearest step given by the step argument. For instance, if
/// the step is 0.5, then all numbers from 0.0 to 0.24999... will be 0., while all numbers from
/// 0.25 to 0.74999... will be 0.5 and so on.
@@ -21,3 +29,46 @@ pub fn round(num: f32, step: f32) -> f32 {
upper_bound
}
}
+
+/// Works like `std::cmp::max`, however also allows partial comparisons. It is specifically
+/// designed so functions that should be able to use f32 and f64 work, eventhough these do not
+/// implement Ord. The downside of this function however is, that its behaviour is undefined when
+/// `f32::NaN` for instance were to be passed.
+pub(crate) fn partial_max<T>(a: T, b: T) -> T
+where
+ T: PartialOrd,
+{
+ match a.partial_cmp(&b) {
+ Some(Ordering::Greater) => a,
+ _ => b,
+ }
+}
+/// Like `partial_max`, but for minimum values. Comes with the same downside, too.
+pub(crate) fn partial_min<T>(a: T, b: T) -> T
+where
+ T: PartialOrd,
+{
+ match a.partial_cmp(&b) {
+ Some(Ordering::Less) => a,
+ _ => b,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn partial_max() {
+ assert_eq!(super::partial_max(0., 0.), 0.);
+ assert_eq!(super::partial_max(-1., 1.), 1.);
+ assert_eq!(super::partial_max(-2., -1.), -1.);
+ assert_eq!(super::partial_max(2., 1.), 2.);
+ }
+
+ #[test]
+ fn partial_min() {
+ assert_eq!(super::partial_min(0., 0.), 0.);
+ assert_eq!(super::partial_min(-1., 1.), -1.);
+ assert_eq!(super::partial_min(-2., -1.), -2.);
+ assert_eq!(super::partial_min(2., 1.), 1.);
+ }
+}
diff --git a/src/math/polygon.rs b/src/math/polygon.rs
new file mode 100644
index 0000000..5711049
--- /dev/null
+++ b/src/math/polygon.rs
@@ -0,0 +1,169 @@
+use super::{PolygonGraph, Vec2};
+use nalgebra::{ClosedDiv, ClosedMul, ClosedSub, RealField, Scalar};
+use num_traits::Zero;
+use std::ops::Neg;
+
+#[derive(Debug)]
+// TODO: Support polygons with holes
+pub struct Polygon<T: Scalar + Copy> {
+ pub corners: Vec<Vec2<T>>,
+}
+
+impl<T: Scalar + Copy> Polygon<T> {
+ pub fn new(corners: Vec<Vec2<T>>) -> Self {
+ Self { corners }
+ }
+
+ /// Check whether a point is inside a polygon or not. If a point lies on an edge, it also
+ /// counts as inside the polygon.
+ pub fn contains_point(&self, p: Vec2<T>) -> bool
+ where
+ T: Zero + ClosedSub + ClosedMul + ClosedDiv + Neg<Output = T> + PartialOrd,
+ {
+ let n = self.corners.len();
+
+ let a = self.corners[n - 1];
+ let mut b = self.corners[n - 2];
+ let mut ax;
+ let mut ay = a.y - p.y;
+ let mut bx = b.x - p.x;
+ let mut by = b.y - p.y;
+
+ let mut lup = by > ay;
+ for i in 0..n {
+ // ax = bx;
+ ay = by;
+ b = self.corners[i];
+ bx = b.x - p.x;
+ by = b.y - p.y;
+
+ if ay == by {
+ continue;
+ }
+ lup = by > ay;
+ }
+
+ let mut depth = 0;
+ for i in 0..n {
+ ax = bx;
+ ay = by;
+ let b = &self.corners[i];
+ bx = b.x - p.x;
+ by = b.y - p.y;
+
+ if ay < T::zero() && by < T::zero() {
+ // both "up" or both "down"
+ continue;
+ }
+ if ay > T::zero() && by > T::zero() {
+ // both "up" or both "down"
+ continue;
+ }
+ if ax < T::zero() && bx < T::zero() {
+ // both points on the left
+ continue;
+ }
+
+ if ay == by && (if ax < bx { ax } else { bx }) <= T::zero() {
+ return true;
+ }
+ if ay == by {
+ continue;
+ }
+
+ let lx = ax + (((bx - ax) * -ay) / (by - ay));
+ if lx == T::zero() {
+ // point on edge
+ return true;
+ }
+ if lx > T::zero() {
+ depth += 1;
+ }
+ if ay == T::zero() && lup && by > ay {
+ // hit vertex, both up
+ depth -= 1;
+ }
+ if ay == T::zero() && !lup && by < ay {
+ // hit vertex, both down
+ depth -= 1;
+ }
+
+ lup = by > ay;
+ }
+
+ (depth & 1) == 1
+ }
+
+ /// Join this polygon with another, ensuring the area of the two stays the same, but the
+ /// overlap is not doubled, but instead joined into one.
+ /// Returns the Polygons themselves, if there is no overlap
+ pub fn unite(self, other: Polygon<T>) -> Vec<Polygon<T>>
+ where
+ T: RealField,
+ {
+ let mut graph = PolygonGraph::from_polygon(&self);
+ graph.add_all(&other);
+
+ // TODO: Make bounding box support multiple polygons
+ vec![graph.bounding_polygon()]
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn polygon_contains() {
+ let polygon = Polygon::new(vec![
+ Vec2::new(0., 0.),
+ Vec2::new(-1., 1.),
+ Vec2::new(0., 2.),
+ Vec2::new(1., 3.),
+ Vec2::new(3., 1.5),
+ Vec2::new(2., 0.),
+ Vec2::new(1., 1.),
+ ]);
+
+ assert!(!polygon.contains_point(Vec2::new(1., -2.)));
+ assert!(!polygon.contains_point(Vec2::new(-1., 0.5)));
+ assert!(polygon.contains_point(Vec2::new(0., 0.5)));
+ assert!(polygon.contains_point(Vec2::new(0.5, 1.)));
+ assert!(polygon.contains_point(Vec2::new(0.5, 1.5)));
+ assert!(!polygon.contains_point(Vec2::new(-2., 1.9)));
+ assert!(!polygon.contains_point(Vec2::new(0., 3.)));
+ assert!(polygon.contains_point(Vec2::new(1., 3.)));
+ }
+
+ #[test]
+ fn polygon_union() {
+ let first = Polygon::new(vec![
+ Vec2::new(-2., 1.),
+ Vec2::new(-0.5, 2.5),
+ Vec2::new(2., 2.),
+ Vec2::new(0.5, 1.5),
+ Vec2::new(1., 0.),
+ Vec2::new(-0.5, 1.),
+ ]);
+
+ let second = Polygon::new(vec![
+ Vec2::new(0., 0.),
+ Vec2::new(-2., 2.),
+ Vec2::new(3., 2.),
+ Vec2::new(1.5, 0.),
+ ]);
+
+ let union = first.unite(second);
+ assert_eq!(union.len(), 1);
+ let union = &union[0];
+
+ println!("Union of the two polygons: {:?}", union);
+
+ assert_eq!(union.corners.len(), 11);
+ assert!(union
+ .corners
+ .iter()
+ .find(|&p| p.x == 0. && p.y == 0.)
+ .is_some());
+ }
+}
diff --git a/src/math/polygon_graph.rs b/src/math/polygon_graph.rs
new file mode 100644
index 0000000..7721cbf
--- /dev/null
+++ b/src/math/polygon_graph.rs
@@ -0,0 +1,466 @@
+use super::{LineSegment, Polygon, Vec2};
+use nalgebra::{RealField, Scalar};
+use std::cmp::{Ordering, PartialOrd};
+
+#[derive(Debug)]
+struct Node<T: Scalar + Copy> {
+ pub vec: Vec2<T>,
+ pub adjacent: Vec<Vec2<T>>,
+}
+
+struct EdgeIterator<'a, T: Scalar + Copy> {
+ nodes: &'a Vec<Node<T>>,
+ pos: (usize, usize),
+}
+
+/// An undirected graph, that is optimised for polygon edge operations. Since edges of a polygon
+/// are an undirected graph, this structure also holds both directions. This makes it rather space
+/// inefficient, but makes edge operations rather swift. ß
+#[derive(Debug)]
+pub struct PolygonGraph<T: Scalar + Copy + PartialOrd> {
+ /// The nodes of the graph, together with their adjacency list.
+ nodes: Vec<Node<T>>,
+}
+// Helper functions to find nodes/vecs in sorted fields, so It doesn't always have to be written
+// out.
+#[inline]
+fn find_vec2<T: Scalar + Copy + PartialOrd>(
+ field: &[Vec2<T>],
+ lookup: &Vec2<T>,
+) -> Result<usize, usize> {
+ field.binary_search_by(|n| n.partial_cmp(lookup).unwrap_or(Ordering::Greater))
+}
+#[inline]
+fn find_node<T: Scalar + Copy + PartialOrd>(
+ field: &[Node<T>],
+ lookup: &Vec2<T>,
+) -> Result<usize, usize> {
+ field.binary_search_by(|n| n.vec.partial_cmp(lookup).unwrap_or(Ordering::Greater))
+}
+
+impl<'a, T: Scalar + Copy> EdgeIterator<'a, T> {
+ pub fn new(nodes: &'a Vec<Node<T>>) -> Self {
+ Self { nodes, pos: (0, 0) }
+ }
+}
+
+impl<'a, T: Scalar + Copy> Iterator for EdgeIterator<'a, T> {
+ type Item = LineSegment<T>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // Try to find the element in the nodes vector, if it exists.
+ if let Some(node) = self.nodes.get(self.pos.0) {
+ let end = node.adjacent[self.pos.1];
+
+ // Advance the iterator to the next possible element
+ if self.pos.1 + 1 < node.adjacent.len() {
+ self.pos.1 += 1;
+ } else {
+ self.pos.1 = 0;
+ self.pos.0 += 1;
+ }
+
+ Some(LineSegment::new(node.vec, end))
+ } else {
+ None
+ }
+ }
+}
+
+impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
+ /// Create a new, empty polygon graph.
+ pub fn new() -> Self {
+ Self { nodes: Vec::new() }
+ }
+
+ /// Count the number of edges in the graph. Internally, for each two connected points there are
+ /// two edges, but this returns the amount of polygon edges.
+ pub fn num_edges(&self) -> usize {
+ let mut num_edges = 0;
+ for node in &self.nodes {
+ for _ in &node.adjacent {
+ num_edges += 1;
+ }
+ }
+
+ assert!(num_edges % 2 == 0);
+ num_edges / 2
+ }
+
+ /// Count the number of nodes in this graph. If this graph consists of multiple polygons, this
+ /// can be different than the amount of corners, since corners with the same position are only
+ /// counted once.
+ pub fn num_nodes(&self) -> usize {
+ self.nodes.len()
+ }
+
+ /// Checks if there is an edge between the two given vectors. Is commutative in respect to the
+ /// two arguments.
+ pub fn has_edge(&self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
+ // Binary search the starting and then the end node.
+ if let Ok(from) = find_node(&self.nodes, from) {
+ if let Ok(_) = find_vec2(&self.nodes[from].adjacent, to) {
+ true
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ // Helper function to add the edge into the internal graph representation for one side only.
+ // Since to the outside the graph should always be undirected, this must be private.
+ fn add_edge_onesided(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
+ match find_node(&self.nodes, from) {
+ Ok(pos) => match find_vec2(&self.nodes[pos].adjacent, to) {
+ Ok(_) => return false,
+ Err(i) => self.nodes[pos].adjacent.insert(i, *to),
+ },
+ Err(pos) => self.nodes.insert(
+ pos,
+ Node {
+ vec: *from,
+ adjacent: vec![*to],
+ },
+ ),
+ }
+
+ true
+ }
+
+ /// Add an edge between the given vectors. If the edge already exists, it does nothing and
+ /// returns false, otherwise it returns true after addition.
+ pub fn add_edge(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
+ if !self.add_edge_onesided(from, to) {
+ return false;
+ }
+
+ let back_edge_succ = self.add_edge_onesided(to, from);
+ assert!(back_edge_succ);
+
+ true
+ }
+
+ // Helper function to remove the edge in the internal graph representation for one side only.
+ // Since to the outside the graph should always be undirected, this must be private.
+ fn remove_edge_onesided(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
+ if let Ok(from) = find_node(&self.nodes, from) {
+ if let Ok(to) = find_vec2(&self.nodes[from].adjacent, to) {
+ // Remove the edge from the vector.
+ self.nodes[from].adjacent.remove(to);
+
+ // If the node has no adjacent nodes anymore, remove it entirely.
+ if self.nodes[from].adjacent.is_empty() {
+ self.nodes.remove(from);
+ }
+
+ true
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ /// Remove an edge between the given vectors. If there is no edge between them, it does nothing
+ /// and returns false, otherwise it returns true after deletion.
+ pub fn remove_edge(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
+ if !self.remove_edge_onesided(from, to) {
+ return false;
+ }
+
+ let back_edge_succ = self.remove_edge_onesided(to, from);
+ assert!(back_edge_succ);
+
+ true
+ }
+
+ /// Constructs a new PolygonGraph from the provided polygon. Adds a node for every corner and
+ /// an edge to all connected corners (which should be exactly two, for regular polygons)
+ pub fn from_polygon(polygon: &Polygon<T>) -> Self {
+ let mut graph = PolygonGraph {
+ nodes: Vec::with_capacity(polygon.corners.len()),
+ };
+
+ graph.add_all(polygon);
+ graph
+ }
+
+ /// Add all edges of the provided polygon into the graph. Requires roughly double as much space
+ /// as the normal polygon.
+ pub fn add_all(&mut self, polygon: &Polygon<T>) {
+ for i in 0..polygon.corners.len() {
+ self.add_edge(
+ &polygon.corners[i],
+ &polygon.corners[(i + 1) % polygon.corners.len()],
+ );
+ }
+ }
+
+ /// Calculates all points where the graph edges intersect with one another. It then adds them
+ /// into the adjacency list such that the intersection point lies between the nodes of the
+ /// lines.
+ pub fn intersect_self(&mut self)
+ where
+ T: RealField,
+ {
+ // Find all intersections with all other edges.
+ let mut to_delete: Vec<LineSegment<T>> = Vec::new();
+ let mut to_add: Vec<(Vec2<T>, Vec2<T>)> = Vec::new();
+ for segment in EdgeIterator::new(&self.nodes) {
+ /* Save all intersections of this line with any other line, and the line that it's
+ * intersecting with.
+ */
+ let mut intersections: Vec<Vec2<T>> = Vec::new();
+ for compare_segment in EdgeIterator::new(&self.nodes) {
+ if segment.eq_ignore_dir(&compare_segment) {
+ continue;
+ }
+
+ if let Some(intersection) = LineSegment::intersection(&segment, &compare_segment) {
+ intersections.push(intersection);
+ }
+ }
+
+ if intersections.is_empty() {
+ continue;
+ }
+
+ to_delete.push(segment.clone());
+
+ // Safe, since at least the line segment itself is represented.
+ let segments = segment.segments(&intersections);
+ for i in 1..segments.len() {
+ to_add.push((segments[i - 1], segments[i]));
+ }
+ }
+
+ for segment in to_delete {
+ self.remove_edge(&segment.start, &segment.end);
+ }
+ for (start, end) in to_add {
+ self.add_edge(&start, &end);
+ }
+ }
+
+ /// Finds the minimal polygon that could hold this graph together, i.e. could contain the
+ /// entire graph, but with the minimal amount of space. It may however still contain extra
+ /// corner points, meaning an extra edge for three collinear points on this edge, that can be
+ /// cleaned up.
+ pub fn bounding_polygon(mut self) -> Polygon<T>
+ where
+ T: RealField,
+ {
+ assert!(self.num_nodes() >= 3);
+ self.intersect_self();
+
+ /* Start with the top-left node. Since the nodes are always sorted by y over x from top to
+ * bottom and left to right, this is the very first element in the vector. This is also a
+ * corner, because for such a node to be enveloped, there would have to be a node further
+ * to the top, in which case that node would have been selected.
+ */
+ let mut current_node = &self.nodes[0];
+ // Pretend we're coming from the top to start in the right direction.
+ let mut last_vec = current_node.vec - Vec2::new(T::zero(), T::one());
+ let mut bounding_polygon = Polygon::new(vec![current_node.vec]);
+ loop {
+ /* Find the next point by choosing the one with the greatest angle. This means we are
+ * "bending" to the leftmost edge at each step. Since we are going around the polygon
+ * in a clockwise direction, this yields the hull around the polygon.
+ * NOTE: Going left is just as viable, but we would have to handle the case where the
+ * algorithm would try to go back because the edge back has 0 degrees, which would be
+ * always preferred. Going right makes going back the absolute worst option.
+ */
+ let next_corner = current_node
+ .adjacent
+ .iter()
+ .max_by(|&a, &b| {
+ super::triplet_angle(last_vec, current_node.vec, *a)
+ .partial_cmp(&super::triplet_angle(last_vec, current_node.vec, *b))
+ .unwrap_or(Ordering::Equal)
+ })
+ .expect("Adjacency list is empty. The polygon has an open edge (is broken)");
+
+ // When we have come back to the start, the traversal is completed
+ if *next_corner == bounding_polygon.corners[0] {
+ break;
+ }
+
+ bounding_polygon.corners.push(*next_corner);
+ last_vec = current_node.vec;
+ current_node = &self.nodes[find_node(&self.nodes, &next_corner)
+ .expect("Failure to find node that should be inside list.")];
+ }
+
+ bounding_polygon
+ }
+}
+
+impl<T: Scalar + Copy + PartialOrd> Default for PolygonGraph<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn from_polygon() {
+ let a = Vec2::new(0., 0.);
+ let b = Vec2::new(0., 1.);
+ let c = Vec2::new(0.5, 1.);
+
+ let triangle = Polygon::new(vec![a, b, c]);
+
+ let graph = PolygonGraph::from_polygon(&triangle);
+ assert_eq!(graph.num_edges(), 3);
+
+ assert!(graph.has_edge(&a, &b));
+ assert!(graph.has_edge(&b, &a));
+
+ assert!(graph.has_edge(&a, &c));
+ assert!(graph.has_edge(&c, &a));
+
+ assert!(graph.has_edge(&b, &c));
+ assert!(graph.has_edge(&c, &b));
+ }
+
+ #[test]
+ fn add_all() {
+ let top_left = Vec2::new(0., 0.);
+ let top_right = Vec2::new(1., 0.);
+ let bot_left = Vec2::new(0., 1.);
+ let bot_right = Vec2::new(1., 1.);
+
+ let triangle = Polygon::new(vec![top_left, bot_right, top_right]);
+
+ let square = Polygon::new(vec![bot_left, bot_right, top_right, top_left]);
+
+ let mut graph = PolygonGraph::new();
+ graph.add_all(&triangle);
+ graph.add_all(&square);
+
+ assert_eq!(graph.num_edges(), 5);
+ assert_eq!(graph.num_nodes(), 4);
+
+ assert!(graph.has_edge(&top_left, &top_right));
+ assert!(graph.has_edge(&top_right, &top_left));
+
+ assert!(graph.has_edge(&top_left, &bot_left));
+ assert!(graph.has_edge(&bot_left, &top_left));
+
+ assert!(graph.has_edge(&bot_left, &bot_right));
+ assert!(graph.has_edge(&bot_right, &bot_left));
+
+ assert!(graph.has_edge(&bot_right, &top_right));
+ assert!(graph.has_edge(&top_right, &bot_right));
+
+ assert!(graph.has_edge(&top_left, &bot_right));
+ assert!(graph.has_edge(&bot_right, &top_left));
+ }
+
+ #[test]
+ fn intersect_self() {
+ let first = Polygon::new(vec![
+ Vec2::new(0., 0.),
+ Vec2::new(0., 2.),
+ Vec2::new(2., 2.),
+ Vec2::new(3., 1.),
+ Vec2::new(2., 0.),
+ ]);
+
+ let second = Polygon::new(vec![
+ Vec2::new(2.5, -0.5),
+ Vec2::new(0., 2.),
+ Vec2::new(2., 2.),
+ Vec2::new(2., 0.5),
+ Vec2::new(2.5, 0.),
+ ]);
+
+ let mut graph = PolygonGraph::from_polygon(&first);
+ graph.add_all(&second);
+
+ graph.intersect_self();
+
+ println!("Intersected graph:");
+ println!("{:#?}", &graph);
+
+ assert_eq!(graph.num_nodes(), 9);
+ assert_eq!(graph.num_edges(), 12);
+
+ assert!(graph.has_edge(&Vec2::new(2., 0.), &Vec2::new(2.25, 0.25)));
+ assert!(graph.has_edge(&Vec2::new(3., 1.), &Vec2::new(2.25, 0.25)));
+ assert!(!graph.has_edge(&Vec2::new(2., 0.), &Vec2::new(3., 1.)));
+ assert!(graph.has_edge(&Vec2::new(0., 2.), &Vec2::new(2., 2.)));
+ assert!(graph.has_edge(&Vec2::new(2., 2.), &Vec2::new(0., 2.)));
+ assert!(graph.has_edge(&Vec2::new(0., 2.), &Vec2::new(2., 0.)));
+ assert!(!graph.has_edge(&Vec2::new(0., 2.), &Vec2::new(2.5, -0.5)));
+ }
+
+ #[test]
+ fn bounding_polygon() {
+ let first = Polygon::new(vec![
+ Vec2::new(0., 0.),
+ Vec2::new(0., 2.),
+ Vec2::new(2., 2.),
+ Vec2::new(3., 1.),
+ Vec2::new(2., 0.),
+ ]);
+
+ let second = Polygon::new(vec![
+ Vec2::new(2.5, -0.5),
+ Vec2::new(0., 2.),
+ Vec2::new(2., 2.),
+ Vec2::new(2., 0.5),
+ Vec2::new(2.5, 0.),
+ ]);
+
+ let mut graph = PolygonGraph::from_polygon(&first);
+ graph.add_all(&second);
+
+ let bounding = graph.bounding_polygon();
+
+ let num_corners = 8;
+ assert_eq!(bounding.corners.len(), num_corners);
+
+ // Run around the polygon to see if it was constructed correctly.
+ let start_i = bounding
+ .corners
+ .iter()
+ .position(|&x| x == Vec2::new(0., 0.))
+ .expect("Starting vector does not exist in polygon.");
+
+ assert_eq!(
+ bounding.corners[(start_i + 1) % num_corners],
+ Vec2::new(2., 0.)
+ );
+ assert_eq!(
+ bounding.corners[(start_i + 2) % num_corners],
+ Vec2::new(2.5, -0.5)
+ );
+ assert_eq!(
+ bounding.corners[(start_i + 3) % num_corners],
+ Vec2::new(2.5, 0.0)
+ );
+ assert_eq!(
+ bounding.corners[(start_i + 4) % num_corners],
+ Vec2::new(2.25, 0.25)
+ );
+ assert_eq!(
+ bounding.corners[(start_i + 5) % num_corners],
+ Vec2::new(3., 1.)
+ );
+ assert_eq!(
+ bounding.corners[(start_i + 6) % num_corners],
+ Vec2::new(2., 2.)
+ );
+ assert_eq!(
+ bounding.corners[(start_i + 7) % num_corners],
+ Vec2::new(0., 2.)
+ );
+ }
+}
diff --git a/src/math/vec2.rs b/src/math/vec2.rs
index 2b33f7b..cd38889 100644
--- a/src/math/vec2.rs
+++ b/src/math/vec2.rs
@@ -8,7 +8,7 @@ use std::convert::{From, Into};
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
use std::{fmt, mem};
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Eq)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct Vec2<T: Scalar + Copy> {
pub x: T,
pub y: T,