1 /**
2  * Polygon
3  */
4 module d2d.sdl2.Polygon;
5 
6 import std.algorithm;
7 import std.array;
8 import std.parallelism;
9 import std.range;
10 import std.traits;
11 import d2d.math.AxisAlignedBoundingBox;
12 import d2d.math.Segment;
13 import d2d.math.Vector;
14 
15 /**
16  * A polygon is an object defined by its vertices in 2 space
17  * T is the type of the polygon and sides is how many sides the polygon has
18  * TODO: upgrade to dimension-ambiguous model class in math
19  */
20 class Polygon(T, uint numSides) {
21 
22     Vector!(T, 2)[numSides] vertices; ///The vertices of the polygon
23 
24     /**
25      * Gets the sides of a polygon
26      */
27     @property Segment!(T, 2)[numSides] sides() {
28         Segment!(T, 2)[numSides] s;
29         foreach (i; iota(0, cast(uint) numSides).parallel) {
30             s[i] = new Segment!(T, 2)(this.vertices[i], this.vertices[(i + 1) % $]);
31         }
32         return s;
33     }
34 
35     /**
36      * Creates an empty polygon
37      */
38     this() {}
39 
40     /**
41      * Creates a polygon using a list of vertices as vertices
42      */
43     this(Vector!(T, 2)[] vertices...) {
44         assert(vertices.length == numSides);
45         this.vertices = vertices;
46     }
47 
48     /**
49      * Casts the polygon to a polygon of another type
50      */
51     U opCast(U)() if (is(U : Polygon!V, V...)) {
52         alias type = TemplateArgsOf!U[0];
53         return new U(cast(Vector!(type, 2)[]) this.vertices);
54     }
55 
56     /**
57      * Returns whether or not a given point is inside the polygon
58      * This algorithm uses scanlining (see Renderer.fillPolygon) 
59      * Conceptually, it draws a ray to the left from the given point; if the ray intersects the polygon an odd number of times
60      * the point is within the polygon
61      */
62     bool contains(U)(Vector!(U, 2) point) {
63         Segment!(T, 2)[] relevantSides = (cast(Segment!(T, 2)[]) this.sides).filter!(
64                 a => (a.initial.y - point.y) * (a.terminal.y - point.y) <= 0).array;
65         int intersections;
66         foreach (side; relevantSides) {
67             immutable dy = point.y - side.initial.y;
68             immutable intersection = (dy * side.direction.x + side.initial.x * side.direction.y) / side
69                 .direction.y;
70             if (intersection < point.x) {
71                 intersections++;
72             }
73         }
74         return intersections % 2 == 1;
75     }
76 
77 }
78 
79 /**
80  * Returns the rectangle bounding a polygon
81  */
82 AxisAlignedBoundingBox!(T, 2) bound(T, uint sides)(Polygon!(T, sides) toBound) {
83     Vector!(T, 2) minVals = new Vector!(T, 2)(T.max);
84     Vector!(T, 2) maxVals = new Vector!(T, 2)(T.max + 1); //Causes an overflow to get small value
85     foreach (vertex; toBound.vertices) {
86         if (vertex.x < minVals.x) {
87             minVals.x = vertex.x;
88         }
89         if (vertex.x > maxVals.x) {
90             maxVals.x = vertex.x;
91         }
92         if (vertex.y < minVals.y) {
93             minVals.y = vertex.y;
94         }
95         if (vertex.y > maxVals.y) {
96             maxVals.y = vertex.y;
97         }
98     }
99     return new AxisAlignedBoundingBox!(T, 2)(minVals.x, minVals.y,
100             maxVals.x - minVals.x, maxVals.y - minVals.y);
101 }
102 
103 alias iPolygon(uint T) = Polygon!(int, T);
104 alias dPolygon(uint T) = Polygon!(double, T);
105 alias fPolygon(uint T) = Polygon!(float, T);