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