1 module polyplex.math.linear.vectors;
2 import std.math, std.traits, std.string, std.range.primitives, std.format;
3 import polyplex.math;
4 
5 private:
6 
7 /// Shared constructors between vectors
8 enum SharedVectorCtor = q{
9 
10 	/// Special constructor that fills the vector with specified value
11 	this(Y)(Y value) if (isNumeric!Y) {
12 		static foreach(i; 0..Dimensions) {
13 			values[i] = cast(T)value;
14 		}
15 	}
16 
17 	/// Construct from vector
18 	this(Y)(Y other) if (IsVector!Y) {
19 		static foreach(i; 0..Dimensions) {
20 			// If we're outside the bounds of the other vector then skip the axies
21 			static if (i < other.Dimensions) mixin(q{ values[i] = cast(T)other.values[i]; }); 
22 		}
23 	}
24 
25 	/// Construct from static array of coordinates
26 	this(T[Dimensions] values) {
27 		this = values[0..Dimensions];
28 	}
29 
30 	/// Construct from dynamic array of coordinates
31 	this(T[] values) {
32 		this = values[0..Dimensions];
33 	}
34 };
35 
36 /// Vector operations are implemented via this.
37 mixin template SharedVectorOp(T, GVector, int Dimensions) {
38 
39 	static assert(Dimensions > 1 && Dimensions < 5, "The dimensions of a vector can minimum be 2, maximum be 4");
40 
41 	/**
42 		OpenGL helper function to get the pointer to the start of the vector data.
43 	*/
44 	T* ptr() { return values.ptr; }
45 
46 	/// Allow casting this vector to an other type of vector
47 	Y opCast(Y)() if (IsVector!Y) {
48 		return Y(this);
49 	}
50 
51 	/// Allow doing binary operations between 2 vectors
52 	GVector opBinary(string op, T2)(T2 other) if (IsVector!T2) {
53 		GVector vec;
54 		static foreach(i; 0..Dimensions) {
55 			// If we're outside the bounds of the other vector then skip the axies
56 			static if (i < other.Dimensions) mixin(q{ vec.values[i] = this.values[i] %s other.values[i]; }.format(op)); 
57 		}
58 		return vec;
59 	}
60 
61 	/// Allow doing binary operations between a vector and a numeric type
62 	GVector opBinary(string op, T2)(T2 other) if (isNumeric!(T2)) {
63 		GVector vec;
64 		static foreach(i; 0..Dimensions) {
65 			mixin(q{ vec.values[i] = this.values[i] %s other; }.format(op)); 
66 		}
67 		return vec;
68 	}
69 
70 	/// Binary operations between this and a vector or a numeric type.
71 	void opOpAssign(string op, T2)(T2 other) if (isNumeric!T2 || IsVector!T2) {
72 		mixin(q{this = this %s other;}.format(op));
73 	}
74 
75 	/// Binary operations between this and a numeric type.
76 	void opAssign(T2)(T2 other) if (isNumeric!T2) {
77 		this = [other];
78 	}
79 
80 	/// Binary operations between this and a vector.
81 	void opAssign(T2)(T2 other) if (!is(T2 : GVector) && IsVector!T2) {
82 		this = cast(GVector)other;
83 	}
84 
85 	/// Binary operations between this and arrays.
86 	void opAssign(T2)(T2 rhs) if (IsNumericArray!T2) {
87 		static foreach(i; 0..Dimensions) {
88 			values[i] = cast(T)rhs[i];
89 		}
90 	}
91 
92 	/**
93 		Get the difference between 2 vectors
94 		Any difference outside the bounds of the vector will be ignored.
95 	*/
96 	GVector Difference(T2)(T2 other) if (IsVector!T2) {
97 		return other-this;
98 	}
99 
100 	/**
101 		Get the distance between 2 vectors
102 		Any difference outside the bounds of the vector will be ignored.
103 	*/
104 	T Distance(T2)(T2 other) if (IsVector!T2) {
105 		return (other-this).Length;
106 	}
107 
108 	/**
109 		Get the length/magnitude of this vector
110 	*/
111 	T Length() {
112 		T len;
113 		static foreach(i; 0..Dimensions) {
114 			len += (values[i]*values[i]);
115 		}
116 		return cast(T)Mathf.Sqrt(cast(float)len);
117 	}
118 
119 	/**
120 		Get the length/magnitude of this vector
121 	*/
122 	alias Magnitude = Length;
123 
124 	/**
125 		Get the normalized vector
126 	*/
127 	GVector Normalize() {
128 		// Normalize by dividing the values from the current values array by the magnitude/length
129 		GVector vec;
130 		immutable(T) len = Length();
131 		static foreach(i; 0..Dimensions) {
132 			vec.values[i] = values[i]/len;
133 		}
134 		return vec;
135 	}
136 
137 	/**
138 		Get the dot product of 2 vectors
139 		Any difference outside the bounds of the vector will be ignored.
140 	*/
141 	T Dot(T2)(T2 other) if (IsVector!T2) {
142 		T dot;
143 		static foreach(i; 0..Dimensions) {
144 			static if (i < other.Dimensions) dot += (val*other.values[i]);
145 		}
146 		return cast(T)dot;
147 	}
148 
149 	/**
150 		Returns a one vector (vector of all ones)
151 	*/
152 	static GVector One() {
153 		return GVector(1);
154 	}
155 
156 	/**
157 		Returns a zero vector (vector of all zeroes)
158 	*/
159 	static GVector Zero() {
160 		return GVector(0);
161 	}
162 
163 	/**
164 		Returns a zero vector (vector of all NaN)
165 	*/
166 	static GVector NaN()() if (is(T : float) || is(T : double)) {
167 		return GVector(T.nan);
168 	}
169 
170 	/**
171 		Returns a zero vector (vector of all infinity)
172 	*/
173 	static GVector Infinity()() if (is(T : float) || is(T : double))  {
174 		return GVector(T.infinity);
175 	}
176 
177 	/**
178 		Cross product of 2D vector
179 	*/
180 	GVector Cross()(GVector other) if (Dimensions == 2) {
181 		return GVector(
182 			(this.Y*other.Z)-(this.Z*other.Y),
183 			(this.X*other.Y)-(this.Y*other.X));
184 	}
185 
186 	/**
187 		Cross product of 2D vector
188 	*/
189 	GVector Cross()(GVector other) if (Dimensions == 3) {
190 		return GVector(
191 			(this.Y*other.Z)-(this.Z*other.Y),
192 			(this.Z*other.X)-(this.X*other.Z),
193 			(this.X*other.Y)-(this.Y*other.X));
194 	}
195 
196 	static if (Dimensions >= 2) {
197 
198 		/**
199 			Returns a vector pointing to the global up
200 		*/
201 		static GVector Up() {
202 			GVector v;
203 			v.Y = -1;
204 			return v;
205 		}
206 
207 		/**
208 			Returns a vector pointing to the global down
209 		*/
210 		static GVector Down() {
211 			GVector v;
212 			v.Y = 1;
213 			return v;
214 		}
215 
216 		/**
217 			Returns a vector pointing to the global left
218 		*/
219 		static GVector Left() {
220 			GVector v;
221 			v.X = -1;
222 			return v;
223 		}
224 
225 		/**
226 			Returns a vector pointing to the global right
227 		*/
228 		static GVector Right() {
229 			GVector v;
230 			v.X = 1;
231 			return v;
232 		}
233 	}
234 
235 	static if (Dimensions >= 3) {
236 
237 		/// TODO: If the Z axis is flipped then flip the logic here!
238 
239 		/**
240 			Returns a vector pointing to the global forward
241 			This is only usable on Vectors with a Z axis!
242 		*/
243 		static GVector Forward() {
244 			GVector v;
245 			v.Z = 1;
246 			return v;
247 		}
248 
249 		/**
250 			Returns a vector pointing to the global backward
251 			This is only usable on Vectors with a Z axis!
252 		*/
253 		static GVector Backward() {
254 			GVector v;
255 			v.Z = -1;
256 			return v;
257 		}
258 
259 	}
260 
261 	string toString() {
262 		import std.conv : text;
263 		return values.text;
264 	}
265 }
266 
267 
268 public:
269 /**
270 	A 2D vector
271 */
272 @trusted 
273 nothrow
274 struct Vector2T(T) if (isNumeric!T) {
275 private:
276 	alias GVector = typeof(this);
277 
278 public:
279 	/// The count of dimensions the vector consists of
280 	enum Dimensions = 2;
281 
282 	alias Type = T;
283 
284 	union {
285 		struct {
286 			/// The X component
287 			T X = 0;
288 
289 			/// The Y component
290 			T Y = 0;
291 		}
292 
293 		/// An array of the values in this vector
294 		T[Dimensions] values;
295 	}
296 
297 	/// Construct from X and Y coordinates
298 	this(Y)(Y x, Y y) if (isNumeric!Y) {
299 		this.X = cast(T)x;
300 		this.Y = cast(T)y;
301 	}
302 
303 	mixin(SharedVectorCtor);
304 	mixin SharedVectorOp!(T, GVector, Dimensions);
305 }
306 
307 /**
308 	A 3D vector
309 */
310 @trusted 
311 nothrow
312 struct Vector3T(T) if (isNumeric!T) {
313 private:
314 	alias GVector = typeof(this);
315 
316 public:
317 	/// The count of dimensions the vector consists of
318 	enum Dimensions = 3;
319 
320 	alias Type = T;
321 
322 	union {
323 		struct {
324 			/// The X component
325 			T X = 0;
326 
327 			/// The Y component
328 			T Y = 0;
329 
330 			/// The Z component
331 			T Z = 0;
332 		}
333 
334 		/// An array of the values in this vector
335 		T[Dimensions] values;
336 	}
337 
338 	/// Construct from X and Y coordinates
339 	this(Y)(Y x, Y y) if (isNumeric!Y) {
340 		this.X = cast(T)x;
341 		this.Y = cast(T)y;
342 	}
343 
344 	/// Construct from X and Y coordinates
345 	this(Y)(Y x, Y y, Y z) if (isNumeric!Y) {
346 		this.X = cast(T)x;
347 		this.Y = cast(T)y;
348 		this.Z = cast(T)z;
349 	}
350 	
351 	mixin(SharedVectorCtor);
352 	mixin SharedVectorOp!(T, GVector, Dimensions);
353 }
354 
355 /**
356 	A 4D vector
357 */
358 @trusted 
359 nothrow
360 struct Vector4T(T) if (isNumeric!T) {
361 private:
362 	alias GVector = typeof(this);
363 
364 public:
365 	/// The count of dimensions the vector consists of
366 	enum Dimensions = 4;
367 
368 	alias Type = T;
369 
370 	union {
371 		struct {
372 			/// The X component
373 			T X = 0;
374 
375 			/// The Y component
376 			T Y = 0;
377 
378 			/// The Z component
379 			T Z = 0;
380 
381 			/// The Z component
382 			T W = 0;
383 		}
384 
385 		/// An array of the values in this vector
386 		T[Dimensions] values;
387 	}
388 
389 	/// Construct from X and Y coordinates
390 	this(Y)(Y x, Y y) if (isNumeric!Y) {
391 		this.X = cast(T)x;
392 		this.Y = cast(T)y;
393 	}
394 
395 	/// Construct from X and Y coordinates
396 	this(Y)(Y x, Y y, Y z) if (isNumeric!Y) {
397 		this.X = cast(T)x;
398 		this.Y = cast(T)y;
399 		this.Z = cast(T)z;
400 	}
401 
402 	/// Construct from X and Y coordinates
403 	this(Y)(Y x, Y y, Y z, Y w) if (isNumeric!Y) {
404 		this.X = cast(T)x;
405 		this.Y = cast(T)y;
406 		this.Z = cast(T)z;
407 		this.W = cast(T)w;
408 	}
409 
410 	// Implement shared vector operations & constructors
411 	mixin(SharedVectorCtor);
412 	mixin SharedVectorOp!(T, GVector, Dimensions);
413 }
414 
415 // Copied from the GLMath implementation, checks if T is a Vector2T
416 enum IsVector2T(T) = is(T : Vector2T!U, U...);
417 
418 // Copied from the GLMath implementation, checks if T is a Vector3T
419 enum IsVector3T(T) = is(T : Vector3T!U, U...);
420 
421 // Copied from the GLMath implementation, checks if T is a Vector4T
422 enum IsVector4T(T) = is(T : Vector4T!U, U...);
423 
424 enum IsNumericArray(T) = isArray!T && isNumeric!(ElementType!T);
425 
426 //Combination of all IsVector
427 enum IsVector(T) = (IsVector2T!T || IsVector3T!T || IsVector4T!T);
428 
429 alias Vector2 = Vector2T!float;
430 alias Vector2i = Vector2T!int;
431 alias Vector3 = Vector3T!float;
432 alias Vector3i = Vector3T!int;
433 alias Vector4 = Vector4T!float;
434 alias Vector4i = Vector4T!int;