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