1 module gfm.math.box;
2 
3 import std.math,
4        std.traits;
5 
6 import gfm.math.vector, 
7        gfm.math.funcs;
8 
9 /// N-dimensional half-open interval [a, b[.
10 align(1) struct Box(T, size_t N)
11 {
12     align(1):
13     static assert(N > 0);
14 
15     public
16     {
17         alias Vector!(T, N) bound_t;
18 
19         bound_t min; // not enforced, the box can have negative volume
20         bound_t max;
21 
22         /// Construct a box which extends between 2 points.
23         /// Boundaries: min is inside the box, max is just outside.
24         this(bound_t min_, bound_t max_) pure nothrow @nogc
25         {
26             min = min_;
27             max = max_;
28         }
29 
30         static if (N == 1u)
31         {
32             this(T min_, T max_) pure nothrow @nogc
33             {
34                 min.x = min_;
35                 max.x = max_;
36             }
37         }
38 
39         static if (N == 2u)
40         {
41             this(T min_x, T min_y, T max_x, T max_y) pure nothrow @nogc
42             {
43                 min = bound_t(min_x, min_y);
44                 max = bound_t(max_x, max_y);
45             }
46         }
47 
48         static if (N == 3u)
49         {
50             this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow @nogc
51             {
52                 min = bound_t(min_x, min_y, min_z);
53                 max = bound_t(max_x, max_y, max_z);
54             }
55         }
56 
57 
58         @property
59         {
60             /// Returns: Dimensions of the box.
61             bound_t size() pure const nothrow @nogc
62             {
63                 return max - min;
64             }
65 
66             /// Returns: Center of the box.
67             bound_t center() pure const nothrow @nogc
68             {
69                 return (min + max) / 2;
70             }
71 
72             /// Returns: Width of the box, always applicable.
73             static if (N >= 1)
74             T width() pure const nothrow @property @nogc
75             {
76                 return max.x - min.x;
77             }
78 
79             /// Returns: Height of the box, if applicable.
80             static if (N >= 2)
81             T height() pure const nothrow @property @nogc
82             {
83                 return max.y - min.y;
84             }
85 
86             /// Returns: Depth of the box, if applicable.
87             static if (N >= 3)
88             T depth() pure const nothrow @property @nogc
89             {
90                 return max.z - min.z;
91             }
92 
93             /// Returns: Signed volume of the box.
94             T volume() pure const nothrow @nogc
95             {
96                 T res = 1;
97                 bound_t size = size();
98                 for(size_t i = 0; i < N; ++i)
99                     res *= size[i];
100                 return res;
101             }
102         }
103 
104         /// Returns: true if it contains point.
105         bool contains(bound_t point) pure const nothrow @nogc
106         {
107             assert(isSorted());
108             for(size_t i = 0; i < N; ++i)
109                 if ( !(point[i] >= min[i] && point[i] < max[i]) )
110                     return false;
111 
112             return true;
113         }
114 
115         /// Returns: true if it contains box other.
116         bool contains(Box other) pure const nothrow @nogc
117         {
118             assert(isSorted());
119             assert(other.isSorted());
120 
121             for(size_t i = 0; i < N; ++i)
122                 if (other.min[i] >= max[i] || other.max[i] < min[i])
123                     return false;
124             return true;
125         }
126 
127         /// Euclidean squared distance from a point.
128         /// See_also: Numerical Recipes Third Edition (2007)
129         double squaredDistance(bound_t point) pure const nothrow @nogc
130         {
131             assert(isSorted());
132             double distanceSquared = 0;
133             for (size_t i = 0; i < N; ++i)
134             {
135                 if (point[i] < min[i])
136                     distanceSquared += (point[i] - min[i]) ^^ 2;
137 
138                 if (point[i] > max[i])
139                     distanceSquared += (point[i] - max[i]) ^^ 2;
140             }
141             return distanceSquared;
142         }
143 
144         /// Euclidean distance from a point.
145         /// See_also: squaredDistance.
146         double distance(bound_t point) pure const nothrow @nogc
147         {
148             return sqrt(squaredDistance(point));
149         }
150 
151         /// Euclidean squared distance from another box.
152         /// See_also: Numerical Recipes Third Edition (2007)
153         double squaredDistance(Box o) pure const nothrow @nogc
154         {
155             assert(isSorted());
156             assert(o.isSorted());
157             double distanceSquared = 0;
158             for (size_t i = 0; i < N; ++i)
159             {
160                 if (o.max[i] < min[i])
161                     distanceSquared += (o.max[i] - min[i]) ^^ 2;
162 
163                 if (o.min[i] > max[i])
164                     distanceSquared += (o.min[i] - max[i]) ^^ 2;
165             }
166             return distanceSquared;
167         }
168 
169         /// Euclidean distance from another box.
170         /// See_also: squaredDistance.
171         double distance(Box o) pure const nothrow @nogc
172         {
173             return sqrt(squaredDistance(o));
174         }
175 
176         /// Assumes sorted boxes.
177         /// Returns: Intersection of two boxes.
178         Box intersection(Box o) pure const nothrow @nogc
179         {
180             assert(isSorted());
181             assert(o.isSorted());
182             Box result;
183             for (size_t i = 0; i < N; ++i)
184             {
185                 T maxOfMins = (min.v[i] > o.min.v[i]) ? min.v[i] : o.min.v[i];
186                 T minOfMaxs = (max.v[i] < o.max.v[i]) ? max.v[i] : o.max.v[i];
187                 result.min.v[i] = maxOfMins;
188                 result.max.v[i] = minOfMaxs;
189             }
190             return result;
191         }
192 
193         deprecated("Renamed to intersection") alias intersect = intersection;
194 
195         /// Assumes sorted boxes.
196         /// Returns: true if boxes overlap.
197         bool intersects(Box other)
198         {
199             Box inter = this.intersection(other);
200             return inter.isSorted() && inter.volume() != 0;
201         }
202 
203         /// Extends the area of this Box.
204         Box grow(bound_t space) pure const nothrow @nogc
205         {
206             Box res = this;
207             res.min -= space;
208             res.max += space;
209             return res;
210         }
211 
212         /// Shrink the area of this Box. The box might became unsorted.
213         Box shrink(bound_t space) pure const nothrow @nogc
214         {
215             return grow(-space);
216         }
217 
218         /// Extends the area of this Box.
219         Box grow(T space) pure const nothrow @nogc
220         {
221             return grow(bound_t(space));
222         }
223 
224         /// Shrink the area of this Box.
225         Box shrink(T space) pure const nothrow @nogc
226         {
227             return shrink(bound_t(space));
228         }
229 
230         /// Expand the box to include point.
231         Box expand(bound_t point) pure const nothrow @nogc
232         {
233           import vector = gfm.math.vector;
234           return Box(vector.min(min, point), vector.max(max, point));
235         }
236 
237         /// Returns: true if each dimension of the box is >= 0.
238         bool isSorted() pure const nothrow @nogc
239         {
240             for(size_t i = 0; i < N; ++i)
241             {
242                 if (min[i] > max[i])
243                     return false;
244             }
245             return true;
246         }
247 
248         /// Assign with another box.
249         ref Box opAssign(U)(U x) nothrow @nogc if (is(typeof(x.isBox)))
250         {
251             static if(is(U.element_t : T))
252             {
253                 static if(U._size == _size)
254                 {
255                     min = x.min;
256                     max = x.max;
257                 }
258                 else
259                 {
260                     static assert(false, "no conversion between boxes with different dimensions");
261                 }
262             }
263             else
264             {
265                 static assert(false, Format!("no conversion from %s to %s", U.element_t.stringof, element_t.stringof));
266             }
267             return this;
268         }
269 
270         /// Returns: true if comparing equal boxes.
271         bool opEquals(U)(U other) pure const nothrow @nogc if (is(U : Box))
272         {
273             return (min == other.min) && (max == other.max);
274         }
275     }
276 
277     private
278     {
279         enum isBox = true;
280         enum _size = N;
281         alias T element_t;
282     }
283 }
284 
285 /// Instanciate to use a 2D box.
286 template box2(T)
287 {
288     alias Box!(T, 2u) box2;
289 }
290 
291 /// Instanciate to use a 3D box.
292 template box3(T)
293 {
294     alias Box!(T, 3u) box3;
295 }
296 
297 
298 alias box2!int box2i; /// 2D box with integer coordinates.
299 alias box3!int box3i; /// 3D box with integer coordinates.
300 alias box2!float box2f; /// 2D box with float coordinates.
301 alias box3!float box3f; /// 3D box with float coordinates.
302 alias box2!double box2d; /// 2D box with double coordinates.
303 alias box3!double box3d; /// 3D box with double coordinates.
304 
305 unittest
306 {
307     box2i a = box2i(1, 2, 3, 4);
308     assert(a.width == 2);
309     assert(a.height == 2);
310     assert(a.volume == 4);
311     box2i b = box2i(vec2i(1, 2), vec2i(3, 4));
312     assert(a == b);
313     box2i c = box2i(0, 0, 1,1);
314     assert(c.contains(vec2i(0, 0)));
315     assert(!c.contains(vec2i(1, 1)));
316     assert(b.contains(b));
317     box2i d = c.expand(vec2i(3, 3));
318     assert(d.contains(vec2i(2, 2)));
319 }