1 /// Vector linear algebra module
2 module gfx.math.vec;
3 
4 import std.traits : isFloatingPoint, isNumeric, isStaticArray;
5 
6 pure @safe nothrow @nogc:
7 
8 alias Vec2(T) = Vec!(T, 2);
9 alias Vec3(T) = Vec!(T, 3);
10 alias Vec4(T) = Vec!(T, 4);
11 
12 alias DVec(int N) = Vec!(double, N);
13 alias FVec(int N) = Vec!(float, N);
14 alias IVec(int N) = Vec!(int, N);
15 
16 alias DVec2 = Vec!(double, 2);
17 alias DVec3 = Vec!(double, 3);
18 alias DVec4 = Vec!(double, 4);
19 
20 alias FVec2 = Vec!(float, 2);
21 alias FVec3 = Vec!(float, 3);
22 alias FVec4 = Vec!(float, 4);
23 
24 alias IVec2 = Vec!(int, 2);
25 alias IVec3 = Vec!(int, 3);
26 alias IVec4 = Vec!(int, 4);
27 
28 
29 /// Build a Vec whose component type and size is deducted from arguments.
30 auto vec(Comps...)(Comps comps)
31 if (Comps.length > 0 && !(Comps.length == 1 && isStaticArray!(Comps[0])))
32 {
33     import std.traits : CommonType, Unqual;
34     alias FlatTup = CompTup!(Comps);
35     alias CompType = Unqual!(CommonType!(typeof(FlatTup.init.expand)));
36     alias ResVec = Vec!(CompType, FlatTup.length);
37     return ResVec (comps);
38 }
39 
40 /// ditto
41 auto vec(Arr)(in Arr arr)
42 if (isStaticArray!Arr)
43 {
44     import std.traits : Unqual;
45     alias CompType = Unqual!(typeof(arr[0]));
46     alias ResVec = Vec!(CompType, Arr.length);
47     return ResVec(arr);
48 }
49 
50 ///
51 unittest
52 {
53     import std.algorithm : equal;
54     import std.traits : Unqual;
55 
56     immutable v1 = vec (1, 2, 4.0, 0); // CommonType!(int, double) is double
57     immutable int[4] arr1 = [1, 2, 4, 0];
58 
59     static assert( is(Unqual!(typeof(v1)) == DVec4) );
60 
61     assert(equal(v1.data, arr1[]));
62 
63     immutable int[3] arr2 = [0, 1, 2];
64     immutable v2 = vec (arr2);
65     static assert( is(Unqual!(typeof(v2)) == IVec3) );
66     assert(equal(v2.data, arr2[]));
67 }
68 
69 /// Build a Vec with specified component type T and size deducted from arguments.
70 template vec (T) if (isNumeric!T)
71 {
72     auto vec (Comps...)(Comps comps)
73     if (Comps.length > 0 && !(Comps.length == 1 && isStaticArray!(Comps[0])))
74     {
75         alias ResVec = Vec!(T, numComps!Comps);
76         return ResVec (comps);
77     }
78     auto vec (ArrT)(in ArrT arr)
79     if (isStaticArray!ArrT)
80     {
81         alias ResVec = Vec!(T, ArrT.length);
82         return ResVec (arr);
83     }
84 }
85 
86 /// ditto
87 alias dvec = vec!double;
88 /// ditto
89 alias fvec = vec!float;
90 /// ditto
91 alias ivec = vec!int;
92 
93 ///
94 unittest
95 {
96     import std.algorithm : equal;
97     import std.traits : Unqual;
98 
99     immutable v1 = dvec (1, 2, 4, 0); // none of the args is double
100     immutable int[4] arr1 = [1, 2, 4, 0];
101     static assert( is(Unqual!(typeof(v1)) == DVec4) );
102     assert(equal(v1.data, arr1[]));
103 
104     immutable int[3] arr2 = [0, 1, 2];
105     immutable v2 = fvec(arr2);
106     static assert( is(Unqual!(typeof(v2)) == FVec3) );
107     assert(equal(v2.data, arr2[]));
108 
109     immutable int[4] arr3 = [0, 1, 2, 3];
110     immutable v3 = dvec (1, 2);
111     immutable v4 = dvec (0, v3, 3);
112     static assert( is(Unqual!(typeof(v4)) == DVec4) );
113     assert(equal(v4.data, arr3[]));
114 }
115 
116 /// Build a Vec with specified size and type deducted from arguments
117 template vec (size_t N)
118 {
119     import std.traits : isDynamicArray, Unqual;
120 
121     auto vec (Arr)(in Arr arr) if (isDynamicArray!Arr)
122     in {
123         assert(arr.length == N);
124     }
125     body {
126         alias CompType = Unqual!(typeof(arr[0]));
127         return Vec!(CompType, N)(arr);
128     }
129 
130     auto vec (T)(in T comp) if (isNumeric!T)
131     {
132         return Vec!(Unqual!T, N)(comp);
133     }
134 }
135 
136 /// ditto
137 alias vec2 = vec!2;
138 /// ditto
139 alias vec3 = vec!3;
140 /// ditto
141 alias vec4 = vec!4;
142 
143 ///
144 unittest
145 {
146     import std.algorithm : equal;
147     import std.array : staticArray;
148     import std.traits : Unqual;
149 
150     const double[4] arr1 = [1, 2, 4, 0];
151     const v1 = vec4 (arr1[]);            // passing slice to erase compile-time length
152     static assert( is(Unqual!(typeof(v1)) == DVec4) );
153     assert(equal(v1.data, arr1[]));
154 
155     const int comp = 2;
156     const v2 = vec4 (comp);
157     static assert( is(Unqual!(typeof(v2)) == IVec4) );
158     assert(equal(v2.data, staticArray([2, 2, 2, 2])[]));
159 }
160 
161 
162 struct Vec(T, size_t N) if(N > 0 && isNumeric!T)
163 {
164     import std.meta : Repeat;
165     import std.traits : isArray, isImplicitlyConvertible;
166     import std.typecons : Tuple;
167 
168     package T[N] _rep = 0;
169 
170     static if (isFloatingPoint!T)
171     {
172         /// A vector with each component set as NaN (not a number)
173         enum nan = Vec!(T, N)(T.nan);
174     }
175 
176     /// The number of components in the vector
177     enum length = N;
178 
179     /// The type of a vector component
180     alias Component = T;
181 
182     /// Build a vector from its components
183     /// Can be called with any combination of scalar or vectors, as long as
184     /// the total number of scalar fit with length
185     this (Comps...)(in Comps comps) pure nothrow @nogc @safe
186     if (Comps.length > 1)
187     {
188         import std.conv : to;
189 
190         enum nc = numComps!(Comps);
191         static assert(nc == N,
192             "type sequence "~Comps.stringof~" (size "~nc.to!string~
193             ") do not fit the size of "~Vec!(T, N).stringof~" (size "~N.to!string~").");
194 
195         const ct = compTup(comps);
196 
197         static if (
198             is(typeof([ ct.expand ])) &&
199             isImplicitlyConvertible!(typeof([ ct.expand ]), typeof(_rep))
200         )
201         {
202             _rep = [ ct.expand ];
203         }
204         else {
205             static foreach (i; 0 .. ct.length) {
206                 _rep[i] = cast(T)ct[i];
207             }
208         }
209     }
210 
211     /// Build a vector from an array.
212     this(Arr)(in Arr arr)
213     if (isArray!Arr)
214     {
215         static if (isStaticArray!Arr) {
216             static assert(Arr.length == length);
217         }
218         else {
219             assert(arr.length == length);
220         }
221         static if (is(typeof(arr[0]) == T)) {
222             _rep[] = arr;
223         }
224         else {
225             static assert(isImplicitlyConvertible!(typeof(arr[0]), T));
226             static foreach (i; 0..N)
227             {
228                 _rep[i] = arr[i];
229             }
230         }
231     }
232 
233     /// Build a vector with all components assigned to one value
234     this(in T comp)
235     {
236         _rep = comp;
237     }
238 
239     // Tuple representation
240 
241     /// Alias to a type sequence holding all components
242     alias CompSeq = Repeat!(N, T);
243 
244     /// All components in a tuple
245     @property Tuple!(CompSeq) tup() const
246     {
247         return Tuple!(CompSeq)(_rep);
248     }
249 
250     /// Return the data of the array
251     @property inout(T)[] data() inout
252     {
253         return _rep[];
254     }
255 
256     /// All components in a static array
257     @property T[length] array() const
258     {
259         return _rep;
260     }
261 
262     // access by name and swizzling
263 
264     private template compNameIndex(char c)
265     {
266         static if (c == 'x' || c == 'r' || c == 's' || c == 'u')
267         {
268             enum compNameIndex = 0;
269         }
270         else static if (c == 'y' || c == 'g' || c == 't' || c == 'v')
271         {
272             enum compNameIndex =  1;
273         }
274         else static if (c == 'z' || c == 'b' || c == 'p')
275         {
276             static assert (N >= 3, "component "~c~" is only accessible with 3 or more components vectors");
277             enum compNameIndex =  2;
278         }
279         else static if (c == 'w' || c == 'a' || c == 'q')
280         {
281             static assert (N >= 4, "component "~c~" is only accessible with 4 or more components vectors");
282             enum compNameIndex =  3;
283         }
284         else
285         {
286             static assert (false, "component "~c~" is not recognized");
287         }
288     }
289 
290     /// Access the component by name.
291     @property T opDispatch(string name)() const
292     if (name.length == 1)
293     {
294         return _rep[compNameIndex!(name[0])];
295     }
296 
297     /// Assign the component by name.
298     @property void opDispatch(string name)(in T val)
299     if (name.length == 1)
300     {
301         _rep[compNameIndex!(name[0])] = val;
302     }
303 
304     /// Access the components by swizzling.
305     @property auto opDispatch(string name)() const
306     if (name.length > 1)
307     {
308         Vec!(T, name.length) res;
309         static foreach (i; 0 .. name.length) {
310             res[i] = _rep[compNameIndex!(name[i])];
311         }
312         return res;
313     }
314 
315     /// Assign the components by swizzling.
316     @property void opDispatch(string name, U, size_t num)(in Vec!(U, num) v)
317     if (isImplicitlyConvertible!(U, T))
318     {
319         static assert(name.length == num, name~" number of components do not match with type "~(Vec!(U, num).stringof));
320         static foreach (i; 0 .. name.length)
321         {
322             _rep[compNameIndex!(name[i])] = v[i];
323         }
324     }
325 
326     /// Cast the vector to another type of component
327     auto opCast(V)() const if (isVec!(length, V))
328     {
329         V res;
330         static foreach (i; 0 .. length) {
331             res[i] = cast(V.Component)_rep[i];
332         }
333         return res;
334     }
335 
336     /// Unary "+" operation.
337     Vec!(T, N) opUnary(string op : "+")() const
338     {
339         return this;
340     }
341     /// Unary "-" operation.
342     Vec!(T, N) opUnary(string op : "-")() const
343     {
344         Vec!(T, N) res = this;
345         static foreach (i; 0 .. length) {
346             res[i] = -res[i];
347         }
348         return res;
349     }
350 
351     /// Perform a term by term assignment operation on the vector.
352     ref Vec!(T, N) opOpAssign(string op, U)(in Vec!(U, N) oth)
353             if ((op == "+" || op == "-" || op == "*" || op == "/") && isNumeric!U)
354     {
355         foreach (i, ref v; _rep)
356         {
357             mixin("v " ~ op ~ "= oth[i];");
358         }
359         return this;
360     }
361 
362     /// Perform a scalar assignment operation on the vector.
363     ref Vec!(T, N) opOpAssign(string op, U)(in U val)
364             if ((op == "+" || op == "-" || op == "*" || op == "/" || (op == "%"
365                 && __traits(isIntegral, U))) && isNumeric!U)
366     {
367         foreach (ref v; _rep)
368         {
369             mixin("v " ~ op ~ "= val;");
370         }
371         return this;
372     }
373 
374     /// Perform a term by term operation on the vector.
375     Vec!(T, N) opBinary(string op, U)(in Vec!(U, N) oth) const
376             if ((op == "+" || op == "-" || op == "*" || op == "/") && isNumeric!U)
377     {
378         Vec!(T, N) res = this;
379         mixin("res " ~ op ~ "= oth;");
380         return res;
381     }
382 
383     /// Perform a scalar operation on the vector.
384     Vec!(T, N) opBinary(string op, U)(in U val) const
385             if ((op == "+" || op == "-" || op == "*" || op == "/" || (op == "%"
386                 && isIntegral!U)) && isNumeric!U)
387     {
388         Vec!(T, N) res = this;
389         mixin("res " ~ op ~ "= val;");
390         return res;
391     }
392 
393     /// Perform a scalar operation on the vector.
394     Vec!(T, N) opBinaryRight(string op, U)(in U val) const
395             if ((op == "+" || op == "-" || op == "*" || op == "/" || (op == "%"
396                 && __traits(isIntegral, U))) && isNumeric!U)
397     {
398         Vec!(T, N) res = void;
399         static foreach (i; 0 .. N) {
400             mixin("res[i] = val " ~ op ~ " _rep[i];");
401         }
402         return res;
403     }
404 
405     /// Foreach support.
406     @system int opApply(int delegate(size_t i, ref T) dg)
407     {
408         int res;
409         foreach (i, ref v; _rep)
410         {
411             res = dg(i, v);
412             if (res)
413                 break;
414         }
415         return res;
416     }
417 
418     /// Foreach support.
419     @system int opApply(int delegate(ref T) dg)
420     {
421         int res;
422         foreach (ref v; _rep)
423         {
424             res = dg(v);
425             if (res)
426                 break;
427         }
428         return res;
429     }
430 
431     /// Foreach support.
432     @safe int opApply(int delegate(size_t i, ref T) @safe dg)
433     {
434         int res;
435         foreach (i, ref v; _rep)
436         {
437             res = dg(i, v);
438             if (res)
439                 break;
440         }
441         return res;
442     }
443 
444     /// Foreach support.
445     @safe int opApply(int delegate(ref T) @safe dg)
446     {
447         int res;
448         foreach (ref v; _rep)
449         {
450             res = dg(v);
451             if (res)
452                 break;
453         }
454         return res;
455     }
456 
457     // TODO: const opApply and foreach_reverse
458 
459     /// Index a vector component.
460     T opIndex(in size_t index) const
461     in {
462         assert(index < N);
463     }
464     body {
465         return _rep[index];
466     }
467 
468     /// Assign a vector component.
469     void opIndexAssign(in T val, in size_t index)
470     in {
471         assert(index < N);
472     }
473     body {
474         _rep[index] = val;
475     }
476 
477     /// Assign a vector component.
478     void opIndexOpAssign(string op)(in T val, in size_t index)
479     if (op == "+" || op == "-" || op == "*" || op == "/")
480     in {
481         assert(index < N);
482     }
483     body {
484         mixin("_rep[index] "~op~"= val;");
485     }
486 
487     /// Slicing support
488     size_t[2] opSlice(in size_t start, in size_t end) const {
489         assert(start <= end && end <= length);
490         return [ start, end ];
491     }
492 
493     /// ditto
494     inout(T)[] opIndex(in size_t[2] slice) inout {
495         return _rep[slice[0] .. slice[1]];
496     }
497 
498     /// ditto
499     inout(T)[] opIndex() inout {
500         return _rep[];
501     }
502 
503     /// ditto
504     void opIndexAssign(U)(in U val, in size_t[2] slice) {
505         assert(correctSlice(slice));
506         _rep[slice[0] .. slice[1]] = val;
507     }
508 
509     /// ditto
510     void opIndexAssign(U)(in U[] val, in size_t[2] slice) {
511         assert(val.length == slice[1]-slice[0] && correctSlice(slice));
512         _rep[slice[0] .. slice[1]] = val;
513     }
514 
515     /// ditto
516     void opIndexAssign(U)(in U[] val) {
517         assert(val.length == length);
518         _rep[] = val;
519     }
520 
521     /// ditto
522     void opIndexOpAssign(string op, U)(in U val, in size_t[2] slice)
523     if (op == "+" || op == "-" || op == "*" || op == "/")
524     {
525         foreach (i; slice[0]..slice[1]) {
526             mixin("_rep[i] "~op~"= val;");
527         }
528     }
529 
530     /// Term by term sliced assignement operation.
531     void opIndexOpAssign(string op, U)(in U val, in size_t[2] slice)
532     if (op == "+" || op == "-" || op == "*" || op == "/")
533     {
534         foreach (i; slice[0]..slice[1]) {
535             mixin("_rep[i] "~op~"= val;");
536         }
537     }
538 
539     /// End of the vector.
540     size_t opDollar()
541     {
542         return length;
543     }
544 
545     string toString() const
546     {
547         string res = "[ ";
548         foreach(i, c; _rep)
549         {
550             import std.format : format;
551             immutable fmt = i == length-1 ? "%s " : "%s, ";
552             res ~= format(fmt, c);
553         }
554         return res ~ "]";
555     }
556 
557     private static bool correctSlice(size_t[2] slice)
558     {
559         return slice[0] <= slice[1] && slice[1] <= length;
560     }
561 }
562 
563 ///
564 unittest
565 {
566     DVec3 v;
567 
568     assert(v._rep[0] == 0);
569     assert(v._rep[1] == 0);
570     assert(v._rep[2] == 0);
571 
572     v = DVec3(4, 5, 6);
573 
574     assert(v._rep[0] == 4);
575     assert(v._rep[1] == 5);
576     assert(v._rep[2] == 6);
577     assert(v[1] == 5);
578     assert(v.z == 6);
579     assert(v[$ - 1] == 6);
580 
581     assert(-v == DVec3(-4, -5, -6));
582 
583     v.z = 2;
584     v[1] = 1;
585     assert(v[1] == 1);
586     assert(v.z == 2);
587 
588     auto c = DVec4(0.2, 1, 0.6, 0.9);
589     assert(c.r == 0.2);
590     assert(c.g == 1);
591     assert(c.b == 0.6);
592     assert(c.a == 0.9);
593 
594     assert(c.rrwuzy == DVec!6(0.2, 0.2, 0.9, 0.2, 0.6, 1));
595     c.bgra = DVec4(0.3, 0.4, 0.5, 0.6);
596     assert(c.data == [0.5, 0.4, 0.3, 0.6]);
597 }
598 
599 
600 /// Compute the dot product of two vectors.
601 template dot(T, size_t N)
602 {
603     T dot(in Vec!(T, N) v1, in Vec!(T, N) v2)
604     pure nothrow @nogc @safe
605     {
606         T sum = 0;
607         static foreach (i; 0 .. N)
608         {
609             sum += v1[i] * v2[i];
610         }
611         return sum;
612     }
613 }
614 
615 /// Compute the magnitude of a vector.
616 /// This is not to be confused with length which gives the number of components.
617 template magnitude(T, size_t N)
618 {
619     T magnitude(in Vec!(T, N) v)
620     pure nothrow @nogc @safe
621     {
622         import std.math : sqrt;
623         return sqrt(squaredMag(v));
624     }
625 }
626 
627 /// Compute the squared magnitude of a vector.
628 template squaredMag(T, size_t N)
629 {
630     T squaredMag(in Vec!(T, N) v)
631     pure nothrow @nogc @safe
632     {
633         T sum = 0;
634         static foreach (i; 0 .. N)
635         {
636             sum += v[i] * v[i];
637         }
638         return sum;
639     }
640 }
641 
642 /// Compute the normalization of a vector.
643 template normalize(T, size_t N) if (isFloatingPoint!T)
644 {
645     import gfx.math.approx : approxUlpAndAbs;
646 
647     Vec!(T, N) normalize(in Vec!(T, N) v)
648     pure nothrow @nogc @safe
649     in {
650         assert(!approxUlpAndAbs(magnitude(v), 0), "Cannot normalize a null vector.");
651     }
652     out (res) {
653         assert(approxUlpAndAbs(magnitude(res), 1));
654     }
655     body
656     {
657         import std.math : sqrt;
658         return v / magnitude(v);
659     }
660 }
661 
662 /// Vector cross product
663 template cross(T)
664 {
665     Vec!(T, 3) cross(in Vec!(T, 3) lhs, in Vec!(T, 3) rhs)
666     pure nothrow @nogc @safe
667     {
668         return Vec!(T, 3)(
669             lhs[1]*rhs[2] - lhs[2]*rhs[1],
670             lhs[2]*rhs[0] - lhs[0]*rhs[2],
671             lhs[0]*rhs[1] - lhs[1]*rhs[0],
672         );
673     }
674 }
675 
676 static assert(DVec2.sizeof == 16);
677 static assert(DVec3.sizeof == 24);
678 static assert(DVec4.sizeof == 32);
679 
680 ///
681 unittest
682 {
683     import gfx.math.approx : approxUlp;
684 
685     auto v1 = DVec3(12, 4, 3);
686     auto v2 = DVec3(-5, 3, 7);
687 
688     assert(approxUlp(dot(v1, v2), -27));
689 
690     auto v = DVec3(3, 4, 5);
691     assert(approxUlp(squaredMag(v), 50));
692 
693     assert(approxUlp(normalize(FVec3(4, 0, 0)), FVec3(1, 0, 0)));
694 }
695 
696 
697 /// Check whether VecT is a Vec
698 template isVec(VecT)
699 {
700     import std.traits : TemplateOf;
701     static if (is(typeof(__traits(isSame, TemplateOf!VecT, Vec))))
702     {
703         enum isVec = __traits(isSame, TemplateOf!VecT, Vec);
704     }
705     else
706     {
707         enum isVec = false;
708     }
709 }
710 
711 static assert( isVec!(FVec2) );
712 static assert( !isVec!double );
713 
714 /// Check whether VecT is a Vec of size N
715 template isVec(size_t N, VecT)
716 {
717     static if (is(typeof(VecT.length)))
718     {
719         enum isVec = isVec!VecT && VecT.length == N;
720     }
721     else
722     {
723         enum isVec = false;
724     }
725 }
726 
727 /// ditto
728 enum isVec2(VecT) = isVec!(2, VecT);
729 /// ditto
730 enum isVec3(VecT) = isVec!(3, VecT);
731 /// ditto
732 enum isVec4(VecT) = isVec!(4, VecT);
733 
734 
735 package:
736 
737 import std.typecons : Tuple;
738 
739 template CompTup(T...)
740 {
741     alias CompTup = typeof(compTup(T.init));
742 }
743 
744 auto compTup(T...)(T vals) pure nothrow @nogc @safe
745 {
746     import std.meta : Repeat;
747     import std.traits : Unqual;
748     import std.typecons : tuple;
749 
750     static if (T.length == 0) {
751         return tuple();
752     }
753     else static if (T.length == 1 && isNumeric!(T[0])) {
754         return tuple(vals[0]);
755     }
756     else static if (T.length == 1 && isStaticArray!(T[0])) {
757         alias U = Unqual!(typeof(T[0][0]));
758         return Tuple!(Repeat!(T[0].length, U))(vals);
759     }
760     else static if (T.length == 1 && isVec!(T[0])) {
761         return vals[0].tup;
762     }
763     else static if (T.length > 1) {
764         return tuple(compTup(vals[0]).expand, compTup(vals[1 .. $]).expand);
765     }
766     else {
767         static assert(false, "wrong arguments to compTup");
768     }
769 }
770 
771 enum numComps(T...) = CompTup!(T).length;
772 
773 static assert (numComps!DVec3 == 3);
774 static assert (is(CompTup!DVec3 == Tuple!(double, double, double)));
775 static assert (is(CompTup!(DVec2, int, float) == Tuple!(double, double, int, float)));
776 
777 
778 static assert(isVec!FVec3);