1 //          Copyright Ferdinand Majerech 2014.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 
7 /// Axis-aligned bounding boxes. Based on gfm.math.box box, not gl3n.
8 module gl3n_extra.box;
9 
10 import gl3n_extra.linalg;
11 
12 /// N-dimensional half-open interval [a, b[.
13 struct Box(T, size_t N)
14 {
15 @safe @nogc:
16     static assert(N > 0);
17 
18 public:
19     alias Vector!(T, N) Bound;
20 
21     Bound min; // not enforced, the box can have negative volume
22     Bound max;
23 
24     /// Construct a box which extends between 2 points.
25     /// Boundaries: min is inside the box, max is just outside.
26     this(Bound min_, Bound max_) pure nothrow
27     {
28         foreach(i; 0 .. N)
29         {
30             min.vector[i] = .min(min_.vector[i], max_.vector[i]);
31             max.vector[i] = .max(min_.vector[i], max_.vector[i]);
32         }
33     }
34 
35     static if (N == 1u)
36     this(T min_, T max_) pure nothrow
37     {
38         this(Bound(min_), Bound(max_));
39     }
40 
41     static if (N == 2u)
42     this(T min_x, T min_y, T max_x, T max_y) pure nothrow
43     {
44         this(Bound(min_x, min_y), Bound(max_x, max_y));
45     }
46 
47     static if (N == 3u)
48     this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow
49     {
50         this(Bound(min_x, min_y, min_z), Bound(max_x, max_y, max_z));
51     }
52 
53 
54     /// Returns: Dimensions of the box.
55     Bound size() pure const nothrow
56     {
57         return max - min;
58     }
59 
60     /// Returns: Area of the box.
61     T area() pure const nothrow
62     {
63         return cast(T)size.magnitude_squared;
64     }
65 
66     /// Returns: Center of the box.
67     Bound center() pure const nothrow
68     {
69         Bound.vt[Bound.dimension] vector = (min.vector[] + max.vector[]) / 2;
70         return Bound(vector);
71     }
72 
73     /// Returns: Width of the box, always applicable.
74     static if (N >= 1)
75     T width() pure const nothrow
76     {
77         return max.x - min.x;
78     }
79 
80     /// Returns: Height of the box, if applicable.
81     static if (N >= 2)
82     T height() pure const nothrow
83     {
84         return max.y - min.y;
85     }
86 
87     /// Returns: Depth of the box, if applicable.
88     static if (N >= 3)
89     T depth() pure const nothrow
90     {
91         return max.z - min.z;
92     }
93 
94     /// Returns: Signed volume of the box.
95     T volume() pure const nothrow
96     {
97         T res = 1;
98         Bound size = size();
99         foreach(i; 0 .. N) { res *= size.vector[i]; }
100         return res;
101     }
102 
103     /// Returns: true if it contains point.
104     bool contains(Bound point) pure const nothrow
105     {
106         foreach(i; 0 .. N)
107         {
108             if(!(point.vector[i] >= min.vector[i] && point.vector[i] < max.vector[i]))
109             {
110                 return false;
111             }
112         }
113 
114         return true;
115     }
116 
117     /// Returns: true if it contains box other.
118     bool contains(Box other) pure const nothrow
119     {
120         assert(isSorted());
121         assert(other.isSorted());
122 
123         foreach(i; 0 .. N)
124         {
125             if(other.min.vector[i] >= max.vector[i] || other.max.vector[i] < min.vector[i])
126             {
127                 return false;
128             }
129         }
130         return true;
131     }
132 
133     /// Euclidean squared distance from a point.
134     /// See_also: Numerical Recipes Third Edition (2007)
135     double squaredDistance(U)(Vector!(U, N) point) pure const nothrow
136     {
137         double distanceSquared = 0;
138         foreach(i; 0 .. N)
139         {
140             if(point.vector[i] < min.vector[i])
141             {
142                 distanceSquared += (point.vector[i] - min.vector[i]) ^^ 2;
143             }
144             if(point.vector[i] > max.vector[i])
145             {
146                 distanceSquared += (point.vector[i] - max.vector[i]) ^^ 2;
147             }
148         }
149         return distanceSquared;
150     }
151 
152     /// Euclidean distance from a point.
153     /// See_also: squaredDistance.
154     double distance(U)(Vector!(U, N) point)
155     {
156         return sqrt(squaredDistance(point));
157     }
158 
159     static if (N == 2u)
160     Box intersect(ref const(Box) o) pure const nothrow
161     {
162         assert(isSorted());
163         assert(o.isSorted());
164         auto xmin = .max(min.x, o.min.x);
165         auto ymin = .max(min.y, o.min.y);
166         auto xmax = .min(max.x, o.max.x);
167         auto ymax = .min(max.y, o.max.y);
168         return Box(xmin, ymin, xmax, ymax);
169     }
170 
171 
172     /// Extends the area of this Box.
173     Box grow(Bound space) pure const nothrow
174     {
175         Box res = this;
176         res.min -= space;
177         res.max += space;
178         return res;
179     }
180 
181     /// Shrink the area of this Box.
182     Box shrink(Bound space) pure const nothrow
183     {
184         return grow(-space);
185     }
186 
187     /// Extends the area of this Box.
188     Box grow(T space) pure const nothrow
189     {
190         return grow(Bound(space));
191     }
192 
193     /// Shrink the area of this Box.
194     Box shrink(T space) pure const nothrow
195     {
196         return shrink(Bound(space));
197     }
198 
199     /// Assign with another box.
200     ref Box opAssign(U)(U x) nothrow if (is(typeof(x.isBox)))
201     {
202         static if(is(U.element_t : T))
203         {
204             static if(U._size == _size)
205             {
206                 min = x.min;
207                 max = x.max;
208             }
209             else
210             {
211                 static assert(false, "no conversion between boxes with different dimensions");
212             }
213         }
214         else
215         {
216             static assert(false, Format!("no conversion from %s to %s", U.element_t.stringof, element_t.stringof));
217         }
218         return this;
219     }
220 
221     /// Returns: true if comparing equal boxes.
222     bool opEquals(U)(U other) pure const nothrow if (is(U : Box))
223     {
224         return (min == other.min) && (max == other.max);
225     }
226 
227     /// Comparison with another Box (for sorting).
228     int opCmp(ref const Box rhs) @safe pure nothrow const @nogc
229     {
230         const cmpMin = min.opCmp(rhs.min);
231         if(cmpMin != 0) { return cmpMin; }
232         return max.opCmp(rhs.max);
233     }
234 
235 private:
236     enum isBox = true;
237     enum _size = N;
238     alias T element_t;
239 
240     /// Returns: true if each dimension of the box is >= 0.
241     bool isSorted() pure const nothrow
242     {
243         foreach(i; 0 .. N) if(min.vector[i] > max.vector[i])
244         {
245             return false;
246         }
247         return true;
248     }
249 }
250 
251 alias box2i = Box!(int, 2);
252 alias box3i = Box!(int, 3);
253 alias box2  = Box!(float, 2);
254 alias box3  = Box!(float, 3);
255 alias box2d = Box!(double, 2);
256 alias box3d = Box!(double, 3);
257 unittest
258 {
259     box2i a = box2i(1, 2, 3, 4);
260     assert(a.width == 2);
261     assert(a.height == 2);
262     assert(a.volume == 4);
263     box2i b = box2i(vec2i(1, 2), vec2i(3, 4));
264     assert(a == b);
265     box2i c = box2i(0, 0, 1,1);
266     assert(c.contains(vec2i(0, 0)));
267     assert(!c.contains(vec2i(1, 1)));
268     assert(b.contains(b));
269 }