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         /// min is inside the box.
24         /// max is just outside.
25         this(bound_t min_, bound_t max_) pure nothrow
26         {
27             min = min_;
28             max = max_;
29         }
30 
31         static if (N == 1u)
32         {
33             this(T min_, T max_) pure nothrow
34             {
35                 min.x = min_;
36                 max.x = max_;
37             }
38         }
39 
40         static if (N == 2u)
41         {
42             this(T min_x, T min_y, T max_x, T max_y) pure nothrow
43             {
44                 min = bound_t(min_x, min_y);
45                 max = bound_t(max_x, max_y);
46             }
47         }
48 
49         static if (N == 3u)
50         {
51             this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow
52             {
53                 min = bound_t(min_x, min_y, min_z);
54                 max = bound_t(max_x, max_y, max_z);
55             }
56         }
57 
58 
59         @property
60         {
61             /// Returns: Dimensions of the box.
62             bound_t size() pure const nothrow
63             {
64                 return max - min;
65             }
66 
67             /// Returns: Center of the box.
68             bound_t center() pure const nothrow
69             {
70                 return (min + max) / 2;
71             }
72 
73             /// Returns: Box width of the box.
74             static if (N >= 1)
75             T width() pure const nothrow @property
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 @property
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 @property
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_t size = size();
99                 for(size_t i = 0; i < N; ++i)
100                     res *= size[i];
101                 return res;
102             }
103         }
104 
105         /// Returns: true if it contains point.
106         bool contains(bound_t point) pure const nothrow
107         {
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
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
130         {
131             double distanceSquared = 0;
132             for (size_t i = 0; i < N; ++i)
133             {
134                 if (point[i] < min[i])
135                     distanceSquared += (point[i] - min[i]) ^^ 2;
136 
137                 if (point[i] > max[i])
138                     distanceSquared += (point[i] - max[i]) ^^ 2;
139             }
140             return distanceSquared;
141         }
142 
143         /// Euclidean distance from a point.
144         /// See_also: squaredDistance.
145         double distance(bound_t point)
146         {
147             return sqrt(squaredDistance(point));
148         }
149 
150         static if (N == 2u)
151         {
152             Box intersect(ref const(Box) o) pure const nothrow
153             {
154                 assert(isSorted());
155                 assert(o.isSorted());
156                 auto xmin = .max(min.x, o.min.x);
157                 auto ymin = .max(min.y, o.min.y);
158                 auto xmax = .min(max.x, o.max.x);
159                 auto ymax = .min(max.y, o.max.y);
160                 return Box(xmin, ymin, xmax, ymax);
161             }
162         }
163 
164 
165         /// Extends the area of this Box.
166         Box grow(bound_t space) pure const nothrow
167         {
168             Box res = this;
169             res.min -= space;
170             res.max += space;
171             return res;
172         }
173 
174         /// Shrink the area of this Box.
175         Box shrink(bound_t space) pure const nothrow
176         {
177             return grow(-space);
178         }
179 
180         /// Extends the area of this Box.
181         Box grow(T space) pure const nothrow
182         {
183             return grow(bound_t(space));
184         }
185 
186         /// Shrink the area of this Box.
187         Box shrink(T space) pure const nothrow
188         {
189             return shrink(bound_t(space));
190         }
191 
192         /// Returns: true if each dimension of the box is >= 0.
193         bool isSorted() pure const nothrow
194         {
195             for(size_t i = 0; i < N; ++i)
196             {
197                 if (min[i] > max[i])
198                     return false;
199             }
200             return true;
201         }
202 
203         /// Assign with another box.
204         ref Box opAssign(U)(U x) nothrow if (is(typeof(x.isBox)))
205         {
206             static if(is(U.element_t : T))
207             {
208                 static if(U._size == _size)
209                 {
210                     min = x.min;
211                     max = x.max;
212                 }
213                 else
214                 {
215                     static assert(false, "no conversion between boxes with different dimensions");
216                 }
217             }
218             else
219             {
220                 static assert(false, Format!("no conversion from %s to %s", U.element_t.stringof, element_t.stringof));
221             }
222             return this;
223         }
224 
225         /// Returns: true if comparing equal boxes.
226         bool opEquals(U)(U other) pure const nothrow if (is(U : Box))
227         {
228             return (min == other.min) && (max == other.max);
229         }
230     }
231 
232     private
233     {
234         enum isBox = true;
235         enum _size = N;
236         alias T element_t;
237     }
238 }
239 
240 /// Instanciate to use a 2D box.
241 template box2(T)
242 {
243     alias Box!(T, 2u) box2;
244 }
245 
246 /// Instanciate to use a 3D box.
247 template box3(T)
248 {
249     alias Box!(T, 3u) box3;
250 }
251 
252 
253 alias box2!int box2i; /// 2D box with integer coordinates.
254 alias box3!int box3i; /// 3D box with integer coordinates.
255 alias box2!float box2f; /// 2D box with float coordinates.
256 alias box3!float box3f; /// 3D box with float coordinates.
257 alias box2!double box2d; /// 2D box with double coordinates.
258 alias box3!double box3d; /// 3D box with double coordinates.
259 
260 unittest
261 {
262     box2i a = box2i(1, 2, 3, 4);
263     assert(a.width == 2);
264     assert(a.height == 2);
265     assert(a.volume == 4);
266     box2i b = box2i(vec2i(1, 2), vec2i(3, 4));
267     assert(a == b);
268     box2i c = box2i(0, 0, 1,1);
269     assert(c.contains(vec2i(0, 0)));
270     assert(!c.contains(vec2i(1, 1)));
271     assert(b.contains(b));
272 }