1 /**
2  * Type and dimension generic Axis-Aligned Bounding Box (AABB).
3  */
4 module bettercmath.box;
5 
6 @safe @nogc nothrow pure:
7 
8 /// Options for the BoundingBox template.
9 enum BoundingBoxOptions
10 {
11     /// Default options: store `end` corner information and derive `size`.
12     none = 0,
13     /// Store `size` information and derive `end` corner.
14     storeSize = 1,
15 }
16 
17 /**
18  * Generic Axis-Aligned Bounding Box.
19  *
20  * May be stored as the starting and ending corners,
21  * or as starting point and size.
22  *
23  * Params:
24  *   T = Element type
25  *   N = Box dimension, must be positive
26  *   options = Additional options, like storage meaning
27  */
28 struct BoundingBox(T, uint Dim, BoundingBoxOptions options = BoundingBoxOptions.none)
29 if (Dim > 0)
30 {
31     import bettercmath.vector : Vector;
32 
33     alias ElementType = T;
34     /// Bounding Box dimension.
35     enum dimension = Dim;
36     /// Point type, a Vector with the same type and dimension.
37     alias Point = Vector!(T, Dim);
38     /// Size type, a Vector with the same type and dimension.
39     alias Size = Vector!(T, Dim);
40 
41     /// Starting BoundingBox corner.
42     Point start = 0;
43 
44     static if (options & BoundingBoxOptions.storeSize)
45     {
46         /// Size of a BoundingBox, may be negative.
47         Size size = 1;
48 
49         /// Get the `end` corner of a BoundingBox.
50         @property Point end() const
51         {
52             return start + size;
53         }
54         /// Set the `end` corner of a BoundingBox.
55         @property void end(const Point value)
56         {
57             size = value - start;
58         }
59 
60         /// Cast BoundingBox to another storage type.
61         auto opCast(U : BoundingBox!(T, Dim, options ^ BoundingBoxOptions.storeSize))() const
62         {
63             typeof(return) box = {
64                 start = this.start,
65                 end = this.end,
66             };
67             return box;
68         }
69     }
70     else
71     {
72         /// Ending BoundingBox corner.
73         Point end = 1;
74 
75         /// Get the size of a BoundingBox, may be negative.
76         @property Size size() const
77         {
78             return end - start;
79         }
80         /// Set the size of a BoundingBox, using `start` as the pivot.
81         @property void size(const Size value)
82         {
83             end = start + value;
84         }
85 
86         /// Cast BoundingBox to another storage type.
87         auto opCast(U : BoundingBox!(T, Dim, options ^ BoundingBoxOptions.storeSize))() const
88         {
89             typeof(return) box = {
90                 start = this.start,
91                 size = this.size,
92             };
93             return box;
94         }
95     }
96 
97     /// Get the width of a BoundingBox, may be negative.
98     @property T width() const
99     {
100         return size.width;
101     }
102     /// Set the width of a BoundingBox, using `start` as the pivot.
103     @property void width(const T value)
104     {
105         auto s = size;
106         s.width = value;
107         size = s;
108     }
109 
110     static if (Dim >= 2)
111     {
112         /// Get the height of a BoundingBox, may be negative.
113         @property T height() const
114         {
115             return size.height;
116         }
117         /// Set the height of a BoundingBox, using `start` as the pivot.
118         @property void height(const T value)
119         {
120             auto s = size;
121             s.height = value;
122             size = s;
123         }
124     }
125     static if (Dim >= 3)
126     {
127         /// Get the depth of a BoundingBox, may be negative.
128         @property T depth() const
129         {
130             return size.depth;
131         }
132         /// Set the depth of a BoundingBox, using `start` as the pivot.
133         @property void depth(const T value)
134         {
135             auto s = size;
136             s.depth = value;
137             size = s;
138         }
139     }
140 
141     /// Get the central point of BoundingBox.
142     @property Point center() const
143     {
144         return (start + end) / 2;
145     }
146 
147     /// Returns whether BoundingBox have any non-positive size values.
148     @property bool empty() const
149     {
150         import std.algorithm : any;
151         return size[].any!"a <= 0";
152     }
153 
154     /// Returns a copy of BoundingBox with sorted corners, so that `size` only presents non-negative values.
155     BoundingBox abs() const
156     {
157         import std.algorithm : swap;
158         typeof(return) result = this;
159         foreach (i; 0 .. result.dimension)
160         {
161             if (result.start[i] < result.end[i])
162             {
163                 swap(result.start[i], result.end[i]);
164             }
165         }
166         return result;
167     }
168 
169     /// Get the volume of the BoundingBox.
170     @property T volume() const
171     {
172         import std.algorithm : fold;
173         return size.fold!"a * b";
174     }
175 
176     static if (Dim == 2)
177     {
178         /// 2D area is the same as generic box volume.
179         alias area = volume;
180     }
181 
182     static if (Dim == 3)
183     {
184         /// Get the surface area of a 3D BoundingBox.
185         @property T surfaceArea() const
186         {
187             auto s = size;
188             return 2 * (s.x * s.y + s.y * s.z + s.x * s.z);
189         }
190     }
191 
192     /// Returns true if Point is contained within BoundingBox.
193     bool contains(T, uint N)(const auto ref T[N] point) const
194     {
195         import std.algorithm : all, map, min;
196         import std.range : iota;
197         enum minDimension = min(this.dimension, N);
198         return iota(minDimension).map!(i => point[i] >= start[i] && point[i] <= end[i]).all;
199     }
200 
201     /// Returns true if `box` is completely contained within `this` BoundingBox.
202     bool contains(Args...)(const auto ref BoundingBox!(T, Args) box) const
203     {
204         return contains(box.start) && contains(box.end);
205     }
206 
207     /// Returns the intersection between two BoundingBoxes.
208     auto intersection(Args...)(const auto ref BoundingBox!(T, Args) box) const
209     {
210         import std.algorithm : map, min, max;
211         import std.range : iota, zip;
212         enum minDimension = min(this.dimension, box.dimension);
213         BoundingBox!(T, minDimension, options) result;
214         result.start = zip(this.start[0 .. minDimension], box.start[0 .. minDimension]).map!(max);
215         result.end = zip(this.end[0 .. minDimension], box.end[0 .. minDimension]).map!(min);
216         return result;
217     }
218 
219     /// Returns true if `box` intersects `this`.
220     auto intersects(Args...)(const auto ref BoundingBox!(T, Args) box) const
221     {
222         return !intersection(box).empty;
223     }
224 }
225 
226 /// Common alias for Bounding Boxes.
227 alias AABB = BoundingBox;