1 module polyplex.math.vector;
2 import std.math, std.traits, std..string;
3 
4 private bool ProperVectorBinaryOperation(string op) {
5 	return op == "/" || op == "*" || op == "+" || op == "-";
6 }
7 
8 private bool ProperVectorUnaryOperation(string op) {
9 	return op == "-" || op == "+";
10 }
11 
12 private auto RSwizzleIndex(char c) {
13 	switch ( toLower(c) ) {
14 		default: assert(false, "Trying to swizzle invalid component '%s'".format(c));
15 		case 'r': case 'x': return 0;
16 		case 'g': case 'y': return 1;
17 		case 'b': case 'z': return 2;
18 		case 'a': case 'w': return 3;
19 	}
20 }
21 unittest {
22 	assert(RSwizzleIndex('r') == 0);
23 	assert(RSwizzleIndex('G') == 1);
24 	assert(RSwizzleIndex('z') == 2);
25 	assert(RSwizzleIndex('W') == 3);
26 }
27 
28 // Returns a list of indices for the swizzle, intended to be enumerated over
29 private auto GenerateSwizzleList(string swizzle_list)() {
30 	assert(swizzle_list.length <= 4, "Can't swizzle with more than 4 elements");
31 	int[] buffer;
32 	static foreach ( swizzle; swizzle_list ) {
33 		buffer ~= RSwizzleIndex(swizzle);
34 	}
35 	return buffer;
36 }
37 
38 /**
39   Applies generic vector operations such as addition, subtraction etc.
40 **/
41 private template GenericVectorOperatorFunctionsMixin(T, int Dim) {
42 	private alias GVec = typeof(this);
43 
44 	/// return a vector by this vector `op` a scalar T
45 	public GVec opBinary(string op)(T rhs) pure const nothrow if ( ProperVectorBinaryOperation(op) ) {
46 		GVec temp_vector;
47 		// Apply operator to each element in the vector. temp[i] = vec[i] op rhs
48 		static foreach ( i; 0 .. Dim )
49 			mixin(q{ temp_vector.data[i] = this.data[i] %s rhs; }.format(op));
50 		return temp_vector;
51 	}
52 
53 	/// return a vector by a scalar T `op` this vector
54 	public GVec opBinaryRight(string op)(T lhs) pure const nothrow if ( ProperVectorBinaryOperation(op) ) {
55 		GVec temp_vector;
56 		// Apply operator to each element in the vector. temp[i] = lhs op vec[i]
57 		static foreach ( i; 0 .. Dim )
58 			mixin(q{ temp_vector.data[i] = lhs %s this.data[i] ; }.format(op));
59 		return temp_vector;
60 	}
61 
62 	/// return a vector operated on by this `op` another vector (of same type)
63 	public GVec opBinary(string op)(GVec rhs) pure const nothrow if ( ProperVectorBinaryOperation(op) ) {
64 		GVec temp_vector;
65 		// Apply operator to each element in the vector. temp[i] = vec[i] op rhs[i]
66 		static foreach ( i; 0 .. Dim )
67 			mixin(q{ temp_vector.data[i] = this.data[i] %s rhs.data[i]; }.format(op));
68 		return temp_vector;
69 	}
70 
71 	/// Operate on vector with a unary operator
72 	public GVec opUnary(string op)() pure const nothrow if ( ProperVectorUnaryOperation(op) ) {
73 		GVec temp_vector;
74 		// Apply unary operator to vector. temp[i] = op vec[i]
75 		static foreach ( i; 0 .. Dim )
76 			mixin(q{ temp_vector.data[i] = %s this.data[i]; }.format(op));
77 		return temp_vector;
78 	}
79 
80 	/// Equality comparison (only on ints)
81 	static if ( is(Type == int) )
82 	public bool opEquals(GVec rhs) pure const nothrow {
83 		bool equality = true;
84 		// propagate equality checks throughout each element comparison of the vectors
85 		static foreach ( i; 0 .. Dim )
86 			equality &= this.data[i] == rhs.data[i];
87 		return equality;
88 	}
89 
90 	/// Casts this vector to another vector type U
91 	public U opCast(U)() pure const nothrow if ( IsVector!U && U.Dim == Dim ) {
92 		U temp_vector;
93 		// Apply cast to each element of vector
94 		static foreach ( i; 0 .. Dim )
95 			temp_vector.data[i] = cast(U.Type)(this.data[i]);
96 		return temp_vector;
97 	}
98 
99 	/// Operator assignment on a scalar or vector
100 	public void opOpAssign(string op, U)(U rhs) pure nothrow if ( __traits(isArithmetic, U) || IsVector!U ) {
101 		mixin(q{ this = this %s rhs; }.format(op));
102 	}
103 }
104 
105 // Unittest on operations
106 unittest {
107 	assert(int3(5) == int3(5));
108 	assert(int3(3) != int3(5));
109 	assert((int3(6)/int3(2) - int3(1)) + int3(3)*int3(5) == int3(17));
110 	assert(int3(6) - 1 == int3(5));
111 	assert(1 - int3(6) == int3(-5));
112 	assert(-int3(6) == +int3(-6));
113 	assert(!__traits(compiles, int3(5)%int3(3)));
114 	assert(__traits(compiles, cast(float3)int3(3)));
115 }
116 
117 /**
118    Applies default functions that should be given to vectors, such as Zero and distance
119  **/
120 private template GenericVectorDefaultFunctionsMixin(T, int Dim) {
121 	private alias GVec = typeof(this);
122 
123 	/// Returns a vector of zero elements
124 	public static GVec Zero ( ) pure nothrow { return GVec(cast(T)0); }
125 
126 	/// Returns a vector of one elements
127 	public static GVec One  ( ) pure nothrow { return GVec(cast(T)1); }
128 
129 	/// Returns a vector of NaN elements (floating points only)
130 	static if (__traits(isFloating, T))
131 	public static GVec NaN  ( ) pure nothrow {
132 		return GVec(T.nan);
133 	}
134 
135 	/// Dot product operator
136 	public T Dot(GVec rhs) pure const nothrow {
137 		T temp = 0;
138 		// multiply each element together and add to temp
139 		static foreach ( i; 0 .. Dim )
140 			temp += this.data[i]*rhs.data[i];
141 		return temp;
142 	}
143 
144 	/// Magnitude/length of a vector (distance from origin)
145 	public alias Magnitude = Length;
146 	public T Length() pure const nothrow {
147 		T accumulator = 0;
148 		// Accumulate the squared of each element in the vector
149 		static foreach ( i; 0 .. Dim )
150 			accumulator += this.data[i]*this.data[i];
151 		// Sqrt the float equivalent and then cast to proper type
152 		return cast(T)sqrt(cast(float)accumulator);
153 	}
154 
155 	/// Distance between two vectors
156 	public T Distance(GVec rhs) pure const nothrow {
157 		return (this - rhs).Length;
158 	}
159 
160 	/// Returns a normalized vector
161 	GVec Normalize() pure const nothrow {
162 		return this.opBinary!"/"(this.Length());
163 	}
164 }
165 
166 // unittest on generic functions
167 unittest {
168 	assert(int3(1, 2, 3).Dot(cast(int3)float3(4, 5, 6)) == 32);
169 	assert(int3(1, 2, 3).Length == 3);
170 	assert(int3(1, 2, 3).Distance(int3(4, 1, 3)) == 3);
171 	assert(int3.One.x == 1);
172 	assert(!__traits(compiles, int3.Nan));
173 	import std.math : abs;
174 	assert(abs(float3(0.2f, 0.6f, 0.5f).Normalize.x-0.248069f) <= 0.02f);
175 }
176 
177 /**
178   Applies assertions, immutables, introspections, component swizzling and other
179     generic functions that can be applied to a vector
180 **/
181 private template GenericVectorMixin(T, int _Dim) {
182 	// -- assertions about type and length
183 	static assert(_Dim >= 2 && _Dim <= 4, "The length of a vector must be 2, 3 or 4.");
184 	static assert(__traits(isArithmetic, T), "Type must be arithmetic (float, int, etc)");
185 	// -- setup immutables
186 	public immutable static size_t Dim = _Dim;
187 	public static alias Type = T;
188 
189 	// -- introspection functions
190 	/// Checks if vector U is compatible with this vector type
191 	public enum IsCompatible(U) = (U.Dim == Dim && is(U.Type == T));
192 
193 	// -- mixins
194 	mixin GenericVectorOperatorFunctionsMixin!(Type, Dim);
195 	mixin GenericVectorDefaultFunctionsMixin!(Type, Dim);
196 
197 	/// Swizzles on one single component returning a single value (ei .x, returning a T)
198 	private auto SwizzleOnOneComponent(string swizzle_list)() pure const nothrow {
199 		// Since GenerateSwizzleList returns an array, index the first element
200 		return this.data[(GenerateSwizzleList!swizzle_list)[0]];
201 	}
202 
203 	/// Swizzles on multiple components returning a vector (ei .xy, returning Vector2T!T)
204 	private auto SwizzleOnMultipleComponents(string swizzle_list)() pure const nothrow {
205 		// create a temporary vector
206 		Vector!(T, swizzle_list.length) ret_vec;
207 		// iterate the swizzle list and fill vector
208 		static foreach ( iter, index; GenerateSwizzleList!swizzle_list )
209 			ret_vec.data[iter] = this.data[index];
210 		return ret_vec;
211 	}
212 
213 	/// opDispatch for vector swizzling
214 	public @property auto opDispatch(string swizzle_list, U = void)() pure const nothrow if ( swizzle_list.length <= 4 ) {
215 		// Check if returning a single element, or a vector
216 		static if ( swizzle_list.length == 1 )
217 			return SwizzleOnOneComponent!swizzle_list;
218 		else
219 			return SwizzleOnMultipleComponents!swizzle_list;
220 	}
221 
222 	/// opDispatch for vector swizzling assignment
223 	public @property auto opDispatch(string swizzle_list, U)(U x) pure nothrow {
224 		// Check if setting a single element or a list of elements
225 		static if ( swizzle_list.length == 1 ) {
226 			// GenerateSwizzleList returns an array, so get first element and set it to x
227 			this.data[(GenerateSwizzleList!swizzle_list)[0]] = x;
228 			return x; // return this scalar ( v.x = v.y = .. )
229 		} else {
230 			// iterate swizzle list and set values of this vector
231 			Vector!(T, swizzle_list.length) tvec = x; // convert scalar to vector
232 			static foreach ( iter, index; GenerateSwizzleList!swizzle_list )
233 				this.data[index] = tvec.data[iter];
234 			return tvec; // return this vector ( v.xy = v.zw = .. )
235 		}
236 	}
237 
238 
239 	/// Returns a pointer to the data container of this vector
240 	public inout(T)* ptr() pure inout nothrow { return data.ptr; }
241 }
242 
243 // unittest on swizzling
244 unittest {
245 	int4 x = int4(1, 2, 3, 4);
246 	assert(x.xyzw == int4(1, 2, 3, 4));
247 	assert(x.xxxx == int4(1));
248 	assert(x.wzyx == int4(4, 3, 2, 1));
249 	assert(x.xz   == int2(1, 3));
250 	assert(x.x    == 1);
251 	int2 y = int2(1, 2);
252 	assert(y.xyxy == int4(1, 2, 1, 2));
253 	assert(y.yyy  == int3(2, 2, 2));
254 }
255 
256 // --- Vector declaration and utility introspection functions
257 struct Vector(T, int Dim);
258 
259 // `is` has an interesting property in that it can generate templates in-place
260 // using its second parameter. Thus you can use an argument-list to check if
261 // T is of any type Vector(U[0], U[1])
262 /// Returns if type is a vector
263 enum IsVector(T) = is(T : Vector!U, U...);
264 
265 unittest {
266 	assert(!IsVector!float);
267 	assert(IsVector!float2);
268 }
269 
270 // --- Vector2T struct
271 alias Vector2T(T) = Vector!(T, 2);
272 alias vec2 = Vector2T!float;
273 alias vec2i = Vector2T!int;
274 alias float2 = Vector2T!float;
275 alias int2 = Vector2T!int;
276 alias Vector2 = Vector2T!float;
277 alias Vector2i = Vector2T!int;
278 
279 struct Vector(T, int _Dim:2) {
280 	public T[2] data = [0, 0];
281 
282 	mixin GenericVectorMixin!(T, 2);
283 
284 	/// Constructor for scalars
285 	public this(U)(U x) { data[] = cast(T)x; }
286 
287 	/// Constructor for parameter lists
288 	public this(U)(U x, U y) { data[] = [x, y]; }
289 
290 	/// Constructor for vectors of same type
291 	public this(Vector2T!T vec) { data[] = vec.data; }
292 
293 	/// Constructor for explicit lists
294 	public this(T[] list) {
295 		assert(list.length == 2, "List length mismatch");
296 		data[] = list;
297 	}
298 
299 	public string toString() { return `<%s, %s>`.format(data[0], data[1]); }
300 	public alias ToString = toString;
301 }
302 
303 // --- Vector3T struct
304 alias Vector3T(T) = Vector!(T, 3);
305 alias vec3 = Vector3T!float;
306 alias vec3i = Vector3T!int;
307 alias float3 = Vector3T!float;
308 alias int3 = Vector3T!int;
309 alias Vector3 = Vector3T!float;
310 alias Vector3i = Vector3T!int;
311 
312 struct Vector(T, int _Dim:3) {
313 	public T[3] data = [0, 0, 0];
314 
315 	mixin GenericVectorMixin!(T, 3);
316 
317 	/// Constructor for scalars
318 	public this(U)(U x) { data[] = cast(T)x; }
319 
320 	/// Constructor for parameter lists
321 	public this(U)(U x, U y, U z) { data[] = [x, y, z]; }
322 
323 	/// Constructor for vectors of same type
324 	public this(Vector3T!T vec) { data[] = vec.data; }
325 
326 	/// Constructor for explicit lists
327 	public this(T[] list) {
328 		assert(list.length == 3, "List length mismatch");
329 		data[] = list;
330 	}
331 
332 	public string toString() { return `<%s, %s, %s>`.format(data[0], data[1], data[2]); }
333 	public alias ToString = toString;
334 
335 	public Vector3T!T Cross(Vector3T!T a, Vector3T!T b) pure const nothrow {
336 		return Vector3T!T(
337 			a.y*b.z - a.z*b.y,
338 			a.z*b.x - a.x * b.z,
339 			a.x*b.y - a.y * b.x);
340 	}
341 }
342 
343 // --- Vector4T struct
344 alias Vector4T(T) = Vector!(T, 4);
345 alias vec4 = Vector4T!float;
346 alias vec4i = Vector4T!int;
347 alias float4 = Vector4T!float;
348 alias int4 = Vector4T!int;
349 alias Vector4 = Vector4T!float;
350 alias Vector4i = Vector4T!int;
351 
352 struct Vector(T, int _Dim:4) {
353 	public T[4] data = [0, 0, 0, 0];
354 
355 	mixin GenericVectorMixin!(T, 4);
356 
357 	/// Constructor for scalars
358 	public this(U)(U x) { data[] = cast(T)x; }
359 
360 	/// Constructor for parameter lists
361 	public this(U)(U x, U y, U z, U w) { data[] = [x, y, z, w]; }
362 
363 	/// Constructor for vectors of same type
364 	public this(Vector4T!T vec) { data[] = vec.data; }
365 
366 	/// Constructor for explicit lists
367 	public this(T[] list) {
368 		assert(list.length == 4, "List length mismatch");
369 		data[] = list;
370 	}
371 
372 	string toString() { return `<%s, %s, %s, %s>`.format(data[0], data[1], data[2], data[3]); }
373 	alias ToString = toString;
374 }