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