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