1 /**
2 Defines a Vector of float point typefrom 2 to 4 dimension
3 */
4 module zmath.vector;
5 
6 alias Vec2r = Vector!(real, 2); /// Alias of a 2d Vector with reals
7 alias Vec3r = Vector!(real, 3); /// Alias of a 3d Vector with reals
8 alias Vec4r = Vector!(real, 4); /// Alias of a 4d Vector with reals
9 
10 alias Vec2d = Vector!(double, 2); /// Alias of a 2d Vector with doubles
11 alias Vec3d = Vector!(double, 3); /// Alias of a 3d Vector with doubles
12 alias Vec4d = Vector!(double, 4); /// Alias of a 4d Vector with doubles
13 
14 alias Vec2f = Vector!(float, 2); /// Alias of a 2d Vector with floats
15 alias Vec3f = Vector!(float, 3); /// Alias of a 3d Vector with floats
16 alias Vec4f = Vector!(float, 4); /// Alias of a 4d Vector with floats
17 
18 /**
19  * N-Dimensional Vector over a FloatPoint type, where N must be 2,3 or 4
20  */
21 public struct Vector(T, size_t dim_) if (__traits(isFloating, T)) {
22 nothrow:
23   static enum size_t dim = dim_; /// Vector Dimension
24 
25   static assert(dim >= 2 && dim <= 4, "Not valid dimension size.");
26   static assert(__traits(isFloating, T), "Type not is a Float Point type.");
27   static assert(is(T : real), "Type not is like a Float Point type.");
28 
29   union {
30     package T[dim] coor; /// Vector coords like Array
31 
32     struct {
33       static if (dim >= 1) {
34         T x; /// X coord
35         alias r = x; /// R component
36         alias roll = x; /// Euler roll angle
37         alias bank = x; /// Euler roll angle
38       }
39       static if (dim >= 2) {
40         T y; /// Y coord
41         alias g = y; /// G component
42         alias pitch = y; /// Euler pith angle
43         alias attidue = y; /// Euler pith angle
44       }
45       static if (dim >= 3) {
46         T z; /// Z coord
47         alias b = z; /// B component
48         alias yaw = z; /// Euler yaw angle
49         alias heading = z; /// Euler yaw angle
50       }
51       static if (dim >= 4) {
52         T w; /// W coord
53         alias a = w; /// Alpha component
54       }
55     }
56   }
57   // Consts
58   static if (dim == 2) { // for R2
59     public static enum Vector!(T, 2) ZERO = Vector!(T, 2)(0, 0); /// Origin
60     public static enum Vector!(T, 2) X_AXIS = Vector!(T, 2)(1, 0); /// X Axis in R2
61     public static enum Vector!(T, 2) Y_AXIS = Vector!(T, 2)(0, 1); /// Y Axis in R2
62 
63   } else static if (dim == 3) { // for R3
64     public static enum Vector!(T, 3) ZERO = Vector!(T, 3)(0, 0, 0); /// Origin
65     public static enum Vector!(T, 3) X_AXIS = Vector!(T, 3)(1, 0, 0); /// X Axis in R3
66     public static enum Vector!(T, 3) Y_AXIS = Vector!(T, 3)(0, 1, 0); /// Y Axis in R3
67     public static enum Vector!(T, 3) Z_AXIS = Vector!(T, 3)(0, 0, 1); /// Z Axis in R3
68 
69   } else static if (dim == 4) { // for R4
70     public static enum Vector!(T, 4) ZERO = Vector!(T, 4)(0, 0, 0, 0); /// Origin
71     public static enum Vector!(T, 4) X_AXIS = Vector!(T, 4)(1, 0, 0, 0); /// X Axis in R4
72     public static enum Vector!(T, 4) Y_AXIS = Vector!(T, 4)(0, 1, 0, 0); /// Y Axis in R4
73     public static enum Vector!(T, 4) Z_AXIS = Vector!(T, 4)(0, 0, 1, 0); /// Z Axis in R4
74     /** W Axis in R4 (used like a scale factor with 3d Maths with 4d Matrixes) */
75     public static enum Vector!(T, 4) W_AXIS = Vector!(T, 4)(0, 0, 0, 1);
76   }
77 
78   /**
79    * Build a new Vector from a set of initial values
80    * If no there values for z and w, will be set to 0
81    * Params:
82    *	x = X coord
83    *	y = Y coord
84    *	z = Z coord
85    *	w = W coord (scale factor in 3d math)
86    */
87   @nogc this(in T x, in T y = 0, in T z = 0, in T w = 1) pure {
88     this.x = x;
89     this.y = y;
90     static if (dim >= 3)
91       this.z = z;
92     static if (dim >= 4)
93       this.w = w;
94   }
95 
96   /**
97    * Build a new Vector from a array
98    * If no there values for y and z, will be set to 0. w sill beset to 1
99    * Params:
100    *	xs = Array with coords
101    */
102   this(in T[] xs) pure {
103     size_t l = xs.length > dim ? dim : xs.length;
104     this.coor[0 .. l] = xs[0 .. l].dup;
105     if (l < dim) {
106       static if (dim < 4) {
107         this.coor[l .. dim] = 0;
108       } else {
109         this.coor[l .. dim - 1] = 0;
110         w = 1;
111       }
112     }
113   }
114 
115   // Basic Properties **********************************************************
116 
117   /**
118    * Returns i coord of this vector
119    */
120   @nogc T opIndex(size_t i) pure const {
121     return coor[i];
122   }
123 
124   /**
125    * Assigns a value to a i coord
126    */
127   void opIndexAssign(T c, size_t i) {
128     coor[i] = c;
129   }
130 
131   /**
132    * Returns the actual length of this Vector
133    */
134   @property @nogc T length() pure const {
135     import std.math : sqrt;
136 
137     return sqrt(sq_length);
138   }
139 
140   /**
141    * Returns the actual squared length of this Vector
142    */
143   @property @nogc T sq_length() pure const {
144     return this * this;
145   }
146 
147   // Operations ****************************************************************
148 
149   /**
150    * Define Equality
151    * Params:
152    * rhs = Vector at rigth of '=='
153    */
154   @nogc bool opEquals()(auto ref const Vector rhs) pure const {
155     if (x != rhs.x)
156       return false;
157     static if (dim >= 2) {
158       if (y != rhs.y) {
159         return false;
160       }
161     }
162     static if (dim >= 3) {
163       if (z != rhs.z) {
164         return false;
165       }
166     }
167     static if (dim == 4) {
168       if (w != rhs.w) {
169         return false;
170       }
171     }
172     return true;
173   }
174 
175   /**
176    * Approximated equality with controlable precision
177    * Params:
178    * rhs = Vector to compare with this vector
179    * maxRelDiff = Maximun relative difference
180    * maxAbsDiff = Maximun absolute difference
181    *
182    * See: std.math : approxEqual
183    */
184   @nogc bool approxEqual()(auto ref const Vector rhs, T maxRelDiff = 1e-2, T maxAbsDiff = 1e-05) pure const {
185     import std.math : approxEqual;
186 
187     static if (dim == 2) {
188       return approxEqual(x, rhs.x, maxRelDiff, maxAbsDiff) && approxEqual(y,
189           rhs.y, maxRelDiff, maxAbsDiff);
190     } else static if (dim == 3) {
191       return approxEqual(x, rhs.x, maxRelDiff, maxAbsDiff) && approxEqual(y, rhs.y,
192           maxRelDiff, maxAbsDiff) && approxEqual(z, rhs.z, maxRelDiff, maxAbsDiff);
193     } else static if (dim == 4) {
194       return approxEqual(x, rhs.x, maxRelDiff, maxAbsDiff) && approxEqual(y, rhs.y, maxRelDiff,
195           maxAbsDiff) && approxEqual(z, rhs.z, maxRelDiff, maxAbsDiff)
196         && approxEqual(w, rhs.w, maxRelDiff, maxAbsDiff);
197     }
198   }
199 
200   /**
201    * Define unary operators + and -
202    */
203   @nogc Vector opUnary(string op)() pure const if (op == "+" || op == "-") {
204     static if (dim == 2) {
205       return Vector(mixin(op ~ "x"), mixin(op ~ "y"));
206     } else static if (dim == 3) {
207       return Vector(mixin(op ~ "x"), mixin(op ~ "y"), mixin(op ~ "z"));
208     } else static if (dim == 4) {
209       return Vector(mixin(op ~ "x"), mixin(op ~ "y"), mixin(op ~ "z"), mixin(op ~ "w"));
210     }
211   }
212 
213   /**
214    * Define binary operator + and -
215    */
216   @nogc Vector opBinary(string op)(auto ref const Vector rhs) pure const if (op == "+" || op == "-") {
217     static if (dim == 2) {
218       return Vector(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"));
219     } else static if (dim == 3) {
220       return Vector(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"), mixin("z" ~ op ~ "rhs.z"));
221     } else static if (dim == 4) {
222       return Vector(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"),
223           mixin("z" ~ op ~ "rhs.z"), mixin("w" ~ op ~ "rhs.w"));
224     }
225   }
226 
227   /**
228    * Define Scalar multiplication
229    */
230   @nogc Vector opBinary(string op)(in T rhs) pure const if (op == "*" || op == "/") {
231     static if (dim == 2) {
232       return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"));
233     } else static if (dim == 3) {
234       return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"), mixin("z" ~ op ~ "rhs"));
235     } else static if (dim == 4) {
236       return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"),
237           mixin("z" ~ op ~ "rhs"), mixin("w" ~ op ~ "rhs"));
238     }
239   }
240 
241   /**
242    * Define Scalar multiplication
243    */
244   @nogc Vector opBinaryRight(string op)(in T rhs) pure const if(op == "*" || op == "/") {
245     static if (dim == 2) {
246       return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"));
247     } else static if (dim == 3) {
248       return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"), mixin("z" ~ op ~ "rhs"));
249     } else static if (dim == 4) {
250       return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"),
251           mixin("z" ~ op ~ "rhs"), mixin("w" ~ op ~ "rhs"));
252     }
253   }
254 
255   /**
256    * Define Dot Product
257    */
258   @nogc T opBinary(string op)(auto ref const Vector rhs) pure const if (op == "*") {
259     T tmp = 0;
260     foreach (i, x; this.coor) {
261       tmp += x * rhs.coor[i];
262     }
263     return tmp;
264   }
265 
266   /**
267    * Define Cross Product for R3 (operation c = a & b )
268    */
269   @nogc Vector opBinary(string op)(auto ref const Vector rhs) pure const
270   if (op == "&" && dim == 3) {
271     // dfmt off
272     return Vector(
273         coor[1] * rhs.coor[2] - coor[2] * rhs.coor[1],
274         coor[2] * rhs.coor[0] - coor[0] * rhs.coor[2],
275         coor[0] * rhs.coor[1] - coor[1] * rhs.coor[0]
276         );
277     // dfmt on
278   }
279 
280   /**
281   * It's a unitary vector (length == 1)
282   * Returns : True if length approxEqual to 1.0
283   */
284   @property @nogc bool isUnit() pure const {
285     import std.math : approxEqual, abs;
286 
287     return approxEqual(abs(this.sq_length - 1.0), 0);
288   }
289 
290   /**
291   * Normalize this vector
292   */
293   @nogc void normalize() {
294     if (!isUnit()) {
295       const T invL = 1 / this.length;
296       static if (dim >= 2) {
297         x = x * invL;
298         y = y * invL;
299       }
300       static if (dim >= 3)
301         z = z * invL;
302       static if (dim == 4)
303         w = w * invL;
304     }
305   }
306 
307   /**
308   * Returns the unit vector of this vector
309   */
310   @property @nogc Vector unit() pure const {
311     Vector ret = this;
312     ret.normalize;
313     return ret;
314   }
315 
316   /**
317   * Obtains the projection of two vectors
318   * Params:
319   *	a = Vector to project
320   * b = Vector where project vector a
321   * Returns : A Vector that it's projection of Vector a over Vector b
322   */
323   @nogc static Vector projectOnTo()(auto ref const Vector a, auto ref const Vector b) pure {
324     const bNormalized = b.unit();
325     Vector ret = b * (a * bNormalized);
326     return ret;
327   }
328 
329   /**
330   * Obtains the projection of this vector over other vector
331   * Params:
332   * b = Vector where project this vector
333   * Returns : A Vector that it's projection of this Vector over Vector b
334   */
335   @nogc Vector projectOnTo()(auto ref const Vector b) pure {
336     return this.projectOnTo(this, b);
337   }
338 
339   /**
340   * Calculate the distance between two points pointed by this vector and other
341   * Params :
342   *	b = Vector B
343   * Returns : Distance between the point pointed by this vector and other point
344   */
345   @nogc T distance()(auto ref const Vector b) pure {
346     auto d = this - b;
347     return d.length;
348   }
349 
350   /**
351   * Calculate the squared distance between two points pointed by this vector
352   * and other
353   * Params :
354   *	b = Vector B
355   * Returns : Squared distance between the point pointed by this vector and
356   *   other point
357   */
358   @nogc T sq_distance()(auto ref const Vector b) pure {
359     auto d = this - b;
360     return d.sq_length;
361   }
362 
363   /**
364   * Rotation in R2
365   * Params:
366   * angle = Rotation angle
367   * Returns : A new vector that is the rotation of this vector
368   */
369   static if (dim == 2) {
370     @nogc Vector rotate(real angle) pure const {
371       import std.math : sin, cos;
372 
373       return Vector(x * cos(angle) - y * sin(angle), x * sin(angle) + y * cos(angle));
374     }
375   }
376 
377   // Misc **********************************************************************
378 
379   /**
380    * Return a pointer of the internal array
381    */
382   @property @nogc T* ptr() pure {
383     return this.coor.ptr;
384   }
385 
386   /**
387   * Checks that the vector not have a weird NaN value
388   * Returns : True if this vector not have a NaN value
389   */
390   @property @nogc bool isOk() pure const {
391     import std.math : isNaN;
392 
393     if (isNaN(x) || isNaN(y))
394       return false;
395     static if (dim >= 3)
396       if (isNaN(z))
397         return false;
398     static if (dim >= 4)
399       if (isNaN(w))
400         return false;
401     return true;
402   }
403 
404   /**
405   * Checks that the vector have finite values
406   * Returns : True if this vector have finite values (not infinite value or
407   *   NaNs)
408   */
409   @property @nogc bool isFinite() pure const {
410     import std.math : isFinite;
411 
412     if (isFinite(x) && isFinite(y)) {
413       static if (dim >= 3)
414         if (!isFinite(z))
415           return false;
416       static if (dim >= 4)
417         if (!isFinite(w))
418           return false;
419       return true;
420     } else {
421       return false;
422     }
423   }
424 
425   /**
426   * Casting method to convert to other vector types
427   */
428   @nogc Tout opCast(Tout)() pure const if (isVector!(Tout)) {
429     import std.conv : to;
430 
431     Tout newVector = void;
432     auto i = 0;
433     static if (is(typeof(newVector.x) == typeof(this.x))) {
434       for (; i < dim && i < Tout.dim; i++)
435         newVector.coor[i] = coor[i];
436     } else {
437       for (; i < dim && i < Tout.dim; i++)
438         newVector.coor[i] = to!(typeof(newVector.x))(coor[i]);
439     }
440 
441     static if (Tout.dim >= 3 && dim < 3) // Expands a small vector to a bigger
442       newVector.z = 0; // Z by default to 0
443 
444     static if (Tout.dim == 4 && dim < 4) {
445       newVector.w = 1;
446       // w is usually a scale factor used in 3d math with 4d matrixes
447     }
448 
449     return newVector;
450   }
451 
452   /**
453    * Returns a string representation of this vector
454    */
455   string toString() const {
456     import std.conv : to;
457     try {
458       string ret = "[" ~ to!string(x) ~ ", " ~ to!string(y);
459       static if (dim >= 3)
460         ret ~= ", " ~ to!string(z);
461       static if (dim >= 4)
462         ret ~= ", " ~ to!string(w);
463       ret ~= "]";
464       return ret;
465     } catch (Exception ex) {
466       assert(false); // This never should happen
467     }
468   }
469 }
470 
471 /**
472 * Say if a thing it's a Vector
473 */
474 template isVector(T) {
475   immutable bool isVector = __traits(compiles, () {
476     T t;
477     static assert(T.dim >= 2 && T.dim <= 4);
478     auto coor = t.coor;
479     auto x = t.x;
480     auto y = t.y;
481     static if (t.dim >= 3)
482       auto z = t.z;
483     static if (t.dim >= 4)
484       auto w = t.w;
485     // TODO : Should test for methods ?
486   });
487 }
488 
489 static assert(Vec2f.sizeof == 8);
490 static assert(Vec3d.sizeof == 24);
491 static assert(Vec4f.sizeof == 16);
492 
493 unittest {
494   Vec2r v1 = Vec2r(0, 2);
495   auto v2 = Vec2r(2, 0);
496   auto v3 = Vec2r([1, 1]);
497   float[] arr = [5, 20.1, 0, 10, 20];
498   auto v4 = Vec4f(arr);
499 
500   assert(v2.x == 2); // By Coordinate name
501   assert(v2.coor[0] == 2); // By direct access to array (only internal use)
502 }
503 
504 unittest {
505   auto v = Vec3f(10, 0, 0);
506   auto v2 = Vec4d(1, 1, 1, 1);
507   assert(v2[0] == 1); // By opIndex
508   v2[0] = 123;
509   assert(v2[0] == 123); // Assign by opIndex
510   assert(v.length == 10.0);
511   assert(Vec4d(1, 1, 1, 1).sq_length == 4);
512 }
513 
514 unittest {
515   // Check equality
516   auto v1 = Vec4f(4, 2, 1, 0);
517   auto v2 = Vec4f(4, 2, 3, 1);
518   auto v3 = Vec4f(4, 2, 1, 0);
519   auto v4 = Vec4f(4, 2, 1.00001, 0);
520   assert(v2 != v1);
521   assert(v1 == v1);
522   assert(v1 == Vec4f(4, 2, 1, 0));
523   assert(v3 == v1);
524   assert(v1 != v4);
525   assert(v1.approxEqual(v1));
526   assert(!v1.approxEqual(v2));
527   assert(v1.approxEqual(v4));
528   assert(v1.approxEqual(Vec4f(4, 2, 1, 0)));
529 }
530 
531 unittest {
532   // Check change of sign
533   auto v = Vec2r([1, 1]);
534   auto n = -v;
535   assert(n.x == -1);
536   assert(n.y == -1);
537 }
538 
539 unittest {
540   // Check addition
541   auto v1 = Vec4d(1, 2, 3, 4);
542   auto v2 = Vec4d(1, 1, 1, 1);
543   auto v12 = v1 + v2;
544   auto v21 = v2 + v1;
545 
546   // Symetry
547   assert(v12 == v21);
548   // Value
549   assert(v12 == Vec4d(2, 3, 4, 5));
550 
551   // Check subtraction
552   auto v1m2 = v1 - v2;
553   assert(v1m2 == Vec4d(0, 1, 2, 3));
554   // Subtraction asimetry
555   auto v2m1 = v2 - v1;
556   assert(v2m1 == Vec4d(0, -1, -2, -3));
557 }
558 
559 unittest {
560   // Product by Scalar
561   auto vten = Vec4r(0, 1, 2, 3) * 10.0L;
562   assert(vten == Vec4r(0, 10, 20, 30));
563   vten = vten / 2.0L;
564   assert(vten == Vec4r(0, 5, 10, 15));
565   vten = 10 * vten ;
566   assert(vten == Vec4r(0, 50, 100, 150));
567 }
568 
569 unittest {
570   // Dot Product and length
571   auto v1 = Vec4f(10, 1, 0, 0);
572   auto v2 = Vec4f(1, 1, 1, 0);
573   auto v2dotv1 = v2 * v1;
574   assert(v2dotv1 == 11.0);
575   v2 = Vec4f(0, 1, 1, 0);
576   v2dotv1 = v2 * Vec4f(10, 0, 1, 0);
577   assert(v2dotv1 == 1.0);
578 }
579 
580 unittest {
581   // Cross product
582   auto r3v1 = Vec3r(0, 0, 10);
583   auto r3v2 = Vec3r(2, 0, 2);
584   auto cross0 = r3v1 & r3v1;
585   auto cross12 = r3v1 & r3v2;
586   auto cross21 = r3v2 & r3v1;
587 
588   assert(cross0.length == 0);
589   assert(cross12.length == 20.0);
590   assert(cross21.length == 20.0);
591 
592   assert(cross12 == Vec3r(0, 20.0L, 0));
593   assert(cross21 == Vec3r(0, -20.0L, 0));
594 }
595 
596 unittest {
597   // Unitary Vector
598   auto v = Vec4f(10, 10, 10, 10);
599   Vec4f uv = v;
600   uv.normalize();
601   auto uv2 = Vec4f(3, 3, 3, 3);
602   uv2.normalize();
603   assert(uv == uv2);
604   assert(uv == Vec4f(.5, .5, .5, .5));
605   assert(!v.isUnit());
606   assert(uv.isUnit());
607 }
608 
609 unittest {
610   // Check projection
611   auto b = Vec3f.X_AXIS;
612   auto a = Vec3f(1, 20, -30);
613   auto proy = a.projectOnTo(b);
614   assert(proy == Vec3f.X_AXIS);
615 }
616 
617 unittest {
618   // Test Distance
619   auto v1 = Vec4d(1, 1, 1, 1);
620   auto v2 = Vec4d(-1, -1, -1, -1);
621   auto dis = v1.distance(v2);
622   auto dis_sq = v1.sq_distance(v2);
623   assert(dis == 4);
624   assert(dis_sq == 16);
625 
626   dis = v1.distance(Vec4d(-1, -1, -1, -1));
627   assert(dis == 4);
628 }
629 
630 unittest {
631   import std.math : PI_2;
632 
633   // Check Rotation in R2
634   auto v = Vec2f(10, 0);
635   auto rot90 = v.rotate(PI_2);
636   auto rotn90 = v.rotate(-PI_2);
637   assert(rot90.length == v.length);
638   assert(rotn90.length == v.length);
639   assert(rot90.approxEqual(Vec2f(0, 10)));
640   assert(rotn90.approxEqual(Vec2f(0, -10)));
641 }
642 
643 unittest {
644   // Check isOk() and isFinite()
645   Vec4d vNaN;
646   assert(!vNaN.isOk);
647   assert(!vNaN.isFinite);
648   Vec4d vOK = Vec4d(1, 2, 3, 4);
649   assert(vOK.isOk);
650   assert(vOK.isFinite);
651   Vec4d vInf = Vec4d(1.0 / 0.0, 10, 0, 1);
652   assert(!vInf.isFinite);
653   assert(vInf.isOk);
654 }
655 
656 unittest {
657   // Check casting
658   auto v2 = Vec2r(10, -10);
659   auto v2f = cast(Vec2f) v2;
660   auto v2d = cast(Vec2d) v2;
661   auto vecf = Vec2f(10, 10);
662   //auto vec4d = toImpl!(Vec4d, Vec2f) (vecf);
663   //auto vec4d = to!Vec4d (vecf); // Not yet
664   auto vec4d = cast(Vec4d) vecf;
665   assert(is(typeof(v2f) == Vec2f));
666   assert(is(typeof(v2d) == Vec2d));
667   assert(is(typeof(vec4d) == Vec4d));
668   auto r2 = Vec2d(5, 4);
669   auto r4 = cast(Vec4d) r2;
670   assert(is(typeof(r4) == Vec4d));
671   assert(r4 == Vec4d(5, 4, 0, 1));
672   r2 = cast(Vec2d) r4;
673   assert(r2 == Vec2d(5, 4));
674 }
675 
676 unittest {
677   const v1 = Vec2f(5, 7);
678   assert(isVector!(typeof(v1))); // Yep it's a Vector
679   assert(!isVector!(int));
680 }
681