aboutsummaryrefslogtreecommitdiff
path: root/src/math/polygon.rs
blob: e70cd2c866a22dfdfa0aa5b06ac1b9b26a36a137 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use super::{LineSegment, TripletOrientation, Vec2};
use alga::general::{ClosedMul, ClosedSub};
use nalgebra::Scalar;
use num_traits::Zero;

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(&self, point: Vec2<T>) -> bool
    where
        T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero,
    {
        // Must be at least a triangle to contain anything
        if self.corners.len() < 3 {
            return false;
        }

        /* What's happening here:
         *
         * This algorithm creates a horizontal line for the point that should be checked. It goes
         * to infinity (since infinite lines are not possible, it just goes to the maximum x value
         * of all corner points). Then, the times the line crosses a polygon edge is counted. If
         * the times it hit a polygon edge is uneven, it must have been inside, otherwise outside.
         *
         * There is an edge case however (pun not intended); When the line from the point is collinear
         * to a polygon edge. it might hit this edge once and no other (it's at the top y or bottom y
         * of the polygon), but may still not be inside the polygon. In this case, it's checked
         * whether the point is in between the corner points of this polygon edge or not. If it is
         * in between, it is still inside the polygon, otherwise outside.
         */

        // Get the maximum x for the horizontal lines for "Infinity".
        let mut max_x = self.corners[0].x;
        for corner in self.corners.iter().skip(1) {
            max_x = super::partial_max(corner.x, max_x);
        }
        println!("Max x is {:?}", max_x);
        // The horizontally "infinite" point of the test point.
        let line_to_infinity = LineSegment::new(point, Vec2::new(max_x, point.y));

        // Check for intersection for all the polygon edges.
        let mut num_intersects = 0;
        for i in 0..self.corners.len() {
            // Wrap around for the edge that returns to the start.
            let j = (i + 1) % self.corners.len();
            let current_edge = LineSegment::new(self.corners[i], self.corners[j]);

            if LineSegment::intersect(&current_edge, &line_to_infinity) {
                /* Check for the edge case, where the point might lie right on an edge of the
                 * polygon.
                 */
                if super::triplet_orientation(current_edge.start, current_edge.end, point)
                    == TripletOrientation::Collinear
                {
                    // The point is right on an edge
                    if current_edge.contains_collinear(point) {
                        return true;
                    }
                } else {
                    num_intersects += 1;
                }
            }
        }

        num_intersects % 2 == 1
    }

    /// Join this polygon with another, ensuring the area of the two stays the same, minus the
    /// overlap.
    ///
    /// # Panics
    /// If the two polygons do not intersect, it is impossible to unite them into a single polygon,
    /// in which case this function is bound to fail.
    // TODO: Create a function that only tries to join the polygons.
    pub fn unite(&mut self, mut _other: Polygon<T>) {
        unimplemented!()
    }
}

#[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(Vec2::new(1., -2.)));
        assert!(!polygon.contains(Vec2::new(-1., 0.5)));
        assert!(polygon.contains(Vec2::new(0., 0.5)));
        assert!(polygon.contains(Vec2::new(0.5, 1.)));
        assert!(polygon.contains(Vec2::new(0.5, 1.5)));
        assert!(!polygon.contains(Vec2::new(-2., 1.9)));
        assert!(!polygon.contains(Vec2::new(0., 3.)));
    }
}