1 /*
2  * Copyright (c) 2017-2018 SEL
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU Lesser General Public License for more details.
13  * 
14  */
15 /**
16  * Copyright: Copyright (c) 2017-2020 sel-project
17  * License: MIT
18  * Authors: Kripth
19  * Source: $(HTTP github.com/sel-project/sel-math/sel/math/vector.d, sel/math/vector.d)
20  */
21 module sel.math.vector;
22 
23 import std.algorithm : reverse, canFind;
24 import std.array : join, split;
25 import std.conv : to, ConvException;
26 static import std.math;
27 import std.meta : staticIndexOf;
28 import std.range.primitives : ElementType;
29 import std.string : replace;
30 import std.traits : IntegralTypeOf, isNumeric, isArray, CommonType, isFloatingPointTrait = isFloatingPoint, isImplicitlyConvertible;
31 import std.typecons : isTuple;
32 import std.typetuple : TypeTuple;
33 
34 static import std.typecons;
35 
36 /**
37  * Vector for coordinates storing and operations.
38  */
39 struct Vector(T, char[] c) if(c.length > 1 && areValidCoordinates(c)) {
40 	
41 	public alias Type = T;
42 	public alias coordinates = c;
43 
44 	mixin("alias Tuple = std.typecons.Tuple!(T, \"" ~ join(coordinates.idup.split(""), "\", T, \"") ~ "\");");
45 	
46 	mixin("public enum coords = TypeTuple!('" ~ join(coordinates.idup.split(""), "','") ~ "');");
47 	
48 	enum bool isFloatingPoint = isFloatingPointTrait!T;
49 	
50 	//private Tuple value;
51 
52 	union {
53 
54 		Tuple value;
55 		struct { mixin("T " ~ join(coordinates.idup.split(""), ";T ") ~ ";"); }
56 		T[c.length] array;
57 
58 	}
59 
60 	alias tuple = value;
61 	
62 	public pure nothrow @safe @nogc this(in Tuple value) {
63 		this.value = value;
64 	}
65 	
66 	public pure nothrow @safe @nogc this(in T value) {
67 		this.array = value;
68 	}
69 	
70 	public @safe this(E...)(in E args) if(E.length == coordinates.length) {
71 		foreach(i, value; args) {
72 			this.array[i] = value;
73 		}
74 	}
75 	
76 	public @safe @nogc this(in T[coords.length] array) {
77 		this.array = array;
78 	}
79 	
80 	public @safe @nogc this(in T[] array) {
81 		this.array = array;
82 	}
83 	
84 	/**
85 	 * Compares the vector with another vector of the same length or with
86 	 * a single number.
87 	 * Returns: true if all the values are equals, false otherwise
88 	 * Example:
89 	 * ---
90 	 * assert(Vector2!int(0, 10) == Vector2!int(0, 10));
91 	 * assert(Vector3!ubyte(1, 1, 255) == Vector3!real(1, 1, 255));
92 	 * assert(vector(0, 0, 0, 0) == 0);
93 	 * assert(vector(1, 2) == [1, 2]);
94 	 * assert(vector(float.nan, float.nan) != vector(float.nan, float.nan));
95 	 * ---
96 	 */
97 	public bool opEquals(F)(in F value) inout {
98 		static if(isVector!F && coords == F.coords) return this.opEqualsImpl!"this.{c}==value.{c}"(value);
99 		else static if(isArray!F) return value.length == coords.length && this.opEqualsImpl!"this.{c}==value[{i}]"(value);
100 		else static if(__traits(compiles, T.init == F.init)) return this.opEqualsImpl!"this.{c}==value"(value);
101 		else return false;
102 	}
103 	
104 	private bool opEqualsImpl(string op, F)(F value) inout {
105 		mixin((){
106 				string[] ret;
107 				foreach(i, immutable c; coords) {
108 					ret ~= op.replace("{c}", to!string(c)).replace("{i}", to!string(i));
109 				}
110 				return "return " ~ ret.join("&&") ~ ";";
111 			}());
112 	}
113 
114 	// for associative arrays key
115 	/*public bool opEquals(ref const Vector) const {
116 		return false;
117 	}*/
118 	
119 	/**
120 	 * Performs an unary operation on the vector.
121 	 * Returns: the new vector
122 	 * Example:
123 	 * ---
124 	 * auto v = vector(-1, 0, 1);
125 	 * assert(-v == vector(1, 0, -1));
126 	 * assert(++v == vector(0, 1, 2)); // this will change the original vector's values!
127 	 * assert(v-- == vector(0, 1, 2) && v == vector(-1, 0, 1));
128 	 * ---
129 	 */
130 	public typeof(this) opUnary(string op)() if(__traits(compiles, { mixin("T t;t=" ~ op ~ "t;"); })) {
131 		typeof(this) ret;
132 		foreach(immutable c ; coords) {
133 			mixin("ret.value." ~ c ~ "=" ~ op ~ "this.value." ~ c ~ ";");
134 		}
135 		return ret;
136 	}
137 	
138 	/**
139 	 * Performs a binary operation on the vector.
140 	 * Params:
141 	 * 		value = a number, a vector or an array with the same size
142 	 * Returns: the new vector
143 	 * Example:
144 	 * ---
145 	 * assert(vector(1, 1) - 1 == vector(0, 0));
146 	 * assert(vector(10, 10) * vector(0, 9) == vector(0, 90));
147 	 * assert(vector(16, 15) & [15, 3] == vector(0, 3));
148 	 * assert(1 - vector(100, 0, -100) == vector(-99, 1, 101));
149 	 * ---
150 	 */
151 	public typeof(this) opBinary(string op, F)(F value) inout if(op != "in") {
152 		return this.dup.opOpAssign!op(value);
153 	}
154 	
155 	public typeof(this) opBinaryRight(string op, F)(F value) inout if(op != "in" && __traits(compiles, typeof(this)(value))) {
156 		return typeof(this)(value).opBinary!op(this);
157 	}
158 	
159 	/**
160 	 * Performs an assign operation on the vector, modifying it.
161 	 * Params:
162 	 * 		value = a number, a vector or an array with the same size
163 	 * Returns:
164 	 * Example:
165 	 * ---
166 	 * auto v = vector(1, 2);
167 	 * v += 4;
168 	 * v *= [0, 2];
169 	 * assert(v == vector(0, 12));
170 	 * ---
171 	 */
172 	public typeof(this) opOpAssign(string op, F)(F value) if(isVector!F && coordinates == F.coordinates) {
173 		return this.opAssignImpl!("this.value.{c}" ~ op ~ "=value.{c}")(value);
174 	}
175 	
176 	/// ditto
177 	public typeof(this) opOpAssign(string op, F)(F value) if(isArray!F) {
178 		return this.opAssignImpl!("this.value.{c}" ~ op ~ "=value[{i}]")(value);
179 	}
180 	
181 	/// ditto
182 	public typeof(this) opOpAssign(string op, F)(F value) if(isImplicitlyConvertible!(F, T)) {
183 		return this.opAssignImpl!("this.value.{c}" ~ op ~ "=value")(value);
184 	}
185 	
186 	private typeof(this) opAssignImpl(string query, F)(F value) {
187 		foreach(i, immutable c; coords) {
188 			mixin(query.replace("{c}", to!string(c)).replace("{i}", to!string(i)) ~ ";");
189 		}
190 		return this;
191 	}
192 	
193 	/**
194 	 * Converts the vector to the given one, mantaining the variables's
195 	 * value when possible.
196 	 * Example:
197 	 * ---
198 	 * assert(cast(Vector2!int)vector(.1, .1, 14) == vector(0, 14));
199 	 * assert(cast(Vector4!real)vector(.5, 100) == vector(.5, 0, 100, 0));
200 	 * // this will only return the vector
201 	 * assert(cast(Vector2!int
202 	 * ---
203 	 */
204 	public @safe auto opCast(F)() inout if(isVector!F) {
205 		static if(is(T == F) && coordinates == F.coordinates) {
206 			return this;
207 		} else {
208 			F ret;
209 			foreach(immutable c; F.coords) {
210 				static if(coordinates.canFind(c)) {
211 					mixin("ret.value." ~ c) = to!(F.Type)(mixin("this." ~ c));
212 				}
213 			}
214 			return ret;
215 		}
216 	}
217 	
218 	/**
219 	 * Converts the vector into an array of the same size.
220 	 * Example:
221 	 * ---
222 	 * assert(cast(int[])vector(1, 2) == [1, 2]);
223 	 * assert(cast(long[])vector(.1, 1.5, -.1) == [0L, 1L, 0L]);
224 	 * ---
225 	 */
226 	public @safe auto opCast(F)() inout if(isArray!F) {
227 		F array = new typeof(F.init[0])[coords.length];
228 		foreach(i, coord; coords) {
229 			array[i] = to!(typeof(F.init[0]))(mixin("this." ~ coord));
230 		}
231 		return array;
232 	}
233 	
234 	/**
235 	 * Changes the vector's type.
236 	 */
237 	public auto type(F)() inout if(isImplicitlyConvertible!(F, T)) {
238 		Vector!(F, coordinates) ret;
239 		foreach(immutable c ; coords) {
240 			mixin("ret.value." ~ c) = mixin("this." ~ c);
241 		}
242 		return ret;
243 	}
244 	
245 	/**
246 	 * Duplicates the vector, mantaing the type, variables'
247 	 * names and their value.
248 	 * Example:
249 	 * ---
250 	 * assert(vector(1, 1).dup == vector(1, 1));
251 	 * ---
252 	 */
253 	alias dup = type!T;
254 	
255 	/**
256 	 * Gets the vector's length.
257 	 */
258 	public @property double length() inout {
259 		double length = 0;
260 		foreach(immutable c ; coords) {
261 			length += mixin("this." ~ c) ^^ 2;
262 			//mixin("length += this.value." ~ c ~ " * this.value." ~ c ~ ";");
263 		}
264 		return std.math.sqrt(length);
265 	}
266 	
267 	/**
268 	 * Sets the vector's length.
269 	 */
270 	public @property double length(double length) {
271 		double mult = length / this.length;
272 		foreach(immutable c ; coords) {
273 			static if(is(T == double)) {
274 				mixin("this.value." ~ c) *= mult;
275 			} else {
276 				mixin("this.value." ~ c) = cast(T)(mixin("this." ~ c) * mult);
277 			}
278 		}
279 		return length;
280 	}
281 	
282 	/**
283 	 * Converts the vector into a string for logging and debugging purposes.
284 	 */
285 	public string toString() inout {
286 		string[] cs;
287 		foreach(i, coord; coords) {
288 			cs ~= to!string(mixin("this." ~ coord));
289 		}
290 		return "Vector!(" ~ T.stringof ~ ", \"" ~ coordinates.idup ~ "\")(" ~ cs.join(", ") ~ ")";
291 	}
292 	
293 }
294 
295 /// ditto
296 alias Vector(T, string coords) = Vector!(T, coords.dup);
297 
298 /// ditto
299 alias Vector2(T) = Vector!(T, "xz");
300 
301 /// ditto
302 alias Vector3(T) = Vector!(T, "xyz");
303 
304 /// ditto
305 alias Vector4(T) = Vector!(T, "xyzw");
306 
307 private bool areValidCoordinates(char[] coords) {
308 	foreach(i, char c; coords[0..$-1]) {
309 		if(coords[i+1..$].canFind(c)) return false;
310 	}
311 	return true;
312 }
313 
314 /**
315  * Automatically creates a vector if the number of the
316  * given arguments matches one of the default vectors.
317  * Example:
318  * ---
319  * assert(is(typeof(vector(1, 1)) == Vector2!int));
320  * assert(is(typeof(vector(2Lu, 4)) == Vector2!ulong));
321  * assert(is(typeof(vector(5, 5, 19.0)) == Vector3!double));
322  * assert(is(typeof(vector(0, real.nan, double.nan, float.nan)) == Vector4!real));
323  * ---
324  */
325 public auto vector(E...)(E args) if(E.length > 1 && E.length <= 4 && !is(CommonType!E == void)) {
326 	return mixin("Vector" ~ to!string(E.length) ~ "!(CommonType!E)")(args);
327 }
328 
329 /// Checks if the given type is a vector
330 enum bool isVector(T) = __traits(compiles, Vector!(T.Type, T.coordinates)(T.Type.init));
331 
332 public nothrow @safe T mathFunction(alias func, T)(T vector) if(isVector!T) {
333 	T.Type[] values;
334 	foreach(immutable c ; T.coords) {
335 		values ~= cast(T.Type)func(mixin("vector." ~ c));
336 	}
337 	return T(values);
338 }
339 
340 /**
341  * Rounds a vector to the nearest integer.
342  * Example:
343  * ---
344  * assert(round(vector(.25, .5, .75)) == vector(0, 1, 1));
345  * ---
346  */
347 public nothrow @safe T round(T)(T vector) if(isVector!T) {
348 	return mathFunction!(std.math.round)(vector);
349 }
350 
351 /**
352  * Floors a vector to the nearest integer.
353  * Example:
354  * ---
355  * assert(floor(vector(.25, .5, .75)) == vector(0, 0, 0));
356  * ---
357  */
358 public nothrow @safe T floor(T)(T vector) if(isVector!T) {
359 	return mathFunction!(std.math.floor)(vector);
360 }
361 
362 /**
363  * Ceils a vector to the nearest integer.
364  * Example:
365  * ---
366  * assert(ceil(vector(.25, .5, .75)) == vector(1, 1, 1));
367  * ---
368  */
369 public nothrow @safe T ceil(T)(T vector) if(isVector!T) {
370 	return mathFunction!(std.math.ceil)(vector);
371 }
372 
373 /**
374  * Calculate the absolute value of the array.
375  * Example:
376  * ---
377  * assert(abs(vector(-1, 0, 90)) == vector(1, 0, 90));
378  * ---
379  */
380 public nothrow @safe T abs(T)(T vector) if(isVector!T) {
381 	return mathFunction!(std.math.abs)(vector);
382 }
383 
384 /**
385  * Checks whether or not every member of the vector is finite
386  * (not infite, -inifite, nan).
387  * Example:
388  * ---
389  * assert(isFinite(vector(1, 2)));
390  * assert(isFinite(vector(float.min, float.max)));
391  * assert(!isFinite(vector(1, float.nan)));
392  * assert(!isFinite(vector(-float.infinity, 1f/0f)));
393  * ---
394  */
395 public pure nothrow @safe @nogc bool isFinite(T)(T vector) if(isVector!T && T.isFloatingPoint) {
396 	foreach(immutable c ; T.coords) {
397 		if(!std.math.isFinite(mixin("vector." ~ c))) return false;
398 	}
399 	return true;
400 }
401 
402 /**
403  * Checks whether or not at least one member of the vector
404  * is not a number (nan).
405  * Example:
406  * ---
407  * assert(!isNaN(vector(0, 2.1)));
408  * assert(isNaN(vector(float.init, -double.init)));
409  * assert(isNaN(vector(0, float.nan)));
410  * ---
411  */
412 public pure nothrow @safe @nogc bool isNaN(T)(T vector) if(isVector!T && T.isFloatingPoint) {
413 	foreach(immutable c ; T.coords) {
414 		if(std.math.isNaN(mixin("vector." ~ c))) return true;
415 	}
416 	return false;
417 }
418 
419 public @safe double distanceSquared(F, G)(F vector1, G vector2) if(isVector!F && isVector!G && F.coordinates == G.coordinates) {
420 	double sum = 0;
421 	foreach(immutable c ; F.coords) {
422 		sum += std.math.pow(mixin("vector1." ~ c) - mixin("vector2." ~ c), 2);
423 	}
424 	return sum;
425 }
426 
427 /**
428  * Calculates the distance between to vectors of the
429  * same length.
430  * Params:
431  * 		vector1 = the first vector
432  * 		vector2 = the second vector
433  * Returns: the distance between the two vectors (always higher or equals than  0)
434  * Example:
435  * ---
436  * assert(distance(vector(0, 0), vector(1, 0)) == 1);
437  * assert(distance(vector(0, 0, 0) == vector(1, 1, 1)) == 3 ^^ .5); // 3 ^^ .5 is the squared root of 3
438  * ---
439  */
440 public @safe double distance(T, char[] coords, E)(Vector!(T, coords) vector1, Vector!(E, coords) vector2) {
441 	return std.math.sqrt(distanceSquared(vector1, vector2));
442 }
443 
444 public pure nothrow @safe double dot(T, char[] coords, E)(Vector!(T, coords) vector1, Vector!(E, coords) vector2) {
445 	double dot = 0;
446 	foreach(immutable c ; Vector!(T, coords).coords) {
447 		dot += mixin("vector1." ~ c) * mixin("vector2." ~ c);
448 	}
449 	return dot;
450 }
451 
452 public pure nothrow @safe Vector!(CommonType!(A, B), coords) cross(A, B, char[] coords)(Vector!(A, coords) a, Vector!(B, coords) b) {
453 	foreach(immutable exc ; Vector!(T, coords).coords) {
454 		
455 	}
456 }
457 
458 unittest {
459 	
460 	Vector3!int v3 = Vector3!int(-1, 0, 12);
461 
462 	// storage
463 	assert(v3.x == -1);
464 	assert(v3.y == 0);
465 	assert(v3.z == 12);
466 	assert(v3.tuple == Vector3!int.Tuple(-1, 0, 12));
467 	assert(v3.array == [-1, 0, 12]);
468 	
469 	// comparing
470 	assert(v3.x == -1);
471 	assert(v3.y == 0);
472 	assert(v3.z == 12);
473 	assert(v3 == Vector3!int(-1, 0, 12));
474 	assert(v3 == Vector3!float(-1, 0, 12));
475 	assert(v3 != Vector3!double(-1, 0, 12.00000001));
476 	
477 	// unary
478 	assert(-v3 == Vector3!int(1, 0, -12));
479 	assert(++v3 == Vector3!int(0, 1, 13) && v3 == Vector3!int(0, 1, 13));
480 	assert(v3-- == Vector3!int(0, 1, 13) && v3 == Vector3!int(-1, 0, 12));
481 	
482 	// binary operator
483 	assert(v3 + 3 == Vector3!int(2, 3, 15));
484 	assert(v3 * 100 == Vector3!int(-100, 0, 1200));
485 	assert(v3 - v3 == Vector3!int(0, 0, 0));
486 	assert(Vector3!double(.5, 0, 0) + v3 == Vector3!double(-.5, 0, 12));
487 	assert(v3 * [1, 2, 3] == Vector3!int(-1, 0, 36));
488 	assert((v3 & 1) == Vector3!int(1, 0, 0));
489 	assert(1 - v3 == Vector3!int(2, 1, -11));
490 	
491 	// assign operator
492 	assert((v3 *= 3) == Vector3!int(-3, 0, 36));
493 	assert(v3 == Vector3!int(-3, 0, 36));
494 	v3 >>= 1;
495 	assert(v3 == Vector3!int(-2, 0, 18));
496 	
497 	// cast
498 	Vector3!float v3f = cast(Vector3!float)v3;
499 	Vector2!int reduced = cast(Vector2!int)v3;
500 	Vector4!long bigger = cast(Vector4!long)v3;
501 	assert(v3f == Vector3!float(-2, 0, 18));
502 	assert(reduced == Vector2!float(-2, 18));
503 	assert(bigger == Vector4!long(-2, 0, 18, 0));
504 	
505 	// vector function
506 	assert(vector(8, 19).Type.stringof == "int");
507 	assert(vector(1.0, 2, 99.9).Type.stringof == "double");
508 	assert(vector(1f, .01, 12L).Type.stringof == "double");
509 	
510 	// math functions
511 	assert(round(vector(.2, .7)) == vector(0, 1));
512 	assert(floor(vector(.2, .7)) == vector(0, 0));
513 	assert(ceil(vector(.2, .7)) == vector(1, 1));
514 	assert(abs(vector(-.2, .7)) == vector(.2, .7));
515 	
516 	// distance
517 	assert(distance(vector(0, 0), vector(0, 1)) == 1);
518 	assert(distance(vector(0, 0, 0), vector(1, 1, 1)) == 3 ^^ .5);
519 
520 	// as associative array key
521 	uint[Vector2!int] aa;
522 	aa[Vector2!int(1, 2)] = 4;
523 	assert(aa[Vector2!int(1, 2)] == 4);
524 	
525 }