1 /**
2  * Vector
3  */
4 module d2d.math.Vector;
5 
6 import std.algorithm;
7 import std.array;
8 import std.conv;
9 import std.math;
10 import std.parallelism;
11 import std.traits;
12 
13 /**
14  * A vector is an object representing distance in vertical and horizontal directions in multidimensional space
15  * Components are the first template parameter with the second template parameter being vector dimensionality
16  * Most vector operations take advantage of parallelism to do simple arithmetic on each component in parallel
17  * TODO: slices returning vectors, swizzling, and dispatch forwarding
18  */
19 class Vector(T, uint dimensions) {
20 
21     alias components this; //Allows for slicing/indexing the vector to work on its components
22 
23     ///The components of the vector
24     union {
25         T[dimensions] components;
26         struct {
27             static if (dimensions > 0) {
28                 T x;
29             }
30             static if (dimensions > 1) {
31                 T y;
32             }
33             static if (dimensions > 2) {
34                 T z;
35             }
36             static if (dimensions > 3) {
37                 T w;
38             }
39         }
40     }
41 
42     /**
43      * Sets the angles of the vector where the angles are given in radians
44      * Angles are direction angles (eg. first angle is direction from x, second is direction from y, etc...)
45      * 0 goes along the positive axis
46      */
47     @property void directionAngles(Vector!(double, dimensions) angles) {
48         immutable mag = this.magnitude;
49         foreach (i, angle; (cast(double[]) angles.components).parallel) {
50             this.components[i] = cast(T)(mag * cos(angle));
51         }
52     }
53 
54     /**
55      * Gets the angles of the vector where the angles are given in radians
56      * Angles are direction angles (eg. first angle is direction from x, second is direction from y, etc...)
57      * 0 goes along the positive axis
58      */
59     @property Vector!(double, dimensions) directionAngles() {
60         Vector!(double, dimensions) angles = new Vector!(double, dimensions)(cast(T) 0);
61         immutable mag = this.magnitude;
62         foreach (i, component; (cast(T[]) this.components).parallel) {
63             angles.components[i] = acos(component / mag);
64         }
65         return angles;
66     }
67 
68     /**
69      * Sets the length of the vector
70      * Maintains component ratios of the vector
71      */
72     @property void magnitude(double mag) {
73         immutable scale = mag / this.magnitude;
74         foreach (i, ref component; (cast(T[]) this.components).parallel) {
75             component = cast(T)(component * scale);
76         }
77     }
78 
79     /**
80      * Gets the length of the vector
81      */
82     @property double magnitude() {
83         return sqrt(cast(double) reduce!((squareMag,
84                 component) => squareMag + component.pow(2))(cast(T) 0, this.components));
85     }
86 
87     /**
88      * A vector constructor; takes in the number of args given and assigns them as components
89      */
90     this(T[] components...) {
91         assert(components.length == dimensions);
92         this.components = components;
93     }
94 
95     /**
96      * A vector constructor; takes in a value that acts as both vector components
97      */
98     this(T allComponents) {
99         this.components[] = allComponents;
100     }
101 
102     /**
103      * A constructor that sets all elements to 0
104      */
105     this() {
106         this.components[] = 0;
107     }
108 
109     /**
110      * A copy constructor for a vector; makes the same vector, but as a different instance
111      */
112     this(Vector!(T, dimensions) toCopy) {
113         this.components[] = toCopy.components[];
114     }
115 
116     /**
117      * Allows assigning the vector to a static array to set all components of the vector
118      */
119     void opAssign(T[] rhs) {
120         this.components = rhs;
121     }
122 
123     /**
124      * Allows assigning the vector to a single value to set all elements of the vector to such a value
125      */
126     void opAssign(T rhs) {
127         this.components[] = rhs;
128     }
129 
130     /**
131      * Allows the vector to have the joint operator assign syntax
132      * Works component-wise (eg. (3, 2, 1) += (1, 2, 3) makes (3, 2, 1) into (4, 4, 4))
133      */
134     void opOpAssign(string op)(Vector!(T, dimensions) otherVector) {
135         mixin("this.components[] " ~ op ~ "= otherVector.components[];");
136     }
137 
138     /**
139      * Allows the vector to have the joint operator assign syntax
140      * Works component-wise, so each operation of the constant is applied to each component
141      */
142     void opOpAssign(string op)(T[] otherComponents) {
143         mixin("this.components[] " ~ op ~ "= otherComponents[];");
144     }
145 
146     /**
147      * Allows the vector to have the joint operator assign syntax
148      * Works component-wise, so each operation of the constant is applied to each component
149      */
150     void opOpAssign(string op)(T constant) {
151         mixin("this.components[] " ~ op ~ "= constant;");
152     }
153 
154     /**
155      * Casts the vector to a vector of another type
156      */
157     U opCast(U)() if (is(U : Vector!V, V...)) {
158         alias type = TemplateArgsOf!U[0];
159         U newVec = new U(type.init);
160         foreach (i, component; (cast(T[]) this.components).parallel) {
161             newVec.components[i] = cast(type) component;
162         }
163         return newVec;
164     }
165 
166     /**
167      * Allows unary functions to be applied to the vector; aplies the same operator to all components
168      */
169     Vector!(T, dimensions) opUnary(string op)() {
170         Vector!(T, dimensions) newVec = new Vector(this.components);
171         mixin("newVec.components[] = " ~ op ~ "newVec.components[];");
172         return newVec;
173     }
174 
175     /**
176      * Allows the vector to be used with normal operators
177      * Works component-wise (eg. (3, 2, 1) + (1, 2, 3) = (4, 4, 4))
178      */
179     Vector!(T, dimensions) opBinary(string op)(Vector!(T, dimensions) otherVector) {
180         Vector!(T, dimensions) newVec = new Vector(this.components);
181         mixin("newVec.components[] " ~ op ~ "= otherVector.components[];");
182         return newVec;
183     }
184 
185     /**
186      * Allows the vector to be used with normal operators
187      * Works component-wise (eg. (3, 2, 1) + (1, 2, 3) = (4, 4, 4))
188      */
189     Vector!(T, dimensions) opBinary(string op)(T[] otherComponents) {
190         Vector!(T, dimensions) newVec = new Vector(this.components);
191         mixin("newVec.components[] " ~ op ~ "= otherComponents[];");
192         return newVec;
193     }
194 
195     /**
196      * Allows the vector to be used with normal operators
197      * Works component-wise, so each operation of the constant is applied to each component
198      */
199     Vector!(T, dimensions) opBinary(string op)(T constant) {
200         Vector!(T, dimensions) newVec = new Vector(this.components);
201         mixin("newVec.components[] " ~ op ~ "= constant;");
202         return newVec;
203     }
204 
205     /**
206      * Gives the vector a pretty string format
207      * (eg. (1, 2, 3) => <1, 2, 3>)
208      */
209     override string toString() {
210         string representation = "<";
211         foreach (i; 0 .. this.components.length) {
212             representation ~= this.components[i].to!string;
213             representation ~= (i + 1 != this.components.length) ? ", " : ">";
214         }
215         return representation;
216     }
217 
218     /**
219      * Returns whether the vector is equal to another vector or constant
220      * Uses approxEquals to do easy equality for vectors of doubles
221      */
222     override bool opEquals(Object o) {
223         Vector!(T, dimensions) cmp = cast(Vector!(T, dimensions)) o;
224         foreach (i, component; this.components) {
225             if (!approxEqual(component, cmp.components[i])) {
226                 return false;
227             }
228         }
229         return true;
230     }
231 
232     /**
233      * Applies the given function to each element of the vector
234      */
235     void apply(void delegate(T) application) {
236         foreach(component; (cast(T[]) this.components).parallel) {
237             application(component);
238         }
239     }
240 
241     /**
242      * Applies the given function to each element of the vector and returns the results in a new vector
243      */
244     Vector!(U, dimensions) apply(U)(U delegate(T) application) {
245         Vector!(U, dimensions) applied = new Vector!(U, dimensions)(U.init);
246         foreach(i, component; (cast(T[]) this.components).parallel) {
247             applied.components[i] = application(component);
248         }
249         return applied;
250     }
251 
252 }
253 
254 /**
255  * Calculates the dot product or the similarity of two vectors
256  */
257 T dot(T, uint dim)(Vector!(T, dim) first, Vector!(T, dim) second) {
258     immutable pairWiseMultiple = first * second;
259     return pairWiseMultiple.components.sum;
260 }
261 
262 /**
263  * Calculates the cross product or the perpendicular vector to two vectors
264  * Currently only works on 2 or 3 dimensional vectors
265  */
266 Vector!(T, 3) cross(T, uint size)(Vector!(T, size) first, Vector!(T, size) second)
267         if (size == 2 || size == 3) {
268     static if (size == 2) {
269         return new Vector!(T, 3)(0, 0, first.x * second.y - first.y * second.x);
270     }
271     return new Vector!(T, 3)(first.y * second.z - first.z * second.y,
272             first.z * second.x - first.x * second.z, first.x * second.y - first.y * second.x);
273 }