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