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