1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:			    $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/math/box.d)
6  * Documentation:
7  * Coverage:
8 **/
9 module liberty.math.box;
10 
11 import liberty.math.vector;
12 
13 /**
14  *
15 **/
16 struct Box(T, int N) if (N >= 1 && N <= 3) {
17   /**
18    *
19   **/
20 	enum dim = N;
21 
22   /**
23    *
24   **/
25 	alias type = T;
26 
27   /**
28    *
29   **/
30 	alias BoundType = Vector!(T, N);
31 
32   /**
33    *
34   **/
35 	BoundType min;
36 
37   /**
38    *
39   **/
40 	BoundType max;
41 
42   /**
43    *
44   **/
45 	this(BoundType min, BoundType max)   {
46 		this.min = min;
47 		this.max = max;
48 	}
49 
50 	static if (N == 1) {
51     /**
52      *
53     **/
54 		this(T min, T max)   {
55 			this.min.x = min;
56 			this.max.x = max;
57 		}
58 	}
59 
60 	static if (N == 2) {
61     /**
62      *
63     **/
64 		this(T min_x, T min_y, T max_x, T max_y)   {
65 			min = BoundType(min_x, min_y);
66 			max = BoundType(max_x, max_y);
67 		}
68 	}
69 
70 	static if (N == 3) {
71     /**
72      *
73     **/
74 		this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z)   {
75 			min = BoundType(min_x, min_y, min_z);
76 			max = BoundType(max_x, max_y, max_z);
77 		}
78 	}
79 
80   /**
81    * Returns box dimensions.
82   **/
83 	BoundType getSize()   const {
84 		return max - min;
85 	}
86 
87   /**
88    * Returns box center.
89   **/
90 	BoundType getCenter()   const {
91 		return (min + max) / 2;
92 	}
93 
94   /**
95    * Returns box width .
96   **/
97 	T getWidth()   const {
98 		return max.x - min.x;
99 	}
100 
101 	static if (N >= 2) {
102     /**
103      * Returns box height.
104     **/
105 		T getHeight()   const {
106 			return max.y - min.y;
107 		}
108 	}
109 
110   
111 	static if (N >= 3) {
112     /**
113      * Returns depth of the box.
114     **/
115 		T getDepth()   const {
116 			return max.z - min.z;
117 		}
118 	}
119 
120   /**
121    * Returns signed volume of the box.
122   **/
123 	T getVolume()   const {
124 		T ret = 1;
125 		BoundType size = getSize();
126 		static foreach (i; 0..N) {
127 			ret *= size[i];
128 		}
129 		return ret;
130 	}
131 
132   /**
133    * Returns true if empty.
134   **/
135 	bool isEmpty()   const {
136 		//const BoundType size = getSize();
137 		static foreach (i; 0..N) {
138 			if (min[i] == max[i]) {
139 				return true;
140 			}
141 		}
142 		return false;
143 	}
144 
145   /**
146    * Returns true if it contains point
147   **/
148 	bool contains(BoundType point)   const
149 	in {
150 		assert(isSorted());
151 	} do {
152 		static foreach (i; 0..N) {
153 			if (!(point[i] >= min[i] && point[i] < max[i])) {
154 				return false;
155 			}
156 		}
157 		return true;
158 	}
159 
160   /**
161    *
162   **/
163 	bool contains(Box other)   const
164 	in {
165 		assert(isSorted());
166 		assert(other.isSorted());
167 	} do {
168 		static foreach (i; 0..N) {
169 			if ((other.min[i] < min[i]) || (other.max[i] > max[i])) {
170 				return false;
171 			}
172 		}
173 		return true;
174 	}
175 
176   /**
177    *
178   **/
179 	real getSquaredDistance(BoundType point)   const
180 	in {
181 		assert(isSorted());
182 	} do {
183 		real distanceSquared = 0;
184 		static foreach (i; 0..N) {
185 			if (point[i] < min[i]) {
186 				distanceSquared += (point[i] - min[i]) ^^ 2;
187 			}
188 			if (point[i] > max[i]) {
189 				distanceSquared += (point[i] - max[i]) ^^ 2;
190 			}
191 		}
192 		return distanceSquared;
193 	}
194 
195   /**
196    *
197   **/
198 	real getDistance(BoundType point)   const {
199 		import liberty.math.functions : sqrt;
200 		return sqrt(getSquaredDistance(point));
201 	}
202 
203   /**
204    *
205   **/
206 	real getSquaredDistance(Box o)   const
207 	in {
208 		assert(isSorted());
209 		assert(o.isSorted());
210 	} do {
211 		real distanceSquared = 0;
212 		static foreach (i; 0..N) {
213 			if (o.max[i] < min[i]) {
214 				distanceSquared += (o.max[i] - min[i]) ^^ 2;
215 			}
216 			if (o.min[i] > max[i]) {
217 				distanceSquared += (o.min[i] - max[i]) ^^ 2;
218 			}
219 		}
220 		return distanceSquared;
221 	}
222 
223   /**
224    *
225   **/
226 	real getDistance(Box o)   const {
227 		import liberty.math.functions : sqrt;
228 		return sqrt(getSquaredDistance(o));
229 	}
230 
231   /**
232    *
233   **/
234 	Box getIntersection(Box o)   const
235 	in {
236 		assert(isSorted());
237 		assert(o.isSorted());
238 	} do {
239 		if (isEmpty()) {
240 			return this;
241 		}
242 		if (o.isEmpty()) {
243 			return o;
244 		}
245 		Box ret;
246 		T maxOfMins = 0;
247 		T minOfMaxs = 0;
248 		static foreach (i; 0..N) {
249 			maxOfMins = (min.v[i] > o.min.v[i]) ? min.v[i] : o.min.v[i];
250 			minOfMaxs = (max.v[i] < o.max.v[i]) ? max.v[i] : o.max.v[i];
251 			ret.min.v[i] = maxOfMins;
252 			ret.max.v[i] = minOfMaxs >= maxOfMins ? minOfMaxs : maxOfMins;
253 		}
254 		return ret;
255 	}
256 
257   /**
258    *
259   **/
260 	bool intersects(Box other)   const {
261 		Box inter = this.getIntersection(other);
262 		return inter.isSorted() && !inter.isEmpty();
263 	}
264 
265   /**
266    *
267   **/
268 	Box grow(BoundType space)   const {
269 		Box ret = this;
270 		ret.min -= space;
271 		ret.max += space;
272 		return ret;
273 	}
274 
275   /**
276    *
277   **/
278 	Box shrink(BoundType space)   const {
279 		return grow(-space);
280 	}
281 
282   /**
283    *
284   **/
285 	Box grow(T space)   const {
286 		return grow(BoundType(space));
287 	}
288 
289   /**
290    *
291   **/
292 	Box translate(BoundType offset)   const {
293 		return Box(min + offset, max + offset);
294 	}
295 
296   /**
297    *
298   **/
299 	Box shrink(T space)   const {
300 		return shrink(BoundType(space));
301 	}
302 
303   /**
304    *
305   **/
306 	Box expand(BoundType point)   const {
307 		import liberty.math.vector : minByElem, maxByElem;
308 		return Box(minByElem(min, point), maxByElem(max, point));
309 	}
310 
311   /**
312    *
313   **/
314 	Box expand(Box other)   const
315 	in {
316 		assert(isSorted());
317 		assert(other.isSorted());
318 	} do {
319 		if (isEmpty()) {
320 			return other;
321 		}
322 		if (other.isEmpty()) {
323 			return this;
324 		}
325 		Box ret;
326 		T minOfMins = 0;
327 		T maxOfMaxs = 0;
328 		static foreach (i; 0..N) {
329 			minOfMins = (min.v[i] < other.min.v[i]) ? min.v[i] : other.min.v[i];
330 			maxOfMaxs = (max.v[i] > other.max.v[i]) ? max.v[i] : other.max.v[i];
331 			ret.min.v[i] = minOfMins;
332 			ret.max.v[i] = maxOfMaxs;
333 		}
334 		return ret;
335 	}
336 
337   /**
338    *
339   **/
340 	bool isSorted()   const {
341 		static foreach (i; 0..N) {
342 			if (min[i] > max[i]) {
343 				return false;
344 			}
345 		}
346 		return true;
347 	}
348 
349   /**
350    *
351   **/
352 	ref Box opAssign(U)(U x)  if (isBox!U) {
353 		static if(is(U.type : T)) {
354 			static if(U.dim == dim) {
355 				min = x.min;
356 				max = x.max;
357 			} else  {
358 				static assert(false, "No conversion between boxes with different dimensions available!");
359 			}
360 		} else {
361 			static assert(false, "No conversion from " ~ U.type.stringof ~ " to " ~ type.stringof ~ " available!");
362 		}
363 		return this;
364 	}
365 
366   /**
367    *
368   **/
369 	bool opEquals(U)(U other)   const if (is(U : Box)) {
370 		return (min == other.min) && (max == other.max);
371 	}
372 
373   /**
374    *
375   **/
376   //size_t toHash()   const {
377   //  size_t hash = min.hashOf();
378   //  hash = max.hashOf(hash);
379   //  return hash;
380   //}
381 
382   /**
383    * Cast to other box types.
384   **/
385 	U opCast(U)()   const if (isBox!U) {
386 		U b = void;
387 		static foreach (i; 0..N) {
388 			b.min[i] = cast(U.type)(min[i]);
389 			b.max[i] = cast(U.type)(max[i]);
390 		}
391 		return b;
392 	}
393 
394 	static if (N == 2) {
395     /**
396      *
397     **/
398 		static Box rectangle(T x, T y, T width, T height)   {
399 			return Box(x, y, x + width, y + height);
400 		}
401 	}
402 }
403 
404 /**
405  *
406 **/
407 template Box2(T) {
408 	alias Box2 = Box!(T, 2);
409 }
410 
411 /**
412  *
413 **/
414 template Box3(T) {
415 	alias Box3 = Box!(T, 3);
416 }
417 
418 /**
419  *
420 **/
421 alias Box2I = Box2!int;
422 
423 /**
424  *
425 **/
426 alias Box3I = Box3!int;
427 
428 /**
429  *
430 **/
431 alias Box2F = Box2!float;
432 
433 /**
434  *
435 **/
436 alias Box3F = Box3!float;
437 
438 /**
439  *
440 **/
441 alias Box2D = Box2!double;
442 
443 /**
444  *
445 **/
446 alias Box3D = Box3!double;