1 /**
2  * Axis Aligned Bounding Box
3  */
4 module d2d.math.AxisAlignedBoundingBox;
5 
6 import std.algorithm;
7 import std.math;
8 import std.parallelism;
9 import std.range;
10 import std.traits;
11 import d2d.math.Segment;
12 import d2d.math.Vector;
13 
14 /**
15  * A rectangle is a box in 2d space
16  * Because these rectangles are axis aligned, they don't have any rotation
17  */
18 class AxisAlignedBoundingBox(T, uint dimensions) {
19 
20     Vector!(T, dimensions) initialPoint; ///The initial or starting point of the AABB
21     Vector!(T, dimensions) extent; ///The extent in each direction the AABB extends from the initial point (eg.)
22 
23     /**
24      * Gives the AABB convenient 2d aliases
25      */
26     static if (dimensions == 2) {
27 
28         //TODO: alias for x, y, w, and h
29 
30         @property Vector!(T, 2) topLeft() {
31             return this.vertices[0];
32         }
33 
34         @property Vector!(T, 2) topRight() {
35             return this.vertices[1];
36         }
37 
38         @property Vector!(T, 2) bottomLeft() {
39             return this.vertices[3];
40         }
41 
42         @property Vector!(T, 2) bottomRight() {
43             return this.vertices[2];
44         }
45 
46     }
47 
48     /**
49      * Gets all the vertices of the AABB
50      */
51     @property Vector!(T, dimensions)[] vertices() {
52         Vector!(T, dimensions)[] allVerts = [new Vector!(T, dimensions)(this.initialPoint)];
53         if (this.extent == new Vector!(T, dimensions)(0)) {
54             return allVerts;
55         }
56         foreach (component; this.extent.components) {
57             foreach (i; 0 .. dimensions) {
58                 AxisAlignedBoundingBox!(T, dimensions) copy = new AxisAlignedBoundingBox!(T,
59                         dimensions)(new Vector!(T, dimensions)(this.initialPoint.components),
60                         new Vector!(T, dimensions)(this.extent.components));
61                 if (copy.extent[i].approxEqual(0)) {
62                     continue;
63                 }
64                 copy.initialPoint[i] += copy.extent[i];
65                 copy.extent[i] = 0;
66                 foreach (vertex; copy.vertices) {
67                     if (!allVerts.canFind(vertex)) {
68                         allVerts ~= vertex;
69                     }
70                 }
71             }
72         }
73         return allVerts;
74     }
75 
76     /**
77      * Gets all the edges of the AABB
78      */
79     @property Segment!(T, dimensions)[] edges() {
80         if (this.extent == new Vector!(T, dimensions)(0)) {
81             return null;
82         }
83         Segment!(T, dimensions)[] allEdges;
84         foreach(i; 0..dimensions) {
85             AxisAlignedBoundingBox!(T, dimensions) copy = new AxisAlignedBoundingBox!(T, dimensions)(this);
86             if (copy.extent[i].approxEqual(0)) {
87                 continue;
88             }
89             copy.initialPoint[i] += copy.extent[i];
90             copy.extent[i] = 0;
91             allEdges ~= new Segment!(T, dimensions)(this.initialPoint, copy.initialPoint);
92             foreach (edge; copy.edges) {
93                 if (!allEdges.canFind(edge)) {
94                     allEdges ~= edge;
95                 }
96             }
97         }
98         return allEdges;
99     }
100 
101     /**
102      * Gets the point that is the middle or center of the AABB
103      */
104     @property Vector!(T, dimensions) center() {
105         return this.initialPoint + this.extent / 2;
106     }
107 
108     /**
109      * Creates an AABB from the initial point, and how much in each direction the box extends
110      */
111     this(Vector!(T, dimensions) initialPoint, Vector!(T, dimensions) extent) {
112         this.initialPoint = initialPoint;
113         this.extent = extent;
114     }
115 
116     /**
117      * Creates an AABB from the same as the vector constructor, but as a varargs input
118      */
119     this(T[] args...) {
120         this.initialPoint = new Vector!(T, dimensions)(0);
121         this.extent = new Vector!(T, dimensions)(0);
122         foreach (i; 0 .. dimensions) {
123             this.initialPoint[i] = args[i];
124             this.extent[i] = args[i + dimensions];
125         }
126     }
127 
128     /**
129      * Copy constructor for AABBs
130      */
131     this(AxisAlignedBoundingBox!(T, dimensions) toCopy) {
132         this(new Vector!(T, dimensions)(toCopy.initialPoint), new Vector!(T,
133                 dimensions)(toCopy.extent));
134     }
135 
136     /**
137      * Returns whether the box contains the given point
138      */
139     bool contains(Vector!(T, dimensions) point) {
140         bool isContained = true;
141         foreach (i, component; (cast(T[]) point.components).parallel) {
142             if ((component - this.initialPoint[i]) * (
143                     component - this.initialPoint[i] - this.extent[i]) >= 0) {
144                 isContained = false;
145             }
146         }
147         return isContained;
148     }
149 
150 }
151 
152 /**
153  * Returns whether two rectangles intersect
154  */
155 bool intersects(T)(T first, T second) if (is(T : AxisAlignedBoundingBox!V, V...)) {
156     foreach (i; 0 .. first.initialPoint.components.length) {
157         if (first.initialPoint[i] < second.initialPoint[i]
158                 && first.initialPoint[i] + first.extent[i] < second.initialPoint[i]
159                 && first.initialPoint[i] < second.initialPoint[i] + second.extent[i]
160                 && first.initialPoint[i] + first.extent[i] < second.initialPoint[i] + second.extent[i]
161                 || first.initialPoint[i] > second.initialPoint[i]
162                 && first.initialPoint[i] + first.extent[i] > second.initialPoint[i]
163                 && first.initialPoint[i] > second.initialPoint[i] + second.extent[i]
164                 && first.initialPoint[i] + first.extent[i]
165                 > second.initialPoint[i] + second.extent[i]) {
166             return false;
167         }
168     }
169     return true;
170 }