1 /** 
2  * This module contains the `Vec` templated struct representing vectors and
3  * their operations.
4  */
5 module dvec.vector;
6 
7 import std.traits : isNumeric, isFloatingPoint;
8 import dvec.vector_types;
9 
10 /** 
11  * Generic struct that represents a vector holding `size` elements of type `T`.
12  * A vector must contain at least 1 element, and has no upper-bound on the size
13  * beyond restrictions imposed by the system.
14  */
15 struct Vec(T, size_t size) if (isNumeric!T && size > 0) {
16     /** 
17      * A static contstant that can be used to get the size of the vector.
18      */
19     public static const size_t SIZE = size;
20 
21     /** 
22      * The internal static array storing the elements of this vector.
23      */
24     public T[size] data;
25 
26     /** 
27      * Internal alias to make template methods more readable. We can just say
28      * `MY_TYPE` instead of `Vec!(T, size)` everywhere.
29      */
30     private alias MY_TYPE = Vec!(T, size);
31 
32     /** 
33      * Constructs a vector from an array of elements.
34      * Params:
35      *   elements = The elements to put in the vector.
36      */
37     public this(T[size] elements) @nogc {
38         static foreach (i; 0 .. size) data[i] = elements[i];
39     }
40     unittest {
41         Vec3f v = Vec3f([1f, 2f, 3f]);
42         assert(v.data == [1f, 2f, 3f]);
43     }
44 
45     /** 
46      * Constructs a vector from a variadic list of elements.
47      * Params:
48      *   elements = The elements to put in the vector.
49      */
50     public this(T[] elements...) @nogc {
51         if (elements.length != size) assert(false, "Invalid number of elements provided to Vec constructor.");
52         static foreach (i; 0 .. size) data[i] = elements[i];
53     }
54     unittest {
55         Vec3i v = Vec3i(5, 4, 3);
56         assert(v.data == [5, 4, 3]);
57     }
58 
59     /** 
60      * Constructs a vector where all elements have the given value.
61      * Params:
62      *   value = The value to assign to all elements in the vector.
63      */
64     public this(T value) @nogc {
65         static foreach (i; 0 .. size) data[i] = value;
66     }
67     unittest {
68         Vec2f v = Vec2f(1f);
69         assert(v.data == [1f, 1f]);
70         Vec!(float, 25) v2 = Vec!(float, 25)(3f);
71         foreach (value; v2.data) {
72             assert(value == 3f);
73         }
74     }
75 
76     /** 
77      * Constructs a vector as a copy of the other.
78      * Params:
79      *   other = The vector to copy.
80      */
81     public this(MY_TYPE other) @nogc {
82         this(other.data);
83     }
84     unittest {
85         Vec2f vOriginal = Vec2f(5f, 16f);
86         Vec2f vCopy = Vec2f(vOriginal);
87         assert(vCopy.data == [5f, 16f]);
88     }
89 
90     /** 
91      * Constructs a vector containing all 0's.
92      * Returns: An vector containing all 0's.
93      */
94     public static MY_TYPE empty() @nogc {
95         MY_TYPE v;
96         static foreach (i; 0 .. size) v[i] = 0;
97         return v;
98     }
99     unittest {
100         Vec4f v = Vec4f.empty();
101         assert(v.data == [0f, 0f, 0f, 0f]);
102     }
103 
104     /** 
105      * Computes the sum of a given array of vectors.
106      * Params:
107      *   vectors = The list of vectors to compute the sum of.
108      * Returns: The sum of all vectors.
109      */
110     public static MY_TYPE sum(MY_TYPE[] vectors) @nogc {
111         MY_TYPE v = MY_TYPE(0);
112         foreach (vector; vectors) v.add(vector);
113         return v;
114     }
115     unittest {
116         Vec2i v1 = Vec2i(1, 1);
117         Vec2i v2 = Vec2i(2, 2);
118         Vec2i v3 = Vec2i(3, 3);
119         Vec2i v4 = Vec2i(-1, -4);
120         assert(Vec2i.sum([v1, v2]) == v3);
121         assert(Vec2i.sum([v1, v2, v3]) == Vec2i(6, 6));
122         assert(Vec2i.sum([v3, v4]) == Vec2i(2, -1));
123     }
124 
125     /** 
126      * Gets a copy of this vector.
127      * Returns: A copy of this vector.
128      */
129     public MY_TYPE copy() const @nogc {
130         return MY_TYPE(this);
131     }
132     unittest {
133         Vec3f v = Vec3f(0.5f, 1.0f, 0.75f);
134         Vec3f vCopy = v.copy();
135         assert(v.data == vCopy.data);
136         v.data[0] = -0.5f;
137         assert(vCopy.data[0] == 0.5f);
138     }
139 
140     /** 
141      * Gets the element at a specified index.
142      * Params:
143      *   i = The index of the element.
144      * Returns: The element at the specified index.
145      */
146     public T opIndex(size_t i) const @nogc {
147         return data[i];
148     }
149     unittest {
150         Vec3f v = Vec3f(1f, 2f, 3f);
151         assert(v[0] == 1f);
152         assert(v[1] == 2f);
153         assert(v[2] == 3f);
154     }
155 
156     /** 
157      * Inserts an element at the specified index.
158      * Params:
159      *   value = The value to assign.
160      *   i = The index of the element.
161      */
162     public void opIndexAssign(T value, size_t i) @nogc {
163         data[i] = value;
164     }
165     unittest {
166         Vec3i v;
167         assert(v[0] == 0);
168         v[0] = 42;
169         assert(v[0] == 42);
170     }
171 
172     /** 
173      * Adds the given vector to this one.
174      * Params:
175      *   other = The vector to add to this one.
176      * Returns: A reference to this vector, for method chaining.
177      */
178     public ref MY_TYPE add(V)(Vec!(V, size) other) @nogc if (isNumeric!V) {
179         static foreach (i; 0 .. size) data[i] += other[i];
180         return this;
181     }
182     unittest {
183         Vec3i v1 = Vec3i(1);
184         Vec3i v2 = Vec3i(2);
185         v1.add(v2);
186         assert(v1.data == [3, 3, 3]);
187         assert(v2.data == [2, 2, 2]);
188     }
189 
190     /** 
191      * Subtracts the given vector from this one.
192      * Params:
193      *   other = The vector to subtract from this one.
194      * Returns: A reference to this vector, for method chaining.
195      */
196     public ref MY_TYPE sub(V)(Vec!(V, size) other) @nogc if (isNumeric!V) {
197         static foreach (i; 0 .. size) data[i] -= other[i];
198         return this;
199     }
200     unittest {
201         Vec3i v1 = Vec3i(1);
202         Vec3i v2 = Vec3i(2);
203         v1.sub(v2);
204         assert(v1.data == [-1, -1, -1]);
205         assert(v2.data == [2, 2, 2]);
206     }
207 
208     /** 
209      * Multiplies this vector by a factor, element-wise.
210      * Params:
211      *   factor = The factor to multiply by.
212      * Returns: A reference to this vector, for method chaining.
213      */
214     public ref MY_TYPE mul(V)(V factor) @nogc if (isNumeric!V) {
215         static foreach (i; 0 .. size) data[i] *= factor;
216         return this;
217     }
218     unittest {
219         assert(Vec2i(2).mul(2).data == [4, 4]);
220     }
221 
222     /** 
223      * Multiplies this vector by another vector, element-wise.
224      * Params:
225      *   other = The other vector to multiply with.
226      * Returns: A reference to this vector, for method chaining.
227      */
228     public ref MY_TYPE mul(V)(Vec!(V, size) other) @nogc if (isNumeric!V) {
229         static foreach (i; 0 .. size) data[i] *= other[i];
230         return this;
231     }
232     unittest {
233         assert(Vec2i(1, 2).mul(Vec2i(3, 2)).data == [3, 4]);
234     }
235 
236     /** 
237      * Divides this vector by a factor, element-wise.
238      * Params:
239      *   factor = The factor to divide by.
240      * Returns: A reference to this vector, for method chaining.
241      */
242     public ref MY_TYPE div(V)(V factor) @nogc if (isNumeric!V) {
243         static foreach (i; 0 .. size) data[i] /= factor;
244         return this;
245     }
246     unittest {
247         assert(Vec2i(8).div(2).data == [4, 4]);
248     }
249 
250     /** 
251      * Divides this vector by another vector, element-wise.
252      * Params:
253      *   other = The other vector to divide with.
254      * Returns: A reference to this vector, for method chaining.
255      */
256     public ref MY_TYPE div(V)(Vec!(V, size) other) @nogc if (isNumeric!V) {
257         static foreach (i; 0 .. size) data[i] /= other[i];
258         return this;
259     }
260     unittest {
261         assert(Vec2i(8).div(Vec2i(2, 4)).data == [4, 2]);
262     }
263 
264     /** 
265      * Determines the squared magnitude of this vector.
266      * Returns: The squared magnitude of this vector.
267      */
268     public double mag2() const @nogc {
269         double sum = 0;
270         static foreach (i; 0 .. size) sum += data[i] * data[i];
271         return sum;
272     }
273     unittest {
274         assert(Vec2i(2).mag2() == 8);
275         assert(Vec2f(3f, 4f).mag2() == 5f * 5f);
276     }
277 
278     /** 
279      * Determines the magnitude of this vector.
280      * Returns: The magnitude of this vector.
281      */
282     public double mag() const @nogc {
283         import std.math : sqrt;
284         return sqrt(mag2());
285     }
286     unittest {
287         import std.math : sqrt;
288         assert(Vec2i(1, 0).mag() == 1);
289         assert(Vec2f(3f, 4f).mag() == 5f);
290         assert(Vec2i(1, 1).mag() == sqrt(2.0));
291     }
292 
293     /** 
294      * Determines the [dot product](https://en.wikipedia.org/wiki/Dot_product)
295      * of this vector and another vector.
296      * Params:
297      *   other = The other vector.
298      * Returns: The dot product of the vectors.
299      */
300     public T dot(MY_TYPE other) const @nogc {
301         T sum = 0;
302         static foreach (i; 0 .. size) sum += data[i] * other[i];
303         return sum;
304     }
305     unittest {
306         assert(Vec3i(1, 3, -5).dot(Vec3i(4, -2, -1)) == 3);
307         assert(Vec2f(1.0f, 0.0f).dot(Vec2f(0.0f, 1.0f)) == 0f);
308         assert(Vec2f(1.0f, 0.0f).dot(Vec2f(1.0f, 0.0f)) == 1f);
309         assert(Vec2f(1.0f, 0.0f).dot(Vec2f(-1.0f, 0.0f)) == -1f);
310     }
311 
312     /** 
313      * Adds two vectors.
314      * Params:
315      *   other = The other vector.
316      * Returns: A vector representing the sum of this and the other.
317      */
318     public MY_TYPE opBinary(string op : "+", V)(Vec!(V, size) other) const @nogc if (isNumeric!V) {
319         auto result = copy();
320         result.add(other);
321         return result;
322     }
323     unittest {
324         assert(Vec2i(1) + Vec2i(3) == Vec2i(4));
325     }
326 
327     /** 
328      * Adds a scalar value to a vector, element-wise.
329      * Params:
330      *   scalar = The scalar value to add to the vector.
331      * Returns: A vector representing the sum of this and the scalar value
332      * applied to all elements.
333      */
334     public MY_TYPE opBinary(string op : "+", V)(V scalar) const @nogc if (isNumeric!V) {
335         auto result = copy();
336         result.add(Vec!(V, size)(scalar));
337         return result;
338     }
339     unittest {
340         assert(Vec2i(1) + 3 == Vec2i(4));
341     }
342 
343     /** 
344      * Adds a vector to this one.
345      * Params:
346      *   other = The other vector.
347      * Returns: A reference to this vector.
348      */
349     public ref MY_TYPE opOpAssign(string op : "+", V)(Vec!(V, size) other) @nogc if (isNumeric!V) {
350         this.add(other);
351         return this;
352     }
353     unittest {
354         Vec3i v = Vec3i(1, 2, 3);
355         v += Vec3i(1);
356         assert(v.data == [2, 3, 4]);
357     }
358 
359     /** 
360      * Adds a scalar value to this vector.
361      * Params:
362      *   scalar = The scalar value to add to the vector.
363      * Returns: A reference to this vector.
364      */
365     public ref MY_TYPE opOpAssign(string op : "+", V)(V scalar) @nogc if (isNumeric!V) {
366         this.add(Vec!(V, size)(scalar));
367         return this;
368     }
369     unittest {
370         Vec3i v = Vec3i(1, 2, 3);
371         v += 2;
372         assert(v.data == [3, 4, 5]);
373     }
374 
375     // TODO: Add scalar operators for:
376     // sub
377     // mul
378     // div
379     // and non-operator overload methods for add, sub
380 
381     /** 
382      * Subtracts two vectors.
383      * Params:
384      *   other = The other vector.
385      * Returns: A vector representing the difference of this and the other.
386      */
387     public MY_TYPE opBinary(string op : "-", V)(Vec!(V, size) other) const @nogc if (isNumeric!V) {
388         auto result = copy();
389         result.sub(other);
390         return result;
391     }
392     unittest {
393         assert(Vec2i(4) - Vec2i(1) == Vec2i(3));
394     }
395 
396     /** 
397      * Subtracts a vector from this one.
398      * Params:
399      *   other = The other vector.
400      * Returns: A reference to this vector.
401      */
402     public ref MY_TYPE opOpAssign(string op : "-", V)(Vec!(V, size) other) @nogc if (isNumeric!V) {
403         this.sub(other);
404         return this;
405     }
406     unittest {
407         Vec3i v = Vec3i(1, 2, 3);
408         v -= Vec3i(2);
409         assert(v.data == [-1, 0, 1]);
410     }
411 
412     /** 
413      * Multiplies a vector by a factor.
414      * Params:
415      *   factor = The factor to multiply by.
416      * Returns: The resultant vector.
417      */
418     public MY_TYPE opBinary(string op : "*", V)(V factor) const @nogc if (isNumeric!V) {
419         auto result = copy();
420         result.mul(factor);
421         return result;
422     }
423     unittest {
424         assert(Vec2f(0.5f) * 2f == Vec2f(1.0f));
425     }
426 
427     /** 
428      * Multiplies this vector by a factor.
429      * Params:
430      *   factor = The factor to multiply by.
431      * Returns: A reference to this vector.
432      */
433     public ref MY_TYPE opOpAssign(string op : "*", V)(V factor) @nogc if (isNumeric!V) {
434         this.mul(factor);
435         return this;
436     }
437     unittest {
438         Vec2f v = Vec2f(2f);
439         v *= 2f;
440         assert(v.data == [4f, 4f]);
441     }
442 
443     /** 
444      * Divides a vector by a factor.
445      * Params:
446      *   factor = The factor to divide by.
447      * Returns: The resultant vector.
448      */
449     public MY_TYPE opBinary(string op : "/", V)(V factor) const @nogc if (isNumeric!V) {
450         auto result = copy();
451         result.div(factor);
452         return result;
453     }
454     unittest {
455         assert(Vec2f(8f) / 4f == Vec2f(2f));
456     }
457 
458     /** 
459      * Divides this vector by a factor.
460      * Params:
461      *   factor = The factor to divide by.
462      * Returns: A reference to this vector.
463      */
464     public ref MY_TYPE opOpAssign(string op : "/", V)(V factor) @nogc if (isNumeric!V) {
465         this.div(factor);
466         return this;
467     }
468     unittest {
469         Vec2f v = Vec2f(2f, 4f);
470         v /= 4f;
471         assert(v.data == [0.5f, 1f]);
472     }
473 
474     /** 
475      * Compares this vector to another, based on their magnitudes.
476      * Params:
477      *   other = The vector to compare to.
478      * Returns: 0 if the vectors have equal magnitude, 1 if this vector's
479      * magnitude is bigger, and -1 if the other's is bigger.
480      */
481     public int opCmp(MY_TYPE other) const @nogc {
482         double a = this.mag2();
483         double b = other.mag2();
484         if (a == b) return 0;
485         if (a < b) return -1;
486         return 1;
487     }
488     unittest {
489         Vec3i v1 = Vec3i(1);
490         Vec3i v2 = Vec3i(2);
491         Vec3i v3 = Vec3i(3);
492         assert(v1 == v1);
493         assert(v1 != v2);
494         assert(v1 < v2);
495         assert(v2 > v1);
496         assert(v3 > v2);
497     }
498 
499     /** 
500      * Gets a simple string representation of this vector.
501      * Returns: The string representation of this vector.
502      */
503     public string toString() const {
504         import std.array : appender;
505         import std.conv : to;
506         auto s = appender!string;
507         s ~= "[";
508         static foreach (i; 0 .. size - 1) {
509             s ~= data[i].to!string;
510             s ~= ", ";
511         }
512         s ~= data[size - 1].to!string;
513         s ~= "]";
514         return s[];
515     }
516 
517     static if (isFloatingPoint!T) {
518         /** 
519          * [Normalizes](https://en.wikipedia.org/wiki/Unit_vector) this vector,
520          * such that it will have a magnitude of 1.
521          * Returns: A reference to this vector, for method chaining.
522          */
523         public ref MY_TYPE norm() @nogc {
524             const double mag = mag();
525             static foreach (i; 0 .. size) {
526                 data[i] /= mag;
527             }
528             return this;
529         }
530     }
531 
532     static if (isFloatingPoint!T && size == 2) {
533         /** 
534          * Converts this 2-dimensional vector from [Cartesian](https://en.wikipedia.org/wiki/Cartesian_coordinate_system)
535          * to [Polar](https://en.wikipedia.org/wiki/Polar_coordinate_system)
536          * coordinates. It is assumed that the first element is the **x**
537          * coordinate, and the second element is the **y** coordinate. The
538          * first element becomes the **radius** and the second becomes the
539          * angle **theta**.
540          * Returns: A reference to this vector, for method chaining.
541          */
542         public ref MY_TYPE toPolar() @nogc {
543             import std.math : atan2;
544             T radius = mag();
545             T angle = atan2(data[1], data[0]);
546             data[0] = radius;
547             data[1] = angle;
548             return this;
549         }
550 
551         /** 
552          * Converts this 2-dimensional vector from [Polar](https://en.wikipedia.org/wiki/Polar_coordinate_system)
553          * to [Cartesian](https://en.wikipedia.org/wiki/Cartesian_coordinate_system)
554          * coordinates. It is assumed that the first element is the **radius**
555          * and the second element is the angle **theta**. The first element
556          * becomes the **x** coordinate, and the second becomes the **y**
557          * coordinate.
558          * Returns: A reference to this vector, for method chaining.
559          */
560         public ref MY_TYPE toCartesian() @nogc {
561             import std.math : cos, sin;
562             T x = data[0] * cos(data[1]);
563             T y = data[0] * sin(data[1]);
564             data[0] = x;
565             data[1] = y;
566             return this;
567         }
568     }
569 
570     static if (isFloatingPoint!T && size == 3) {
571         /** 
572          * Computes the [cross product](https://en.wikipedia.org/wiki/Cross_product) of this vector and another, and stores
573          * the result in this vector.
574          * Params:
575          *   other = The other vector.
576          * Returns: A reference to this vector, for method chaining.
577          */
578         public ref MY_TYPE cross(MY_TYPE other) @nogc {
579             MY_TYPE tmp;
580             tmp[0] = data[1] * other[2] - data[2] * other[1];
581             tmp[1] = data[2] * other[0] - data[0] * other[2];
582             tmp[2] = data[0] * other[1] - data[1] * other[0];
583             data[0] = tmp[0];
584             data[1] = tmp[1];
585             data[2] = tmp[2];
586             return this;
587         }
588     }
589 }
590 
591 unittest {
592     import std.stdio;
593     import std.math;
594 
595     void assertFP(double actual, double expected, double delta = 1e-06, string msg = "Assertion failed.") {
596         double lowBound = expected - delta;
597         double highBound = expected + delta;
598         assert(actual > lowBound && actual < highBound, msg);
599     }
600 
601     // Test floating-point specific methods.
602     auto v6 = Vec2f(3, 3);
603     v6.norm();
604     assertFP(v6.mag, 1);
605     assertFP(v6[0], sqrt(2f) / 2f);
606     v6 = Vec2f(1, 0);
607     auto v6Copy = Vec2f(v6);
608     v6.norm();
609     assert(v6.mag == 1);
610     assert(v6.data == v6Copy.data);
611 
612     // Test toPolar
613     auto vCart = Vec2d(1, 0);
614     vCart.toPolar();
615     assert(vCart.data == [1.0, 0.0]);
616     vCart.toCartesian();
617     assert(vCart.data == [1.0, 0.0]);
618     vCart = Vec2d(1, 1);
619     vCart.toPolar();
620     assertFP(vCart[0], sqrt(2f));
621     assertFP(vCart[1], PI_4);
622     vCart.toCartesian();
623     assertFP(vCart[0], 1);
624     assertFP(vCart[1], 1);
625     vCart = Vec2d(-1, 1);
626     vCart.toPolar();
627     assertFP(vCart[0], sqrt(2f));
628     assertFP(vCart[1], 3 * PI_4);
629 }