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