1 module gfm.math.vector;
2 
3 import std.traits,
4        std.math,
5        std.string;
6 
7 import gfm.math.funcs;
8 
9 static if( __VERSION__ < 2066 ) private enum nogc = 1;
10 
11 /**
12  * Generic 1D small vector.
13  * Params:
14  *    N = number of elements
15  *    T = type of elements
16  */
17 align(1) struct Vector(T, int N)
18 {
19 align(1):
20 nothrow:
21     public
22     {
23         static assert(N >= 1);
24 
25         // fields definition
26         union
27         {
28             T[N] v;
29             struct
30             {
31                 static if (N >= 1)
32                 {
33                     T x;
34                     alias x r;
35                 }
36                 static if (N >= 2)
37                 {
38                     T y;
39                     alias y g;
40                 }
41                 static if (N >= 3)
42                 {
43                     T z;
44                     alias z b;
45                 }
46                 static if (N >= 4)
47                 {
48                     T w;
49                     alias w a;
50                 }
51             }
52         }
53 
54         static if (N == 2)
55         {
56             /// Creates a vector of 2 elements.
57             @nogc this(X : T, Y : T)(X x_, Y y_) pure nothrow
58             {
59                 x = x_;
60                 y = y_;
61             }
62         }
63         else static if (N == 3)
64         {
65             /// Creates a vector of 3 elements.
66             @nogc this(X : T, Y : T, Z : T)(X x_, Y y_, Z z_) pure nothrow
67             {
68                 x = x_;
69                 y = y_;
70                 z = z_;
71             }
72 
73             /// Creates a vector of 3 elements.
74             @nogc this(X : T, Y : T)(Vector!(X, 2) xy_, Y z_) pure nothrow
75             {
76                 x = xy_.x;
77                 y = xy_.y;
78                 z = z_;
79             }
80 
81             /// Creates a vector of 3 elements.
82             @nogc this(X : T, Y : T)(X x_, Vector!(Y, 2) yz_) pure nothrow
83             {
84                 x = x_;
85                 y = yz_.x;
86                 z = yz_.y;
87             }
88         }
89         else static if (N == 4)
90         {
91             /// Creates a vector of 4 elements.
92             @nogc this(X : T, Y : T, Z : T, W : T)(X x_, Y y_, Z z_, W w_) pure nothrow
93             {
94                 x = x_;
95                 y = y_;
96                 z = z_;
97                 w = w_;
98             }
99 
100             /// Creates a vector of 4 elements.
101             @nogc this(X : T, Y : T)(Vector!(X, 2) xy_, Vector!(Y, 2)zw_) pure nothrow
102             {
103                 x = xy_.x;
104                 y = xy_.y;
105                 z = zw_.x;
106                 w = zw_.y;
107             }
108 
109             /// Creates a vector of 4 elements.
110             @nogc this(X : T, Y : T, Z : T)(Vector!(X, 2) xy_, Y z_, Z w_) pure nothrow
111             {
112                 x = xy_.x;
113                 y = xy_.y;
114                 z = z_;
115                 w = w_;
116             }
117 
118             /// Creates a vector of 4 elements.
119             @nogc this(X : T, Y : T)(Vector!(X, 3) xyz_, Y w_) pure nothrow
120             {
121                 x = xyz_.x;
122                 y = xyz_.y;
123                 z = xyz_.z;
124                 w = w_;
125             }
126 
127             /// Creates a vector of 4 elements.
128             @nogc this(X : T, Y : T)(X x_, Vector!(X, 3) yzw_) pure nothrow
129             {
130                 x = x_;
131                 y = yzw_.x;
132                 z = yzw_.y;
133                 w = yzw_.z;
134             }
135         }
136 
137         /// Construct a Vector from a value.
138         @nogc this(U)(U x) pure nothrow
139         {
140             opAssign!U(x);
141         }
142 
143         /// Assign a Vector from a compatible type.
144         @nogc ref Vector opAssign(U)(U x) pure nothrow if (is(U: T))
145         {
146             v[] = x; // copy to each component
147             return this;
148         }
149 
150         /// Assign a Vector with a static array type.
151         @nogc ref Vector opAssign(U)(U arr) pure nothrow if ((isStaticArray!(U) && is(typeof(arr[0]) : T) && (arr.length == N)))
152         {
153             for (int i = 0; i < N; ++i)
154                 v[i] = arr[i];
155             return this;
156         }
157 
158         /// Assign with a dynamic array.
159         /// Size is checked in debug-mode.
160         @nogc ref Vector opAssign(U)(U arr) pure nothrow if (isDynamicArray!(U) && is(typeof(arr[0]) : T))
161         {
162             assert(arr.length == N);
163             for (int i = 0; i < N; ++i)
164                 v[i] = arr[i];
165             return this;
166         }
167 
168         /// Assign from a samey Vector.
169         @nogc ref Vector opAssign(U)(U u) pure nothrow if (is(U : Vector))
170         {
171             static if (N <= 4)
172             {
173                 x = u.x;
174                 static if(N >= 2) y = u.y;
175                 static if(N >= 3) z = u.z;
176                 static if(N >= 4) w = u.w;
177             }
178             else
179             {
180                 for (int i = 0; i < N; ++i)
181                 {
182                     v[i] = u.v[i];
183                 }
184             }
185             return this;
186         }
187 
188         /// Assign from other vectors types (same size, compatible type).
189         @nogc ref Vector opAssign(U)(U x) pure nothrow if (is(typeof(U._isVector))
190                                                        && is(U._T : T)
191                                                        && (!is(U: Vector))
192                                                        && (U._N == _N))
193         {
194             for (int i = 0; i < N; ++i)
195                 v[i] = x.v[i];
196             return this;
197         }
198 
199         /// Returns: a pointer to content.
200         @nogc inout(T)* ptr() pure inout nothrow @property
201         {
202             return v.ptr;
203         }
204 
205         /// Converts to a pretty string.
206         string toString() const nothrow
207         {
208             try
209                 return format("%s", v);
210             catch (Exception e)
211                 assert(false); // should not happen since format is right
212         }
213 
214         @nogc bool opEquals(U)(U other) pure const nothrow
215             if (is(U : Vector))
216         {
217             for (int i = 0; i < N; ++i)
218             {
219                 if (v[i] != other.v[i])
220                 {
221                     return false;
222                 }
223             }
224             return true;
225         }
226 
227         @nogc bool opEquals(U)(U other) pure const nothrow
228             if (isConvertible!U)
229         {
230             Vector conv = other;
231             return opEquals(conv);
232         }
233 
234         @nogc Vector opUnary(string op)() pure const nothrow
235             if (op == "+" || op == "-" || op == "~" || op == "!")
236         {
237             Vector res = void;
238             for (int i = 0; i < N; ++i)
239             {
240                 mixin("res.v[i] = " ~ op ~ "v[i];");
241             }
242             return res;
243         }
244 
245         @nogc ref Vector opOpAssign(string op, U)(U operand) pure nothrow
246             if (is(U : Vector))
247         {
248             for (int i = 0; i < N; ++i)
249             {
250                 mixin("v[i] " ~ op ~ "= operand.v[i];");
251             }
252             return this;
253         }
254 
255         @nogc ref Vector opOpAssign(string op, U)(U operand) pure nothrow if (isConvertible!U)
256         {
257             Vector conv = operand;
258             return opOpAssign!op(conv);
259         }
260 
261         @nogc Vector opBinary(string op, U)(U operand) pure const nothrow
262             if (is(U: Vector) || (isConvertible!U))
263         {
264             Vector temp = this;
265             return temp.opOpAssign!op(operand);
266         }
267 
268         @nogc Vector opBinaryRight(string op, U)(U operand) pure const nothrow if (isConvertible!U)
269         {
270             Vector temp = operand;
271             return temp.opOpAssign!op(this);
272         }
273 
274         @nogc ref T opIndex(size_t i) pure nothrow
275         {
276             return v[i];
277         }
278 
279         @nogc ref const(T) opIndex(size_t i) pure const nothrow
280         {
281             return v[i];
282         }
283 
284         @nogc T opIndexAssign(U : T)(U x, size_t i) pure nothrow
285         {
286             return v[i] = x;
287         }
288 
289 
290         /// Implements swizzling.
291         ///
292         /// Example:
293         /// ---
294         /// vec4i vi = [4, 1, 83, 10];
295         /// assert(vi.zxxyw == [83, 4, 4, 1, 10]);
296         /// ---
297         @nogc @property auto opDispatch(string op, U = void)() pure const nothrow if (isValidSwizzle!(op))
298         {
299             alias Vector!(T, op.length) returnType;
300             returnType res = void;
301             enum indexTuple = swizzleTuple!op;
302             foreach(i, index; indexTuple)
303                 res.v[i] = v[index];
304             return res;
305         }
306 
307         /// Support swizzling assignment like in shader languages.
308         ///
309         /// Example:
310         /// ---
311         /// vec3f v = [0, 1, 2];
312         /// v.yz = v.zx;
313         /// assert(v == [0, 2, 0]);
314         /// ---
315         @nogc @property void opDispatch(string op, U)(U x) pure
316             if ((op.length >= 2)
317                 && (isValidSwizzleUnique!op)                   // v.xyy will be rejected
318                 && is(typeof(Vector!(T, op.length)(x)))) // can be converted to a small vector of the right size
319         {
320             Vector!(T, op.length) conv = x;
321             enum indexTuple = swizzleTuple!op;
322             foreach(i, index; indexTuple)
323                 v[index] = conv[i];
324         }
325 
326         /// Casting to small vectors of the same size.
327         /// Example:
328         /// ---
329         /// vec4f vf;
330         /// vec4d vd = cast!(vec4d)vf;
331         /// ---
332         @nogc U opCast(U)() pure const nothrow if (is(typeof(U._isVector)) && (U._N == _N))
333         {
334             U res = void;
335             for (int i = 0; i < N; ++i)
336             {
337                 res.v[i] = cast(U._T)v[i];
338             }
339             return res;
340         }
341 
342         /// Implement slices operator overloading.
343         /// Allows to go back to slice world.
344         /// Returns: length.
345         @nogc int opDollar() pure const nothrow
346         {
347             return N;
348         }
349 
350         /// Returns: a slice which covers the whole Vector.
351         @nogc T[] opSlice() pure nothrow
352         {
353             return v[];
354         }
355 
356         // vec[a..b]
357         @nogc T[] opSlice(int a, int b) pure nothrow
358         {
359             return v[a..b];
360         }
361 
362         /// Returns: squared length.
363         @nogc T squaredLength() pure const nothrow
364         {
365             T sumSquares = 0;
366             for (int i = 0; i < N; ++i)
367             {
368                 sumSquares += v[i] * v[i];
369             }
370             return sumSquares;
371         }
372 
373         // Returns: squared Euclidean distance.
374         @nogc T squaredDistanceTo(Vector v) pure const nothrow
375         {
376             return (v - this).squaredLength();
377         }
378 
379         static if (isFloatingPoint!T)
380         {
381             /// Returns: Euclidean length
382             @nogc T length() pure const nothrow
383             {
384                 return sqrt(squaredLength());
385             }
386 
387             /// Returns: Euclidean distance between this and other.
388             @nogc T distanceTo(Vector other) pure const nothrow
389             {
390                 return (other - this).length();
391             }
392 
393             /// In-place normalization.
394             @nogc void normalize() pure nothrow
395             {
396                 auto invLength = 1 / length();
397                 for (int i = 0; i < N; ++i)
398                 {
399                     v[i] *= invLength;
400                 }
401             }
402 
403             /// Returns: Normalized vector.
404             @nogc Vector normalized() pure const nothrow
405             {
406                 Vector res = this;
407                 res.normalize();
408                 return res;
409             }
410 
411             static if (N == 3)
412             {
413                 /// Gets an orthogonal vector from a 3-dimensional vector.
414                 /// Doesn’t normalise the output.
415                 /// Authors: Sam Hocevar
416                 /// See_also: Source at $(WEB lolengine.net/blog/2013/09/21/picking-orthogonal-vector-combing-coconuts).
417                 @nogc Vector getOrthogonalVector() pure const nothrow
418                 {
419                     return abs(x) > abs(z) ? Vector(-y, x, 0.0) : Vector(0.0, -z, y);
420                 }
421             }
422         }
423     }
424 
425     private
426     {
427         enum _isVector = true; // do we really need this? I don't know.
428 
429         enum _N = N;
430         alias T _T;
431 
432         // define types that can be converted to this, but are not the same type
433         template isConvertible(T)
434         {
435             enum bool isConvertible = (!is(T : Vector))
436             && is(typeof(
437                 {
438                     T x;
439                     Vector v = x;
440                 }()));
441         }
442 
443         // define types that can't be converted to this
444         template isForeign(T)
445         {
446             enum bool isForeign = (!isConvertible!T) && (!is(T: Vector));
447         }
448 
449         template isValidSwizzle(string op, int lastSwizzleClass = -1)
450         {
451             static if (op.length == 0)
452                 enum bool isValidSwizzle = true;
453             else
454             {
455                 enum len = op.length;
456                 enum int swizzleClass = swizzleClassify!(op[0]);
457                 enum bool swizzleClassValid = (lastSwizzleClass == -1 || (swizzleClass == lastSwizzleClass));
458                 enum bool isValidSwizzle = (swizzleIndex!(op[0]) != -1)
459                                          && swizzleClassValid
460                                          && isValidSwizzle!(op[1..len], swizzleClass);
461             }
462         }
463 
464         template searchElement(char c, string s)
465         {
466             static if (s.length == 0)
467             {
468                 enum bool result = false;
469             }
470             else
471             {
472                 enum string tail = s[1..s.length];
473                 enum bool result = (s[0] == c) || searchElement!(c, tail).result;
474             }
475         }
476 
477         template hasNoDuplicates(string s)
478         {
479             static if (s.length == 1)
480             {
481                 enum bool result = true;
482             }
483             else
484             {
485                 enum tail = s[1..s.length];
486                 enum bool result = !(searchElement!(s[0], tail).result) && hasNoDuplicates!(tail).result;
487             }
488         }
489 
490         // true if the swizzle has at the maximum one time each letter
491         template isValidSwizzleUnique(string op)
492         {
493             static if (isValidSwizzle!op)
494                 enum isValidSwizzleUnique = hasNoDuplicates!op.result;
495             else
496                 enum bool isValidSwizzleUnique = false;
497         }
498 
499         template swizzleIndex(char c)
500         {
501             static if((c == 'x' || c == 'r') && N >= 1)
502                 enum swizzleIndex = 0;
503             else static if((c == 'y' || c == 'g') && N >= 2)
504                 enum swizzleIndex = 1;
505             else static if((c == 'z' || c == 'b') && N >= 3)
506                 enum swizzleIndex = 2;
507             else static if ((c == 'w' || c == 'a') && N >= 4)
508                 enum swizzleIndex = 3;
509             else
510                 enum swizzleIndex = -1;
511         }
512 
513         template swizzleClassify(char c)
514         {
515             static if(c == 'x' || c == 'y' || c == 'z' || c == 'w')
516                 enum swizzleClassify = 0;
517             else static if(c == 'r' || c == 'g' || c == 'b' || c == 'a')
518                 enum swizzleClassify = 1;
519             else
520                 enum swizzleClassify = -1;
521         }
522 
523         template swizzleTuple(string op)
524         {
525             enum opLength = op.length;
526             static if (op.length == 0)
527                 enum swizzleTuple = [];
528             else
529                 enum swizzleTuple = [ swizzleIndex!(op[0]) ] ~ swizzleTuple!(op[1..op.length]);
530         }
531     }
532 }
533 
534 private string definePostfixAliases(string type)
535 {
536     return "alias " ~ type ~ "!byte "   ~ type ~ "b;\n"
537          ~ "alias " ~ type ~ "!ubyte "  ~ type ~ "ub;\n"
538          ~ "alias " ~ type ~ "!short "  ~ type ~ "s;\n"
539          ~ "alias " ~ type ~ "!ushort " ~ type ~ "us;\n"
540          ~ "alias " ~ type ~ "!int "    ~ type ~ "i;\n"
541          ~ "alias " ~ type ~ "!uint "   ~ type ~ "ui;\n"
542          ~ "alias " ~ type ~ "!long "   ~ type ~ "l;\n"
543          ~ "alias " ~ type ~ "!ulong "  ~ type ~ "ul;\n"
544          ~ "alias " ~ type ~ "!float "  ~ type ~ "f;\n"
545          ~ "alias " ~ type ~ "!double " ~ type ~ "d;\n";
546 }
547 
548 template vec2(T) { alias Vector!(T, 2) vec2; }
549 template vec3(T) { alias Vector!(T, 3) vec3; }
550 template vec4(T) { alias Vector!(T, 4) vec4; }
551 
552 mixin(definePostfixAliases("vec2"));
553 mixin(definePostfixAliases("vec3"));
554 mixin(definePostfixAliases("vec4"));
555 
556 
557 /// Element-wise minimum.
558 @nogc Vector!(T, N) min(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow
559 {
560     import std.algorithm: min;
561     Vector!(T, N) res = void;
562     for(int i = 0; i < N; ++i)
563         res[i] = min(a.v[i], b.v[i]);
564     return res;
565 }
566 
567 /// Element-wise maximum.
568 @nogc Vector!(T, N) max(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow
569 {
570     import std.algorithm: max;
571     Vector!(T, N) res = void;
572     for(int i = 0; i < N; ++i)
573         res[i] = max(a.v[i], b.v[i]);
574     return res;
575 }
576 
577 /// Returns: Dot product.
578 @nogc T dot(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow
579 {
580     T sum = 0;
581     for(int i = 0; i < N; ++i)
582         sum += a.v[i] * b.v[i];
583     return sum;
584 }
585 
586 /// Returns: 3D cross product.
587 /// Thanks to vuaru for corrections.
588 @nogc Vector!(T, 3) cross(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow
589 {
590     return Vector!(T, 3)(a.y * b.z - a.z * b.y,
591                          a.z * b.x - a.x * b.z,
592                          a.x * b.y - a.y * b.x);
593 }
594 
595 /// 3D reflect, like the GLSL function.
596 /// Returns: a reflected by normal b.
597 @nogc Vector!(T, 3) reflect(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow
598 {
599     return a - (2 * dot(b, a)) * b;
600 }
601 
602 
603 /// Returns: angle between vectors.
604 /// See_also: "The Right Way to Calculate Stuff" at $(WEB www.plunk.org/~hatch/rightway.php)
605 @nogc T angleBetween(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow
606 {
607     auto aN = a.normalized();
608     auto bN = b.normalized();
609     auto dp = dot(aN, bN);
610 
611     if (dp < 0)
612         return PI - 2 * asin((-bN-aN).length / 2);
613     else
614         return 2 * asin((bN-aN).length / 2);
615 }
616 
617 
618 unittest
619 {
620     static assert(vec2i.isValidSwizzle!"xyx");
621     static assert(!vec2i.isValidSwizzle!"xyz");
622     static assert(vec4i.isValidSwizzle!"brra");
623     static assert(!vec4i.isValidSwizzle!"rgyz");
624     static assert(vec2i.isValidSwizzleUnique!"xy");
625     static assert(vec2i.isValidSwizzleUnique!"yx");
626     static assert(!vec2i.isValidSwizzleUnique!"xx");
627 
628     assert(vec2l(0, 1) == vec2i(0, 1));
629 
630     int[2] arr = [0, 1];
631     int[] arr2 = new int[2];
632     arr2[] = arr[];
633     vec2i a = vec2i([0, 1]);
634     vec2i a2 = vec2i(0, 1);
635     immutable vec2i b = vec2i(0);
636     assert(b[0] == 0 && b[1] == 0);
637     vec2i c = arr;
638     vec2l d = arr2;
639     assert(a == a2);
640     assert(a == c);
641     assert(vec2l(a) == vec2l(a));
642     assert(vec2l(a) == d);
643 
644     vec4i x = [4, 5, 6, 7];
645     assert(x == x);
646     --x[0];
647     assert(x[0] == 3);
648     ++x[0];
649     assert(x[0] == 4);
650     x[1] &= 1;
651     x[2] = 77 + x[2];
652     x[3] += 3;
653     assert(x == [4, 1, 83, 10]);
654     assert(x.xxywz == [4, 4, 1, 10, 83]);
655     assert(x.xxxxxxx == [4, 4, 4, 4, 4, 4, 4]);
656     assert(x.abgr == [10, 83, 1, 4]);
657     assert(a != b);
658 
659     vec2l e = a;
660     vec2l f = a + b;
661     assert(f == vec2l(a));
662 
663     vec3ui g = vec3i(78,9,4);
664     g ^= vec3i(78,9,4);
665     assert(g == vec3ui(0));
666     //g[0..2] = 1u;
667     //assert(g == [2, 1, 0]);
668 
669     assert(vec2i(4, 5) + 1 == vec2i(5,6));
670     assert(vec2i(4, 5) - 1 == vec2i(3,4));
671     assert(1 + vec2i(4, 5) == vec2i(5,6));
672     assert(vec3f(1,1,1) * 0 == 0);
673     assert(1.0 * vec3d(4,5,6) == vec3f(4,5.0f,6.0));
674 
675     auto dx = vec2i(1,2);
676     auto dy = vec2i(4,5);
677     auto dp = dot(dx, dy);
678     assert(dp == 14 );
679 
680     vec3i h = cast(vec3i)(vec3d(0.5, 1.1, -2.2));
681     assert(h == [0, 1, -2]);
682     assert(h[] == [0, 1, -2]);
683     assert(h[1..3] == [1, -2]);
684     assert(h.zyx == [-2, 1, 0]);
685 
686     h.yx = vec2i(5, 2); // swizzle assignment
687 
688     assert(h.xy == [2, 5]);
689     assert(-h[1] == -5);
690     assert(++h[0] == 3);
691 
692     //assert(h == [-2, 1, 0]);
693     assert(!__traits(compiles, h.xx = h.yy));
694     vec4ub j;
695 
696     assert(lerp(vec2f(-10, -1), vec2f(10, 1), 0.5) == vec2f(0, 0));
697 }
698