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