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