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/vector.d, _vector.d)
6  * Documentation:
7  * Coverage:
8  */
9 module liberty.math.vector;
10 import std.traits : isFloatingPoint;
11 /// T = type of elements.
12 /// N = number of elements (2, 3, 4).
13 struct Vector(T, ubyte N) if (N >= 2 && N <= 4) {
14     ///
15     alias size = N;
16     ///
17     alias type = T;
18 	/// Fields definition.
19     union {
20         ///
21         T[N] v;
22 	    ///
23         struct {
24             ///
25             T x;
26             ///
27             alias r = x;
28             ///
29             alias p = y;
30             ///
31             T y;
32             ///
33             alias g = y;
34             ///
35             alias q = y;
36             static if (N >= 3) {
37                 ///
38                 T z;
39                 ///
40                 alias b = z;
41                 ///
42                 alias s = z;
43             }
44             static if (N == 4) {
45                 ///
46                 T w;
47                 ///
48                 alias a = w;
49                 ///
50                 alias t = w;
51             }
52         }
53     }
54     static if (N == 2 && is(T == float)) {
55         /// Vector2 with values of 0.
56         static const Vector!(T, 2) zero = Vector!(T, 2)(0, 0);
57         /// Vector2 with values of 1.
58         static const Vector!(T, 2) one = Vector!(T, 2)(1, 1);
59         /// Vector2 pointing up.
60         static const Vector!(T, 2) up = Vector!(T, 2)(0, 1);
61         /// Vector2 pointing right.
62         static const Vector!(T, 2) right = Vector!(T, 2)(1, 0);
63         /// Vector2 pointing down.
64         static const Vector!(T, 2) down = Vector!(T, 2)(0, -1);
65         /// Vector2 pointing left.
66         static const Vector!(T, 2) left = Vector!(T, 2)(-1, 0);
67         ///
68         unittest {
69             assert(Vector!(T, 2).zero == Vector!(T, 2)(0, 0));
70             assert(Vector!(T, 2).one == Vector!(T, 2)(1, 1));
71             assert(Vector!(T, 2).up == Vector!(T, 2)(0, 1));
72             assert(Vector!(T, 2).right == Vector!(T, 2)(1, 0));
73             assert(Vector!(T, 2).down == Vector!(T, 2)(0, -1));
74             assert(Vector!(T, 2).left == Vector!(T, 2)(-1, 0));
75         }
76     } else static if (N == 3 && is(T == float)) {
77         /// Vector3 with values of 0.
78         static const Vector!(T, 3) zero = Vector!(T, 3)(0, 0, 0);
79         /// Vector3 with values of 1.
80         static const Vector!(T, 3) one = Vector!(T, 3)(1, 1, 1);
81         /// Vector3 pointing up.
82         static const Vector!(T, 3) up = Vector!(T, 3)(0, 1, 0);
83         /// Vector3 pointing right.
84         static const Vector!(T, 3) right = Vector!(T, 3)(1, 0, 0);
85         /// Vector3 pointing down.
86         static const Vector!(T, 3) down = Vector!(T, 3)(0, -1, 0);
87         /// Vector3 pointing left.
88         static const Vector!(T, 3) left = Vector!(T, 3)(-1, 0, 0);
89         /// Vector3 pointing forward.
90         static const Vector!(T, 3) forward = Vector!(T, 3)(0, 0, 1);
91         /// Vector3 pointing backward.
92         static const Vector!(T, 3) backward = Vector!(T, 3)(0, 0, -1);
93         ///
94         unittest {
95             assert(Vector!(T, 3).zero == Vector!(T, 3)(0, 0, 0));
96             assert(Vector!(T, 3).one == Vector!(T, 3)(1, 1, 1));
97             assert(Vector!(T, 3).up == Vector!(T, 3)(0, 1, 0));
98             assert(Vector!(T, 3).right == Vector!(T, 3)(1, 0, 0));
99             assert(Vector!(T, 3).down == Vector!(T, 3)(0, -1, 0));
100             assert(Vector!(T, 3).left == Vector!(T, 3)(-1, 0, 0));
101             assert(Vector!(T, 3).forward == Vector!(T, 3)(0, 0, 1));
102             assert(Vector!(T, 3).backward == Vector!(T, 3)(0, 0, -1));
103         }
104     }
105     ///
106     this(U...)(U values) pure nothrow @safe @nogc {
107         import std.meta : allSatisfy;
108         import std.traits : isAssignable;
109         enum bool isAsgn(U) = isAssignable!(T, U);
110         static if ((U.length == N) && allSatisfy!(isAsgn, U)) {
111             static foreach (i, x; values) {
112                 v[i] = x;
113             }
114         } /*else static if ((U.length == 2) && is(U[0] : Vector2!T) && is(U[1] : T) && is(this == Vector3!T)) {
115             v[0] = U[0].x;
116 			v[1] = U[0].y;
117 			v[2] = U[1];
118         }*/
119         else static if ((U.length == 1) && isAsgn!U && (!is(U[0] : Vector))) {
120             v[] = values[0];
121         } else static if (U.length == 1 && is(U[0] : T[])) {
122             v = values[0];
123         } else static if (U.length == 0) {
124             v[] = cast(T)0;
125         } else {
126             static assert(false, "Cannot create a vector from given arguments!");
127         }
128     }
129     ///
130     pure nothrow @safe unittest {
131         auto v1 = Vector2I(4, 2);
132         assert (v1.v == [4, 2]);
133         auto v2 = Vector2I(5);
134         assert (v2.v == [5, 5]);
135         auto v3 = Vector3I([1, 2, 3]);
136         assert (v3.v == [1, 2, 3]);
137         auto v4 = Vector4I();
138         assert (v4.v == [0, 0, 0, 0]);
139         auto v5 = Vector3I(Vector2I(1, 3), 5);
140         assert (v5.v == [1, 3, 5]);
141         auto v6 = Vector4I(Vector3I(-2, 1, 3), 5);
142         assert (v6.v == [-2, 1, 3, 5]);
143         auto v7 = Vector4I(Vector2I(1, 3), Vector2I(5, 4));
144         assert (v7.v == [1, 3, 5, 4]);
145     }
146     // TODO: remove this constructors
147 	static if (N == 3) {
148 		this(Vector2!T xy, T z) { v[0] = xy.x; v[1] = xy.y; v[2] = z; }
149 	} else static if (N == 4) {
150 		this(Vector2!T xy, Vector2!T zw) { v[0] = xy.x; v[1] = xy.y; v[2] = zw.x; v[3] = zw.y; }
151 		this(Vector3!T xyz, T w) { v[0] = xyz.x; v[1] = xyz.y; v[2] = xyz.z; v[3] = w; }
152 	}
153     /// Assign a Vector from a compatible type.
154     ref Vector opAssign(U)(U rhs) pure nothrow @safe @nogc {
155         static if (is(U : Vector)) {
156             static foreach (i; 0..N) {
157                 v[i] = rhs.v[i];
158             }
159         } else static if (is(U : T)) {
160             v[] = rhs;
161         } else static if (is(U : T[])) {
162             assert (rhs.length == N, "Static array's lenght must be equal to vector's length!");
163             v = rhs;
164         } else {
165             static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Vector.stringof);
166         }
167         return this;
168     }
169     ///
170     pure nothrow @safe unittest {
171         auto v1 = Vector3I();
172         v1 = Vector3I(7, 8, 9);
173         assert (v1.v == [7, 8, 9]);
174         auto v2 = Vector2F();
175         v2 = 3.4f;
176         assert (v2.v == [3.4f, 3.4f]);
177         auto v3 = Vector4I();
178         v3 = [1, 3, 5, -6];
179         assert (v3.v == [1, 3, 5, -6]);
180     }
181     ///
182     bool opEquals(U)(U rhs) pure nothrow const @safe @nogc {
183         static if (is(U : Vector)) {
184             static foreach (i; 0..N) {
185                 if (v[i] != rhs.v[i]) {
186                     return false;
187                 }
188             }
189         } else static if (is(U : T)) {
190             static foreach (i; 0..N) {
191                 if (v[i] != rhs) {
192                     return false;
193                 }
194             }
195         } else {
196             static assert (0, "Cannot compare a variable of type " ~ U.stringof ~ " with a variable of type " ~ Vector.stringof);
197         }
198         return true;
199     }
200     ///
201     pure nothrow @safe unittest {
202         auto v1 = Vector2F(4.5f, 6.0f);
203         assert (v1 == Vector2F(4.5f, 6.0f));
204         assert (v1 != Vector2F(4.5f, 8.0f));
205         auto v2 = Vector3I(-1, -1, -1);
206         assert (v2 == -1);
207         assert (v2 != 0);
208     }
209 	///
210 	Vector opUnary(string op)() pure const nothrow @safe @nogc if (op == "+" || op == "-" || op == "~" || op == "!") {
211 		Vector ret = void;
212 		static foreach (i; 0..N) {
213 			mixin("ret.v[i] = " ~ op ~ "v[i];");
214 		}
215 		return ret;
216 	}
217     ///
218     pure nothrow @safe @nogc unittest {
219         auto v1 = Vector2I(2, -5);
220         assert ((+v1).v == [2, -5]);
221         assert ((-v1).v == [-2, 5]);
222         assert ((~v1).v == [-3, 4]);
223         // *** BUG ***
224         //auto v2 = Vector!(bool, 2)(true, false);
225         //assert ((!v2).v == [false, true]);
226     }
227 	///
228 	Vector opBinary(string op, U)(U rhs) pure nothrow const @safe @nogc {
229 		Vector ret = void;
230         static if (is(U : Vector)) {
231             static foreach (i; 0..N) {
232 				mixin("ret.v[i] = cast(T)(v[i] " ~ op ~ " rhs.v[i]);");
233 			}
234         } else static if (is(U : T)) {
235 			static foreach (i; 0..N) {
236 				mixin("ret.v[i] = cast(T)(v[i] " ~ op ~ " rhs);");
237 			}
238 		} else {
239             static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Vector.stringof);
240 		}
241 		return ret;
242 	}
243     ///
244     pure nothrow @safe @nogc unittest {
245         auto v1 = Vector2I(4, -5);
246         auto v2 = Vector2I(7, 2);
247         auto v3 = v1 + v2;
248         assert (v3.v == [11, -3]);
249         v3 = v2 - 2;
250         assert (v3.v == [5, 0]);
251     }
252 	///
253     Vector opBinaryRight(string op, U)(U lhs) pure nothrow const @safe @nogc {
254         Vector ret = void;
255         static if (is(U : Vector)) {
256             static foreach (i; 0..N) {
257 		        mixin("ret.v[i] = cast(T)(lhs.v[i] " ~ op ~ " v[i]);");
258 	        }
259         } else static if (is(U : T)) {
260 	        static foreach (i; 0..N) {
261 		        mixin("ret.v[i] = cast(T)(lhs " ~ op ~ " v[i]);");
262 	        }
263         } else  {
264             static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Vector.stringof);
265         }
266 	    return ret;
267     }
268     ///
269     pure nothrow @safe @nogc unittest {
270         auto v1 = Vector2I(4, -5);
271         auto v2 = 3 + v1;
272         assert (v2.v == [7, -2]);
273     }
274     ///
275 	ref Vector opOpAssign(string op, U)(U rhs) pure nothrow @safe @nogc {
276         static if (is(U : Vector) || is(U : T)) {
277             mixin("this = this " ~ op ~ " rhs;");
278         } else {
279             static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Matrix.stringof);
280         }
281 		return this;
282 	}
283 	///
284     pure nothrow @safe @nogc unittest {
285         auto v1 = Vector2I(2, -8);
286         v1 += 3;
287         assert (v1.v == [5, -5]);
288         auto v2 = Vector2I(1, 2);
289         v1 -= v2;
290         assert (v1.v == [4, -7]);
291     }
292 	///
293 	ref T opIndex(size_t i) pure nothrow @safe @nogc {
294 		return v[i];
295 	}
296 	///
297 	ref const(T) opIndex(size_t i) pure nothrow const @safe @nogc {
298 		return v[i];
299 	}
300 	///
301 	T opIndexAssign(U : T)(U x, size_t i) pure nothrow @safe @nogc {
302 		return v[i] = x;
303 	}
304 	///
305 	U opCast(U)() pure nothrow const @safe @nogc if (isVector!U && U.size == size) {
306 		U ret = void;
307 		static foreach (i; 0..N) {
308 			mixin("ret.v[i] = cast(U.type)v[i];");
309 		}
310 		return ret;
311 	}
312     ///
313     pure nothrow @safe @nogc unittest {
314         auto v1 = Vector2F(1.3f, -5.7f);
315         auto v2 = cast(Vector2I)v1;
316         assert (v2.v == [1, -5]);
317     }
318 	///
319 	int opDollar() pure nothrow const @safe @nogc {
320 		return N;
321 	}
322 	///
323 	T[] opSlice() pure nothrow @safe @nogc {
324 		return v[];
325 	}
326 	///
327 	T[] opSlice(int a, int b) pure nothrow @safe @nogc {
328 		return v[a..b];
329 	}
330 	/// Returns a pointer to content.
331     inout(T)* ptr() pure nothrow inout @nogc @property {
332         return v.ptr;
333     }
334     /// Converts current vector to a string.
335     string toString() nothrow const @safe {
336         try {
337             import std.string : format;
338             return format("%s", v);
339         } catch (Exception e) {
340             assert (0);
341         }
342     }
343     ///
344     pure nothrow @safe unittest {
345         auto v = Vector!(uint, 2)(2, 4);
346         assert (v.toString() == "[2, 4]");
347     }
348 	///
349 	T squaredMagnitude() pure nothrow const @safe @nogc {
350 		T sumSquares = 0;
351 		static foreach (i; 0..N) {
352 			mixin("sumSquares += v[i] * v[i];");
353 		}
354 		return sumSquares;
355 	}
356 	///
357 	T squaredDistanceTo(Vector v) pure nothrow const @safe @nogc {
358 		return (v - this).squaredMagnitude();
359 	}
360 	static if (isFloatingPoint!T) {
361 		/// Returns Euclidean length.
362 		T magnitude() pure nothrow const @safe @nogc {
363             import liberty.math.functions : sqrt;
364 			return sqrt(squaredMagnitude());
365 		}
366 		/// Returns inverse of Euclidean length.
367 		T inverseMagnitude() pure nothrow const @safe @nogc {
368             import liberty.math.functions : sqrt;
369 			return 1 / sqrt(squaredMagnitude());
370 		}
371 		/// Faster but less accurate inverse of Euclidean length. Returns inverse of Euclidean length.
372 		T fastInverseMagnitude() pure nothrow const @safe @nogc {
373             import liberty.math.functions : inverseSqrt;
374 			return inverseSqrt(squaredMagnitude());
375 		}
376 		/// Returns Euclidean distance between this and other.
377 		T distanceTo(Vector other) pure nothrow const @safe @nogc {
378 			return (other - this).magnitude();
379 		}
380 		/// In-place normalization.
381 		void normalize() pure nothrow @safe @nogc {
382 			auto invMag = inverseMagnitude();
383 			v[] *= invMag;
384 		}
385 		/// Returns normalized vector.
386 		Vector normalized() pure nothrow const @safe @nogc {
387 			Vector res = this;
388 			res.normalize();
389 			return res;
390 		}
391 		/// Faster but less accurate in-place normalization.
392 		void fastNormalize() pure nothrow @safe @nogc {
393 			auto invLength = fastInverseMagnitude();
394 			v[] *= invLength;
395 		}
396 		/// Faster but less accurate vector normalization. Returns normalized vector.
397 		Vector fastNormalized() pure nothrow const @safe @nogc {
398 			Vector ret = this;
399 			ret.fastNormalize();
400 			return ret;
401 		}
402 		static if (N == 3) {
403 			/// Gets an orthogonal vector from a 3D vector.
404 			Vector getOrthogonalVector() pure nothrow const @safe @nogc {
405                 import liberty.math.functions : abs;
406 				return abs(x) > abs(z) ? Vector(-y, x, 0.0) : Vector(0.0, -z, y);
407 			}
408 		}
409 	}
410 }
411 /// True if T is some kind of Vector.
412 enum isVector(T) = is(T : Vector!U, U...);
413 ///
414 pure nothrow @safe @nogc unittest {
415     static assert (isVector!Vector2F);
416     static assert (isVector!Vector3I);
417     static assert (isVector!(Vector4!uint));
418     static assert (!isVector!real);
419 }
420 /// The numeric type used to measure coordinates of a vector.
421 alias DimensionType(T : Vector!U, U...) = U[0];
422 ///
423 pure nothrow @safe @nogc unittest {
424     static assert (is(DimensionType!Vector2F == float));
425     static assert (is(DimensionType!Vector3D == double));
426     static assert (is(DimensionType!Vector4U == uint));
427 }
428 ///
429 template Vector2(T) {
430 	alias Vector2 = Vector!(T, 2);
431 }
432 ///
433 template Vector3(T) {
434 	alias Vector3 = Vector!(T, 3);
435 }
436 ///
437 template Vector4(T) {
438 	alias Vector4 = Vector!(T, 4);
439 }
440 ///
441 alias Vector2I = Vector2!int ;
442 ///
443 alias Vector2U = Vector2!uint;
444 ///
445 alias Vector2F = Vector2!float;
446 ///
447 alias Vector2D = Vector2!double;
448 ///
449 alias Vector3I = Vector3!int;
450 ///
451 alias Vector3U = Vector3!uint;
452 ///
453 alias Vector3F = Vector3!float;
454 ///
455 alias Vector3D = Vector3!double;
456 ///
457 alias Vector4I = Vector4!int;
458 ///
459 alias Vector4U = Vector4!uint;
460 ///
461 alias Vector4F = Vector4!float;
462 ///
463 alias Vector4D = Vector4!double;
464 /// Element-wise minimum.
465 Vector!(T, N) minByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc {
466     import std.algorithm : min;
467     Vector!(T, N) ret = void;
468     static foreach (i; 0..N) {
469 	    ret.v[i] = min(a.v[i], b.v[i]);
470     }
471     return ret;
472 }
473 /// Element-wise maximum.
474 Vector!(T, N) maxByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc {
475     import std.algorithm : max;
476     Vector!(T, N) ret = void;
477     static foreach (i; 0..N) {
478 	    ret.v[i] = max(a.v[i], b.v[i]);
479     }
480     return ret;
481 }
482 /// Element-wise absolute value.
483 Vector!(T, N) absByElem(T, int N)(const Vector!(T, N) a) pure nothrow @safe @nogc {
484     import liberty.math.functions : abs;
485     Vector!(T, N) ret = void;
486     static foreach (i; 0..N) {
487 	    ret.v[i] = abs(a.v[i]);
488     }
489     return ret;
490 }
491 /// Returns dot product.
492 T dot(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc {
493     T sum = 0;
494     static foreach (i; 0..N) {
495 	    sum += a.v[i] * b.v[i];
496     }
497     return sum;
498 }
499 /// Returns 3D cross product.
500 Vector!(T, 3) cross(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow @safe @nogc {
501     return Vector!(T, 3)(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
502 }
503 /// Returns 3D reflect.
504 Vector!(T, N) reflect(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc {
505     return a - (2 * dot(b, a)) * b;
506 }
507 ///
508 static assert (Vector2F.sizeof == 8);
509 ///
510 static assert (Vector3D.sizeof == 24);
511 ///
512 static assert (Vector4I.sizeof == 16);