1 module dath.vector;
2 
3 version(unittest) import fluent.asserts;
4 
5 import std.traits;
6 
7 public alias Vec2f = Vec!(float, 2);
8 public alias Vec3f = Vec!(float, 3);
9 public alias Vec4f = Vec!(float, 4);
10 
11 public alias Vec2 = Vec2f;
12 public alias Vec3 = Vec3f;
13 public alias Vec4 = Vec4f;
14 
15 public alias Vec2d = Vec!(double, 2);
16 public alias Vec3d = Vec!(double, 3);
17 public alias Vec4d = Vec!(double, 4);
18 
19 public alias Vec2i = Vec!(int, 2);
20 public alias Vec3i = Vec!(int, 3);
21 public alias Vec4i = Vec!(int, 4);
22 
23 public alias Vec2u = Vec!(uint, 2);
24 public alias Vec3u = Vec!(uint, 3);
25 public alias Vec4u = Vec!(uint, 4);
26 
27 /++
28  + Numeric Vector type with an optional amount of components.
29  +/
30 public struct Vec(T, ulong n) if (n >= 1 && isNumeric!T)
31 {
32     union {
33         /++
34          + Internal data.
35          +/
36         T[n] v;
37 
38         struct
39         {
40             // Predefined component names.
41 
42             static if (n >= 1)
43             {
44                 T x;
45             }
46 
47             static if (n >= 2)
48             {
49                 T y;
50             }
51 
52             static if (n >= 3)
53             {
54                 T z;
55             }
56 
57             static if (n >= 4)
58             {
59                 T w;
60             }
61         }
62 
63         struct
64         {
65             // Predefined components names for colors.   
66 
67             static if (n >= 1)
68             {
69                 T r;
70             }
71 
72             static if (n >= 2)
73             {
74                 T g;
75             }
76 
77             static if (n >= 3)
78             {
79                 T b;
80             }
81 
82             static if (n >= 4)
83             {
84                 T a;
85             }
86         }
87     }
88 
89     private alias _t = T;
90     private enum _n = n;
91 
92     public this(U)(U[] elements) @nogc pure nothrow
93     {
94         assert(isNumeric!U, "All components must be numeric.");
95 
96         assert(elements.length > 0, "No components provided.");
97 
98         assert(elements.length == 1 || elements.length == n,
99             "Number of components must be either 1 or the number of components the vector holds.");
100         
101         v = elements;
102     }
103 
104     public this(U...)(U args) @nogc pure nothrow
105     {
106         static foreach (arg; args)
107         {
108             static assert(isNumeric!(typeof(arg)), "All components must be numeric");
109         }
110 
111         static assert(args.length > 0, "No components provided.");
112 
113         static assert(args.length == 1 || args.length == n,
114             "Number of components must be either 1 or the number of components the vector holds.");
115 
116         static if (args.length == 1)
117         {
118             static if (!is(T == typeof(args[0])))
119             {
120                 v[] = cast(T) args[0];
121             }
122             else
123             {
124                 v[] = args[0];
125             }
126         }
127         else
128         {
129             static if (!is(T == typeof(args[0])))
130             {
131                 v = cast(T[]) [args];
132             }
133             else
134             {
135                 v = [args];
136             }
137         }
138     }
139 
140     /++
141      + Internal data as a pointer, use for sending data to shaders.
142      +/
143     public auto ptr() @nogc pure nothrow const
144     {
145         return v.ptr;
146     }
147 
148     /++ 
149      + Vector magnitude.
150      +/
151     public real magnitude() @nogc pure nothrow const
152     {
153         import std.math : sqrt;
154 
155         real sum = 0;
156         for (int i = 0; i < n; i++)
157         {
158             sum += v[i] * v[i];
159         }
160 
161         return sqrt(sum);
162     }
163 
164     /++
165      + Normalizes the vectors. Changes the current struct!
166      +/
167     public void normalize() @nogc pure nothrow
168     {
169         this = normalized();
170     }
171 
172     /++
173      + Returns the normalized vector. Doesn't change the current struct!
174      +/
175     public Vec!(T, n) normalized() @nogc pure nothrow const
176     {
177         real mag = magnitude();
178 
179         if (mag == 0)
180         {
181             return Vec!(T, n)(0);
182         }
183 
184         return this / mag;
185     }
186 
187     /++
188      + Returns the negated vector.
189      +/
190     public Vec!(T, n) opUnary(string s)() @nogc pure nothrow const if (s == "-")
191     {
192         Vec!(T, n) res;
193         res.v = -v[];
194         return res;
195     }
196 
197     /++
198      + Returns the mul of this * scalar.
199      +/
200     public Vec!(T, n) opBinary(string s) (const float scalar) @nogc pure nothrow const if (s == "*")
201     {
202         Vec!(T, n) res;
203         res.v = v[] * scalar;
204         return res;
205     }
206 
207     /++
208      + Returns the mul of this * scalar.
209      +/
210     public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "*")
211     {
212         v[] *= scalar;
213     }
214 
215     /++
216      + Returns the sum of this + scalar.
217      +/
218     public Vec!(T, n) opBinary(string s) (const float scalar) @nogc pure nothrow const if (s == "+")
219     {
220         Vec!(T, n) res;
221         res.v = v[] + scalar;
222         return res;
223     }
224 
225     /++
226      + Returns the sum of this + scalar.
227      +/
228     public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "+")
229     {
230         v[] += scalar;
231     }
232 
233     /++
234      + Returns the sub of this - scalar.
235      +/
236     public Vec!(T, n) opBinary(string s) (const float scalar) @nogc pure nothrow const if (s == "-")
237     {
238         Vec!(T, n) res;
239         res.v = v[] - scalar;
240         return res;
241     }
242 
243     /++
244      + Returns the sub of this - scalar.
245      +/
246     public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "-")
247     {
248         v[] -= scalar;
249     }
250 
251     /++
252      + Returns the div of this / scalar.
253      +/
254     public Vec!(T, n) opBinary(string s) (in float scalar) @nogc pure nothrow const if (s == "/")
255     {
256         Vec!(T, n) res;
257         res.v = v[] / cast(T) scalar;
258         return res;
259     }
260 
261     /++
262      + Returns the div of this / scalar.
263      +/
264     public void opOpAssign(string s) (in float scalar) @nogc pure nothrow if (s == "/")
265     {
266         v[] /= scalar;
267     }
268 
269     /++
270      + Returns the sum of 2 vectors.
271      +/
272     public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "+")
273     {
274         Vec!(T, n) res;
275         res.v = v[] + other.v[];
276         return res;
277     }
278 
279     /++
280      + Returns the sum of 2 vectors.
281      +/
282     public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "+")
283     {
284         v[] += other.v[];
285     }
286 
287     /++
288      + Returns the sub of 2 vectors.
289      +/
290     public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "-")
291     {
292         Vec!(T, n) res;
293         res.v = v[] - other.v[];
294         return res;
295     }
296 
297     /++
298      + Returns the sub of 2 vectors.
299      +/
300     public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "-")
301     {
302         v[] -= other.v[];
303     }
304 
305     /++
306      + Returns the mul of 2 vectors.
307      +/
308     public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "*")
309     {
310         Vec!(T, n) res;
311         res.v = v[] * other.v[];
312         return res;
313     }
314 
315     /++
316      + Returns the mul of 2 vectors.
317      +/
318     public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "*")
319     {
320         v[] *= other.v[];
321     }
322 
323     /++
324      + Returns the div of 2 vectors.
325      +/
326     public Vec!(T, n) opBinary(string s) (const Vec!(T, n) other) @nogc pure nothrow const if (s == "/")
327     {
328         Vec!(T, n) res;
329         res.v = v[] / other.v[];
330         return res;
331     }
332 
333     /++
334      + Returns the div of 2 vectors.
335      +/
336     public void opOpAssign(string s) (const Vec!(T, n) other) @nogc pure nothrow if (s == "/")
337     {
338         v[] /= other.v[];
339     }
340 
341     /++
342      + Get the N-th component.
343      +/
344     public T opIndex(int n) @nogc pure const nothrow
345     {
346         return v[n];
347     }
348 
349     /++
350      + Get the whole internal array.
351      +/
352     public T[n] opIndex() @nogc pure const nothrow
353     {
354         return v[];
355     }
356 
357     /++
358      + Set the nth component
359      +/
360     public T opIndexAssign(T value, int n) @nogc pure nothrow
361     {
362         return v[n] = value;
363     }
364 
365     /++
366      + Cast to a vector of a different type.
367      +/
368     public U opCast(U)() pure nothrow const if (is(U : Vec!R, R...) && (U._n == n))
369     {
370         U res;
371         foreach (i, el; v)
372         {
373             res.v[i] = cast(U._t) v[i];
374         }
375         return res;
376     }
377 
378     /++ 
379      + Swizzling.
380      +/
381     public Vec!(T, swizzle.length) opDispatch(const string swizzle)() @nogc const pure nothrow
382     {
383         T[swizzle.length] arr;
384 
385         static foreach (i, c; swizzle)
386         {
387             static assert(coordToIdx!(c) <= n-1,
388                 "Trying to swizzle the " ~ c ~ " component, but this vector is too small.");
389 
390             arr[i] = v[coordToIdx!(c)];
391         }
392 
393         Vec!(T, swizzle.length) res;
394         res.v = arr;
395         return res;
396     }
397 
398     private template coordToIdx(char c)
399     {
400         static if (c == 'x') enum coordToIdx = 0;
401         else static if (c == 'r') enum coordToIdx = 0;
402         else static if (c == 'y') enum coordToIdx = 1;
403         else static if (c == 'g') enum coordToIdx = 1;
404         else static if (c == 'z') enum coordToIdx = 2;
405         else static if (c == 'b') enum coordToIdx = 1;
406         else static if (c == 'w') enum coordToIdx = 3;
407         else static if (c == 'a') enum coordToIdx = 3;
408         else static assert(false, "Unknown vector component " ~ c);
409     }
410 }
411 
412 /++
413  + Returns the dot product of 2 vectors.
414  +/
415 public real dot(T, ulong n)(Vec!(T, n) a, Vec!(T, n) b) @nogc pure nothrow
416 {
417     real res = 0f;
418     static foreach (i; 0..n)
419     {
420         res += a.v[i] * b.v[i];
421     }
422     return res;
423 }
424 
425 /++
426  + Returns the cross product of 2 Vec3. The result is always a float Vec3.
427  +/
428 public Vec!(float, 3) cross(T)(Vec!(T, 3) a, Vec!(T, 3) b) @nogc pure nothrow
429 {
430     return Vec!(float, 3)(a.y * b.z - a.z * b.y,
431                           a.z * b.x - a.x * b.z,
432                           a.x * b.y - a.y * b.x);
433 }
434 
435 @("Creating vectors")
436 unittest
437 {
438     const t1 = Vec2(2f, 3f);
439 
440     t1.x.should.equal(2f);
441     t1.y.should.equal(3f);
442 
443     t1.r.should.equal(2f);
444     t1.g.should.equal(3f);
445 
446     const t2 = Vec2(2f);
447 
448     t2.x.should.equal(2f);
449     t2.y.should.equal(2f);
450 
451     const t3 = Vec4d(1f, 2f, 3f, 4f);
452 
453     t3.x.should.equal(1f);
454     t3.y.should.equal(2f);
455     t3.z.should.equal(3f);
456     t3.w.should.equal(4f);
457 
458     const t4 = Vec2i(5, 6);
459 
460     t4.x.should.equal(5);
461     t4.y.should.equal(6);
462 
463     const float[] arr = [1f, 2f];
464     const t5 = Vec2(arr);
465 
466     t5.x.should.equal(1);
467     t5.y.should.equal(2);
468 }
469 
470 @("Vector magnitude")
471 unittest
472 {
473     const t1 = Vec3(5, 6, 8);
474 
475     t1.magnitude().should.be.approximately(11.180, 0.01);
476 
477     const t2 = Vec2d(65, 76);
478 
479     t2.magnitude().should.be.approximately(100, 0.01);
480 }
481 
482 @("Vector normalization")
483 unittest
484 {
485     const t1 = Vec3(75, 64, 23);
486     const t2 = t1.normalized();
487 
488     t2.x.should.be.approximately(0.74, 0.01);
489     t2.y.should.be.approximately(0.63, 0.01);
490     t2.z.should.be.approximately(0.22, 0.01);
491 }
492 
493 @("Vector scalar operations")
494 unittest
495 {
496     auto t1 = Vec3(63, 75, 38);
497 
498     // negation
499     (-t1).should.equal(Vec3(-63, -75, -38));
500 
501     // multiplication with a scalar
502     (t1 * 2).should.equal(Vec3(126, 150, 76));
503     t1 *= 3;
504     t1.should.equal(Vec3(189, 225, 114));
505 
506     // sum with a scalar
507     (t1 + 2).should.equal(Vec3(191, 227, 116));
508     t1 += 3;
509     t1.should.equal(Vec3(192, 228, 117));
510 
511     // sub with a scalar
512     (t1 - 2).should.equal(Vec3(190, 226, 115));
513     t1 -= 3;
514     t1.should.equal(Vec3(189, 225, 114));
515 
516     // division with a scalar
517     (t1 / 2).should.equal(Vec3(94.5, 112.5, 57));
518     t1 /= 3;
519     t1.should.equal(Vec3(63, 75, 38));
520 }
521 
522 @("Vector operations with other vectors")
523 unittest
524 {
525     auto t1 = Vec3(63, 75, 38);
526     auto t2 = Vec3(37, 98, 100);
527 
528     // sum of 2 vectors
529     (t1 + t2).should.equal(Vec3(100, 173, 138));
530     t1 += t2;
531     t1.should.equal(Vec3(100, 173, 138));
532 
533     // sub of 2 vectors
534     (t1 - t2).should.equal(Vec3(63, 75, 38));
535     t1 -= t2;
536     t1.should.equal(Vec3(63, 75, 38));
537 
538     // multiplication of 2 vectors
539     (t1 * t2).should.equal(Vec3(2331, 7350, 3800));
540     t1 *= t2;
541     t1.should.equal(Vec3(2331, 7350, 3800));
542 
543     // division of 2 vectors
544     (t1 / t2).should.equal(Vec3(63, 75, 38));
545     t1 /= t2;
546     t1.should.equal(Vec3(63, 75, 38));
547 }
548 
549 @("Vector components and casting")
550 unittest
551 {
552     auto t1 = Vec3(63, 75, 38);
553 
554     t1[0].should.equal(63);
555 
556     t1[1] = 100;
557     t1[1].should.equal(100);
558     t1.y.should.equal(100);
559 
560     auto t2 = cast(Vec3d) t1;
561     t2.should.equal(Vec3d(63, 100, 38));
562 
563     auto t3 = cast(Vec3i) t2;
564     t3.should.equal(Vec3i(63, 100, 38));
565 }
566 
567 @("Vector dot and cross product")
568 unittest
569 {
570     const t1 = Vec3(63, 75, 38);
571     const t2 = Vec3(37, 98, 100);
572 
573     t1.dot(t2).should.equal(13_481);
574 
575     t1.cross(t2).should.equal(Vec3(3776, -4894, 3399));
576 }
577 
578 @("Vector swizzling")
579 unittest
580 {
581     const t1 = Vec3(4, 5, 6);
582     const t2 = t1.xz;
583 
584     t2.should.equal(Vec2(4, 6));
585 
586     const t3 = Vec4(1f, 2f, 3f, 4f);
587     const t4 = t3.xxyyww;
588 
589     t4.should.equal(Vec!(float, 6)(1f, 1f, 2f, 2f, 4f, 4f));
590 
591     // rgb swizzling because why not
592     const t5 = t3.rrr;
593     t5.should.equal(Vec3(1f));
594 
595     // rgba mixed with xyzw because why not
596     const t6 = t3.rrzz;
597     t6.should.equal(Vec4(1f, 1f, 3f, 3f));
598 }