1 module polyplex.math.glmath.vector;
2 import std.math, std.traits, std.string;
3 
4 private bool ProperVectorBinaryOperation(string op) {
5 	return op == "/" || op == "*" || 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 	/// Returns a pointer to the data container of this vector
239 	public inout(T)* ptr() pure inout nothrow { return data.ptr; }
240 }
241 
242 // unittest on swizzling
243 unittest {
244 	int4 x = int4(1, 2, 3, 4);
245 	assert(x.xyzw == int4(1, 2, 3, 4));
246 	assert(x.xxxx == int4(1));
247 	assert(x.wzyx == int4(4, 3, 2, 1));
248 	assert(x.xz   == int2(1, 3));
249 	assert(x.x    == 1);
250 	int2 y = int2(1, 2);
251 	assert(y.xyxy == int4(1, 2, 1, 2));
252 	assert(y.yyy  == int3(2, 2, 2));
253 }
254 
255 // --- Vector declaration and utility introspection functions
256 struct Vector(T, int Dim);
257 
258 // `is` has an interesting property in that it can generate templates in-place
259 // using its second parameter. Thus you can use an argument-list to check if
260 // T is of any type Vector(U[0], U[1])
261 /// Returns if type is a vector
262 enum IsVector(T) = is(T : Vector!U, U...);
263 
264 unittest {
265 	assert(!IsVector!float);
266 	assert(IsVector!float2);
267 }
268 
269 // --- Vector2T struct
270 alias Vector2T(T) = Vector!(T, 2);
271 alias vec2 = Vector2T!float;
272 alias vec2i = Vector2T!int;
273 alias float2 = Vector2T!float;
274 alias int2 = Vector2T!int;
275 
276 // For compatiblity with simplemath.
277 alias Vector2 = Vector2T!float;
278 alias Vector2i = Vector2T!int;
279 
280 struct Vector(T, int _Dim:2) {
281 	public T[2] data = [0, 0];
282 
283 	mixin GenericVectorMixin!(T, 2);
284 
285 	/// Constructor for scalars
286 	public this(U)(U x) { data[] = cast(T)x; }
287 
288 	/// Constructor for parameter lists
289 	public this(U)(U x, U y) { data[] = [x, y]; }
290 
291 	/// Constructor for vectors of same type
292 	public this(Vector2T!T vec) { data[] = vec.data; }
293 
294 	/// Constructor for explicit lists
295 	public this(T[] list) {
296 		assert(list.length == 2, "List length mismatch");
297 		data[] = list;
298 	}
299 
300 	public string toString() { return `<%s, %s>`.format(data[0], data[1]); }
301 	public alias ToString = toString;
302 }
303 
304 // --- Vector3T struct
305 alias Vector3T(T) = Vector!(T, 3);
306 alias vec3 = Vector3T!float;
307 alias vec3i = Vector3T!int;
308 alias float3 = Vector3T!float;
309 alias int3 = Vector3T!int;
310 
311 // For compatiblity with simplemath.
312 alias Vector3 = Vector3T!float;
313 alias Vector3i = Vector3T!int;
314 
315 struct Vector(T, int _Dim:3) {
316 	public T[3] data = [0, 0, 0];
317 
318 	mixin GenericVectorMixin!(T, 3);
319 
320 	/// Constructor for scalars
321 	public this(U)(U x) { data[] = cast(T)x; }
322 
323 	/// Constructor for parameter lists
324 	public this(U)(U x, U y, U z) { data[] = [x, y, z]; }
325 
326 	/// Constructor for vectors of same type
327 	public this(Vector3T!T vec) { data[] = vec.data; }
328 
329 	/// Constructor for explicit lists
330 	public this(T[] list) {
331 		assert(list.length == 3, "List length mismatch");
332 		data[] = list;
333 	}
334 
335 	public string toString() { return `<%s, %s, %s>`.format(data[0], data[1], data[2]); }
336 	public alias ToString = toString;
337 
338 	public Vector3T!T Cross(Vector3T!T a, Vector3T!T b) pure const nothrow {
339 		return Vector3T!T(
340 			a.y*b.z - a.z*b.y,
341 			a.z*b.x - a.x * b.z,
342 			a.x*b.y - a.y * b.x);
343 	}
344 }
345 
346 // --- Vector4T struct
347 alias Vector4T(T) = Vector!(T, 4);
348 alias vec4 = Vector4T!float;
349 alias vec4i = Vector4T!int;
350 alias float4 = Vector4T!float;
351 alias int4 = Vector4T!int;
352 
353 // For compatiblity with simplemath.
354 alias Vector4 = Vector4T!float;
355 alias Vector4i = Vector4T!int;
356 
357 struct Vector(T, int _Dim:4) {
358 	public T[4] data = [0, 0, 0, 0];
359 
360 	mixin GenericVectorMixin!(T, 4);
361 
362 	/// Constructor for scalars
363 	public this(U)(U x) { data[] = cast(T)x; }
364 
365 	/// Constructor for parameter lists
366 	public this(U)(U x, U y, U z, U w) { data[] = [x, y, z, w]; }
367 
368 	/// Constructor for vectors of same type
369 	public this(Vector4T!T vec) { data[] = vec.data; }
370 
371 	/// Constructor for explicit lists
372 	public this(T[] list) {
373 		assert(list.length == 4, "List length mismatch");
374 		data[] = list;
375 	}
376 
377 	string toString() { return `<%s, %s, %s, %s>`.format(data[0], data[1], data[2], data[3]); }
378 	alias ToString = toString;
379 }