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, _box.d)
6  * Documentation:
7  * Coverage:
8  */
9 module liberty.math.box;
10 import liberty.math.vector : Vector;
11 ///
12 struct Box(T, int N) if (N >= 1 && N <= 3) {
13 	///
14 	enum dim = N;
15 	///
16 	alias type = T;
17 	///
18 	alias BoundType = Vector!(T, N);
19 	///
20 	BoundType min;
21 	///
22 	BoundType max;
23 	///
24 	this(BoundType min, BoundType max) pure nothrow @safe @nogc {
25 		this.min = min;
26 		this.max = max;
27 	}
28 	static if (N == 1) {
29 		///
30 		this(T min, T max) pure nothrow @safe @nogc {
31 			this.min.x = min;
32 			this.max.x = max;
33 		}
34 	}
35 	static if (N == 2) {
36 		///
37 		this(T min_x, T min_y, T max_x, T max_y) pure nothrow @safe @nogc {
38 			min = BoundType(min_x, min_y);
39 			max = BoundType(max_x, max_y);
40 		}
41 	}
42 	static if (N == 3) {
43 		this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow @safe @nogc {
44 			min = BoundType(min_x, min_y, min_z);
45 			max = BoundType(max_x, max_y, max_z);
46 		}
47 	}
48 	/// Returns dimensions of the box.
49 	BoundType size() pure nothrow const @safe @nogc @property {
50 		return max - min;
51 	}
52 	/// Returns center of the box.
53 	BoundType center() pure nothrow const @safe @nogc @property {
54 		return (min + max) / 2;
55 	}
56 	/// Returns width of the box.
57 	T width() pure nothrow const @safe @nogc @property {
58 		return max.x - min.x;
59 	}
60 	static if (N >= 2) {
61 		/// Returns height of the box.
62 		T height() pure nothrow const @safe @nogc @property {
63 			return max.y - min.y;
64 		}
65 	}
66 	static if (N >= 3) {
67 		/// Returns depth of the box.
68 		T depth() pure nothrow const @safe @nogc @property {
69 			return max.z - min.z;
70 		}
71 	}
72 	/// Returns signed volume of the box.
73 	T volume() pure nothrow const @safe @nogc @property {
74 		T ret = 1;
75 		BoundType size = size();
76 		static foreach (i; 0..N) {
77 			ret *= size[i];
78 		}
79 		return ret;
80 	}
81 	/// Returns true if empty.
82 	bool empty() pure nothrow const @safe @nogc @property {
83 		BoundType size = size();
84 		static foreach (i; 0..N) {
85 			if (min[i] == max[i]) {
86 				return true;
87 			}
88 		}
89 		return false;
90 	}
91 	/// Returns true if it contains point.
92 	bool contains(BoundType point) pure nothrow const @safe @nogc
93 	in {
94 		assert(isSorted());
95 	} do {
96 		static foreach (i; 0..N) {
97 			if (!(point[i] >= min[i] && point[i] < max[i])) {
98 				return false;
99 			}
100 		}
101 		return true;
102 	}
103 	///
104 	bool contains(Box other) pure nothrow const @safe @nogc
105 	in {
106 		assert(isSorted());
107 		assert(other.isSorted());
108 	} do {
109 		static foreach (i; 0..N) {
110 			if ((other.min[i] < min[i]) || (other.max[i] > max[i])) {
111 				return false;
112 			}
113 		}
114 		return true;
115 	}
116 	///
117 	real squaredDistance(BoundType point) pure nothrow const @safe @nogc
118 	in {
119 		assert(isSorted());
120 	} do {
121 		real distanceSquared = 0;
122 		static foreach (i; 0..N) {
123 			if (point[i] < min[i]) {
124 				distanceSquared += (point[i] - min[i]) ^^ 2;
125 			}
126 			if (point[i] > max[i]) {
127 				distanceSquared += (point[i] - max[i]) ^^ 2;
128 			}
129 		}
130 		return distanceSquared;
131 	}
132 	///
133 	real distance(BoundType point) pure nothrow const @safe @nogc {
134 		import liberty.math.functions : sqrt;
135 		return sqrt(squaredDistance(point));
136 	}
137 	///
138 	real squaredDistance(Box o) pure nothrow const @safe @nogc
139 	in {
140 		assert(isSorted());
141 		assert(o.isSorted());
142 	} do {
143 		real distanceSquared = 0;
144 		static foreach (i; 0..N) {
145 			if (o.max[i] < min[i]) {
146 				distanceSquared += (o.max[i] - min[i]) ^^ 2;
147 			}
148 			if (o.min[i] > max[i]) {
149 				distanceSquared += (o.min[i] - max[i]) ^^ 2;
150 			}
151 		}
152 		return distanceSquared;
153 	}
154 	///
155 	real distance(Box o) pure nothrow const @safe @nogc {
156 		import liberty.math.functions : sqrt;
157 		return sqrt(squaredDistance(o));
158 	}
159 	///
160 	Box intersection(Box o) pure nothrow const @safe @nogc
161 	in {
162 		assert(isSorted());
163 		assert(o.isSorted());
164 	} do {
165 		if (empty()) {
166 			return this;
167 		}
168 		if (o.empty()) {
169 			return o;
170 		}
171 		Box ret;
172 		T maxOfMins = 0;
173 		T minOfMaxs = 0;
174 		static foreach (i; 0..N) {
175 			maxOfMins = (min.v[i] > o.min.v[i]) ? min.v[i] : o.min.v[i];
176 			minOfMaxs = (max.v[i] < o.max.v[i]) ? max.v[i] : o.max.v[i];
177 			ret.min.v[i] = maxOfMins;
178 			ret.max.v[i] = minOfMaxs >= maxOfMins ? minOfMaxs : maxOfMins;
179 		}
180 		return ret;
181 	}
182 	///
183 	bool intersects(Box other) pure nothrow const @safe @nogc {
184 		Box inter = this.intersection(other);
185 		return inter.isSorted() && !inter.empty();
186 	}
187 	///
188 	Box grow(BoundType space) pure nothrow const @safe @nogc {
189 		Box ret = this;
190 		ret.min -= space;
191 		ret.max += space;
192 		return ret;
193 	}
194 	///
195 	Box shrink(BoundType space) pure nothrow const @safe @nogc {
196 		return grow(-space);
197 	}
198 	///
199 	Box grow(T space) pure nothrow const @safe @nogc {
200 		return grow(BoundType(space));
201 	}
202 	///
203 	Box translate(BoundType offset) pure nothrow const @safe @nogc {
204 		return Box(min + offset, max + offset);
205 	}
206 	///
207 	Box shrink(T space) pure nothrow const @safe @nogc {
208 		return shrink(BoundType(space));
209 	}
210 	///
211 	Box expand(BoundType point) pure nothrow const @safe @nogc {
212 		import liberty.math.vector : minByElem, maxByElem;
213 		return Box(minByElem(min, point), maxByElem(max, point));
214 	}
215 	///
216 	Box expand(Box other) pure nothrow const @safe @nogc
217 	in {
218 		assert(isSorted());
219 		assert(other.isSorted());
220 	} do {
221 		if (empty()) {
222 			return other;
223 		}
224 		if (other.empty()) {
225 			return this;
226 		}
227 		Box ret;
228 		T minOfMins = 0;
229 		T maxOfMaxs = 0;
230 		static foreach (i; 0..N) {
231 			minOfMins = (min.v[i] < other.min.v[i]) ? min.v[i] : other.min.v[i];
232 			maxOfMaxs = (max.v[i] > other.max.v[i]) ? max.v[i] : other.max.v[i];
233 			ret.min.v[i] = minOfMins;
234 			ret.max.v[i] = maxOfMaxs;
235 		}
236 		return ret;
237 	}
238 	///
239 	bool isSorted() pure nothrow const @safe @nogc {
240 		static foreach (i; 0..N) {
241 			if (min[i] > max[i]) {
242 				return false;
243 			}
244 		}
245 		return true;
246 	}
247 	///
248 	ref Box opAssign(U)(U x) nothrow @safe @nogc if (isBox!U) {
249 		static if(is(U.type : T)) {
250 			static if(U.dim == dim) {
251 				min = x.min;
252 				max = x.max;
253 			} else  {
254 				static assert(false, "No conversion between boxes with different dimensions available!");
255 			}
256 		} else {
257 			static assert(false, "No conversion from " ~ U.type.stringof ~ " to " ~ type.stringof ~ " available!");
258 		}
259 		return this;
260 	}
261 	///
262 	bool opEquals(U)(U other) pure nothrow const @safe @nogc if (is(U : Box)) {
263 		return (min == other.min) && (max == other.max);
264 	}
265 	/// Cast to other box types.
266 	U opCast(U)() pure nothrow const @safe @nogc if (isBox!U) {
267 		U b = void;
268 		static foreach (i; 0..N) {
269 			b.min[i] = cast(U.type)(min[i]);
270 			b.max[i] = cast(U.type)(max[i]);
271 		}
272 		return b;
273 	}
274 	static if (N == 2) {
275 		///
276 		static Box rectangle(T x, T y, T width, T height) pure nothrow @safe @nogc {
277 			return Box(x, y, x + width, y + height);
278 		}
279 	}
280 }
281 ///
282 alias DimensionType(T : Box!U, U...) = U[0];
283 ///
284 unittest {
285 	static assert (is(DimensionType!Box2F == float));
286 	static assert (is(DimensionType!Box3D == double));
287 }
288 ///
289 enum isBox(T) = is(T : Box!U, U...);
290 ///
291 pure nothrow @safe @nogc unittest {
292 	static assert (isBox!Box2F);
293 	static assert (isBox!Box3I);
294 	static assert (isBox!(Box!(double, 2)));
295 	static assert (!isBox!(Vector!(float, 3)));
296 }
297 ///
298 template Box2(T) {
299 	alias Box2 = Box!(T, 2);
300 }
301 ///
302 template Box3(T) {
303 	alias Box3 = Box!(T, 3);
304 }
305 ///
306 alias Box2I = Box2!int;
307 ///
308 alias Box3I = Box3!int;
309 ///
310 alias Box2F = Box2!float;
311 ///
312 alias Box3F = Box3!float;
313 ///
314 alias Box2D = Box2!double;
315 ///
316 alias Box3D = Box3!double;