1 module moggle.math.matrix;
2 
3 import std.conv;
4 import std.traits;
5 import std.math;
6 import std.algorithm;
7 
8 /// An N by M Matrix of T.
9 struct Matrix(T, size_t N, size_t M = N) {
10 
11 	static assert(N > 0 && M > 0, "Zero sized matrices are not supported.");
12 	static assert(is(Unqual!T == T), "Don't use const or immutable types as elements.");
13 
14 	private T[N*M] values;
15 
16 	alias N height;
17 	alias M width;
18 
19 	pure nothrow {
20 
21 		this()(in const(T)[N*M] v ...) { this = v; }
22 		this(T2)(in Matrix!(T2, N, M) m) { this = m; }
23 
24 		ref Matrix opAssign()(in const(T)[N*M] v) {
25 			values = v;
26 			return this;
27 		}
28 
29 		ref Matrix opAssign(T2)(in Matrix!(T2, N, M) m) {
30 			foreach (i; 0 .. N*M) values[i] = m[i];
31 			return this;
32 		}
33 
34 		@property auto ptr() inout { return values.ptr; }
35 
36 		static if (M == 1) {
37 
38 			this(size_t N2)(in const(T)[N2] v ...) if (N2 < N) { this = v; }
39 			this(T2, size_t N2)(in Matrix!(T2, N2, 1) v) if (N2 < N) { this = v; }
40 
41 			ref Matrix opAssign(size_t N2)(in const(T)[N2] v ...) if (N2 < N) {
42 				values[0..v.length] = v;
43 				values[v.length..$] = 0;
44 				return this;
45 			}
46 
47 			ref Matrix opAssign(T2, size_t N2)(in Matrix!(T2, N2, 1) v) if (N2 < N) {
48 				foreach (i; 0 .. N2) values[i] = v[i];
49 				values[N2 .. $] = 0;
50 				return this;
51 			}
52 
53 		}
54 
55 		@property static Matrix zero() {
56 			Matrix m;
57 			m[] = cast(T)0;
58 			return m;
59 		}
60 
61 		static if (N == M)
62 		@property static Matrix identity() {
63 			auto m = zero;
64 			foreach (i; 0 .. N) m[i, i] = 1;
65 			return m;
66 		}
67 
68 		ref auto opIndex(size_t i) inout { return values[i]; }
69 		ref auto opIndex(size_t n, size_t m) inout { return values[n*M + m]; }
70 
71 		auto opSlice() inout { return values[]; }
72 		auto opSliceAssign(T2)(in T2 v) { values[] = v; }
73 		auto opSliceOpAssign(string op, T2)(in T2 v) { mixin("values[] " ~ op ~ "= v;"); }
74 
75 		size_t opDollar() const { return N*M; }
76 
77 		auto transposed() const {
78 			Matrix!(T, M, N) result;
79 			foreach (i; 0 .. N) foreach (j; 0 .. M) result[j, i] = this[i, j];
80 			return result;
81 		}
82 
83 		static if (N > 1 && M > 1) {
84 
85 			Matrix!(T, N, 1) column(size_t k) const {
86 				typeof(return) m;
87 				foreach (i; 0 .. N) m[i] = this[i, k];
88 				return m;
89 			}
90 
91 			Matrix!(T, 1, M) row(size_t k) const {
92 				T[M] r = values[k*M .. (k+1)*M];
93 				return typeof(return)(r);
94 			}
95 
96 			static if (M > 1)
97 			Matrix!(T, N, M-1) without_column(size_t k) const {
98 				typeof(return) m;
99 				foreach (i; 0 .. N) foreach (j; 0 .. M-1) {
100 					m[i, j] = this[i, j + (j >= k)];
101 				}
102 				return m;
103 			}
104 
105 			static if (N > 1)
106 			Matrix!(T, N-1, M) without_row(size_t k) const {
107 				typeof(return) m;
108 				foreach (i; 0 .. N-1) foreach (j; 0 .. M) {
109 					m[i, j] = this[i + (i >= k), j];
110 				}
111 				return m;
112 			}
113 
114 			static if (N > 1 && M > 1)
115 			Matrix!(T, N-1, M-1) without_row_column(size_t r, size_t c) const {
116 				typeof(return) m;
117 				foreach (i; 0 .. N-1) foreach (j; 0 .. M-1) {
118 					m[i, j] = this[i + (i >= r), j + (j >= c)];
119 				}
120 				return m;
121 			}
122 
123 		}
124 
125 		static if (M == 1) {
126 
127 			auto length() const {
128 				CommonType!(T, float) d = this * this;
129 				return sqrt(d);
130 			}
131 
132 			auto normalized() const {
133 				return this / length;
134 			}
135 
136 			static if (!isIntegral!T)
137 			void normalize() {
138 				this /= length;
139 			}
140 
141 		}
142 
143 		static if (N == M) {
144 
145 			void transpose() {
146 				this = transposed;
147 			}
148 
149 			T determinant() const {
150 				static if (N == 1) {
151 					return this[0];
152 				} else static if (N == 2) {
153 					return cast(T)(this[0, 0] * this[1, 1] - this[0, 1] * this[1, 0]);
154 				} else static if (N == 3) {
155 					return cast(T)(
156 						this[0, 0] * (this[1, 1] * this[2, 2] - this[2, 1] * this[1, 2]) +
157 						this[1, 0] * (this[2, 1] * this[0, 2] - this[0, 1] * this[2, 2]) +
158 						this[2, 0] * (this[0, 1] * this[1, 2] - this[1, 1] * this[0, 2])
159 					);
160 				} else { // Generic case. (Would work for any N.)
161 					T result = 0;
162 					foreach (i; 0 .. N) result += this[0, i] * cofactor(0, i);
163 					return result;
164 				}
165 			}
166 
167 			T cofactor(size_t n, size_t m) const {
168 				static if (N == 1) {
169 					return cast(T)1;
170 				} else {
171 					auto d = without_row_column(n, m).determinant;
172 					return (n + m) % 2 ? -d : d;
173 				}
174 			}
175 
176 			Matrix cofactor_matrix() const {
177 				Matrix result;
178 				foreach (i; 0 .. N) foreach (j; 0 .. M) result[i, j] = cofactor(i, j);
179 				return result;
180 			}
181 
182 			Matrix adjugate() const {
183 				return cofactor_matrix.transposed;
184 			}
185 
186 			Matrix inverse() const {
187 				auto a = adjugate;
188 				return a /= determinant;
189 			}
190 
191 			void invert() {
192 				this = inverse;
193 			}
194 
195 		}
196 
197 		// M+=M, M-=M
198 		ref Matrix opOpAssign(string op, T2)(in Matrix!(T2, N, M) m)
199 		if (op == "+" || op == "-") {
200 			foreach (i; 0 .. N*M) mixin("this[i] " ~ op ~ "= m[i];");
201 			return this;
202 		}
203 
204 		// V+=V, V-=V (different sizes)
205 		static if (M == 1)
206 		ref Matrix opOpAssign(string op, T2, size_t N2)(in Matrix!(T2, N2, 1) m)
207 		if ((op == "+" || op == "-") && N2 < N) {
208 			foreach (i; 0 .. N2) mixin("this[i] " ~ op ~ "= m[i];");
209 			return this;
210 		}
211 
212 		// M+M, M-M
213 		auto opBinary(string op, T2)(in Matrix!(T2, N, M) m) const
214 		if (op == "+" || op == "-") {
215 			Matrix!(Unqual!(typeof(this[0] + m[0])), N, M) result;
216 			foreach (i; 0 .. N*M) mixin("result[i] = this[i] " ~ op ~ " m[i];");
217 			return result;
218 		}
219 
220 		// V+V, V-V (different sizes)
221 		static if (M == 1)
222 		auto opBinary(string op, T2, size_t N2)(in Matrix!(T2, N2, 1) m) const
223 		if ((op == "+" || op == "-") && N != N2) {
224 			Matrix!(Unqual!(typeof(this[0] + m[0])), max(N, N2), 1) result;
225 			foreach (i; 0 .. result.height) mixin("result[i] = (i < N ? this[i] : 0) " ~ op ~ " (i < N2 ? m[i] : 0);");
226 			return result;
227 		}
228 
229 		// +M, -M
230 		Matrix opUnary(string op)() const
231 		if (op == "+" || op == "-") {
232 			Matrix m;
233 			foreach (i; 0 .. N*M) mixin("m[i] = " ~ op ~ "this[i];");
234 			return m;
235 		}
236 
237 		// M*S, M/S
238 		auto opBinary(string op, T2)(in T2 v) const
239 		if ((op == "*" || op == "/") && !isInstanceOf!(moggle.math.matrix.Matrix, T2)) {
240 			Matrix!(Unqual!(typeof(this[0] * v)), N, M) result;
241 			foreach (i; 0 .. N*M) mixin("result[i] = this[i] " ~ op ~ " v;");
242 			return result;
243 		}
244 
245 		// S*M
246 		auto opBinaryRight(string op, T2)(in T2 v) const
247 		if (op == "*" && !isInstanceOf!(moggle.math.matrix.Matrix, T2)) {
248 			Matrix!(Unqual!(typeof(v * this[0])), N, M) result;
249 			foreach (i; 0 .. N*M) mixin("result[i] = v " ~ op ~ " this[i];");
250 			return result;
251 		}
252 
253 		// M*=S, M/=S
254 		ref Matrix opOpAssign(string op, T2)(in T2 v)
255 		if ((op == "*" || op == "/") && !isInstanceOf!(moggle.math.matrix.Matrix, T2)) {
256 			foreach (i; 0 .. N*M) mixin("this[i] " ~ op ~ "= v;");
257 			return this;
258 		}
259 
260 		// V*V
261 		static if (M == 1)
262 		auto opBinary(string op, T2, size_t N2)(in Matrix!(T2, N2, 1) m) const
263 		if (op == "*") {
264 			Unqual!(typeof(this[0] * m[0])) result = 0;
265 			foreach (i; 0 .. min(N, N2)) result += this[i] * m[i];
266 			return result;
267 		}
268 
269 		// M*M
270 		auto opBinary(string op, T2, size_t M2)(in Matrix!(T2, M, M2) m) const
271 		if (op == "*" && (N != 1 || M != 1 || M2 != 1)) {
272 			Matrix!(typeof(row(0).transposed * m.column(0)), N, M2) result;
273 			foreach (i; 0 .. result.height)
274 			foreach (j; 0 .. result.width) {
275 				result[i, j] = row(i).transposed * m.column(j);
276 			}
277 			return result;
278 		}
279 
280 		// M*=M
281 		static if (N == M)
282 		ref Matrix opOpAssign(string op, T2)(in Matrix!(T2, N, N) m)
283 		if (op == "*") {
284 			this = this * m;
285 			return this;
286 		}
287 
288 	}
289 
290 	static if (M == 1) {
291 		auto opSlice(size_t i, size_t j) inout { return values[i..j]; }
292 		auto opSliceAssign(T2)(T2 v, size_t i, size_t j) { values[i..j] = v; }
293 		auto opSliceOpAssign(string op, T2)(T2 v, size_t i, size_t j) { mixin("values[i..j] " ~ op ~ "= v;"); }
294 	}
295 
296 	string toString() const {
297 		string s = "[";
298 		foreach (i; 0 .. N) {
299 			if (i) s ~= ";";
300 			foreach (j; 0 .. M) {
301 				if (i || j) s ~= " ";
302 				s ~= text(this[i, j]);
303 			}
304 		}
305 		return s ~= "]";
306 	}
307 
308 }
309 
310 ///
311 unittest {
312 	// Matrix!(T=ElementType, N=Height, M=Width) can be initialized
313 	// with an array of N*M elements, or by passing N*M values to the constructor.
314 	auto m = Matrix!(float, 2, 3)(
315 		7, 3, 2,
316 		9, 1, 5,
317 	);
318 
319 	// Elements can be accessed with [i] (i=0..N*M) and [row, column] (row=0..N, column=0..M)
320 	assert(m[4] == 1);
321 	assert(m[1, 1] == 1);
322 	assert(m[0, 2] == 2);
323 
324 	// A matrix is default-initialied to just N*M default-initialized Ts.
325 	{
326 		Matrix!(float, 3, 3) a;
327 		Matrix!(int, 2, 3) b;
328 		assert(isNaN(a[2]));
329 		assert(b[2] == 0);
330 	}
331 
332 	// .zero gives a zero-filled matrix.
333 	{
334 		auto a = Matrix!(float, 3, 3).zero;
335 		assert(a[3] == 0);
336 	}
337 
338 	// For square matrices, .identity gives the identity matrix.
339 	{
340 		auto a = Matrix!(float, 2, 2).identity;
341 		// The identity is also accessible as a.identity.
342 		assert(a[0, 0] == 1);
343 		assert(a[0, 1] == 0);
344 	}
345 
346 	// .width and .height are aliases for N and M.
347 	assert(Matrix!(int, 13, 37).height == 13);
348 	assert(m.width == 3);
349 
350 	// [] gives a T[] slice of all N*M elements.
351 	assert(m[] == [7, 3, 2, 9, 1, 5]);
352 	m[] = 0;
353 	assert(m == m.zero);
354 
355 	// .transposed gives the transposed M*N matrix.
356 	assert(m.transposed.width == m.height);
357 	assert(m.transposed.height == m.width);
358 	m[0, 2] = 4;
359 	assert(m.transposed[2, 0] == 4);
360 
361 	// For square matrices, .transpose() transposes the matrix in place.
362 	{
363 		auto a = Matrix!(float, 2, 2)(1, 2, 3, 4);
364 		a.transpose();
365 		assert(a[] == [1, 3, 2, 4]);
366 	}
367 
368 	// Vectors are just matrices with a width of 1. Vector!(T, N) is just an alias.
369 	assert(is(Vector!(float, 3) == Matrix!(float, 3, 1)));
370 
371 	// For vectors, [i..j] can be used to get/set/modify a part of the vector.
372 	auto a = Vector!(float, 4).zero;
373 	a[0..2] = 1;
374 	a[1..3] += 2;
375 	assert(a[0..3] == [1, 3, 2]);
376 
377 	// Vectors of different sizes can be used together, they're padded with zeros.
378 	a += Vector!(float, 2)(1, 2);
379 	assert(a[] == [2, 5, 2, 0]);
380 	assert((a - Vector!(float, 5)(0, 0, 0, 0, 1))[] == [2, 5, 2, 0, -1]);
381 
382 	// .column(i) and .row(i) give you a specific row or column as N*1 or 1*M matrix, respectively.
383 	assert(m.row(0)[] == [0, 0, 4]);
384 	assert(m.column(2)[] == [4, 0]);
385 	assert(m.row(0).height == 1);
386 	assert(m.column(0).width == 1);
387 
388 	// .without_row(i), .without_column(i), and .without_row_column(r, c) do as they say.
389 	assert(m.without_column(1) == Matrix!(float, 2, 2)(0, 4, 0, 0));
390 	assert(m.without_row(1) == Matrix!(float, 1, 3)(0, 0, 4));
391 	assert(m.without_row_column(1, 1) == Matrix!(float, 1, 2)(0, 4));
392 
393 	// For (column) vectors, .length gives the Euclidian length,
394 	// .normalized and .normalize do what you want.
395 	Vector!(float, 2) v = [3, 4];
396 	assert(v.length == 5);
397 	assert(v.normalized == Vector!(float, 2)(0.6, 0.8));
398 	v.normalize();
399 	assert(v.length == 1);
400 
401 	// For square matrices, there is .determinant, .cofactor(row, column),
402 	// .cofactor_matrix, .adjugate, .inverse and .invert.
403 	Matrix!(float, 3, 3) x = [
404 		1, 2, 3,
405 		0, 6, 1,
406 		0, 5, 0,
407 	];
408 	assert(x.determinant == -5);
409 	assert(x.cofactor(1, 0) == -x.without_row_column(1, 0).determinant);
410 	assert(x.cofactor_matrix[1, 0] == x.cofactor(1, 0));
411 	assert(x.adjugate == x.cofactor_matrix.transposed);
412 	assert(x.inverse == x.adjugate / x.determinant);
413 	x.invert();
414 	assert(x.column(1)[] == [-3, 0, 1]);
415 
416 	// You can add and subtract same-sized matrices with +=, -=, + and -,
417 	// and scale them with *=, /=, *, and /.
418 	auto y = x.without_row(2) + m;
419 	y -= -m * 2;
420 	y /= 0.5;
421 	assert(y[1] == -6);
422 
423 	// Matrix multiplication is done with * and *=.
424 	assert(x * x.inverse == x.identity);
425 	x *= x.inverse;
426 	assert(x == x.identity);
427 
428 	// For vectors, * gives you the dot product.
429 	assert(v * v == 1);
430 
431 	// There are aliases available for the most common matrices and vectors.
432 	// (1..4 in size, for types int, float, double and real.)
433 	assert(is(Matrix2x3f == Matrix!(float, 2, 3)));
434 	assert(is(Matrix3d == Matrix!(double, 3, 3)));
435 	assert(is(Vector2i == Vector!(int, 2)));
436 	assert(is(HVector4r == HVector!(real, 4)));
437 }
438 
439 /// Alias for a Matrix with a width of 1.
440 template Vector(T, size_t N) {
441 	alias Matrix!(T, N, 1) Vector;
442 }
443 
444 /// A 'homogeneous vector': A vector with the last element set to 1 by default.
445 struct HVector(T, size_t N) {
446 
447 	Vector!(T, N) vector;
448 
449 	alias vector this;
450 
451 pure nothrow:
452 
453 	this(size_t N2)(const(T)[N2] v ...) if (N2 <= N) { this = v; }
454 	this(T2, size_t N2)(in Matrix!(T2, N2, 1) v) if (N2 <= N) { this = v; }
455 
456 	ref HVector opAssign(size_t N2)(const(T)[N2] v ...) if (N2 <= N) {
457 		vector = v;
458 		if (v.length < N) vector[$-1] = 1;
459 		return this;
460 	}
461 
462 	ref HVector opAssign(T2, size_t N2)(in Matrix!(T2, N2, 1) v) if (N2 <= N) {
463 		vector = v;
464 		if (v.length < N) vector[$-1] = 1;
465 		return this;
466 	}
467 
468 	// Length (and therefore, normalizing) doesn't make sense for these kind of vectors.
469 	@disable void length();
470 	@disable void normalized();
471 	@disable void normalize();
472 
473 }
474 
475 ///
476 unittest {
477 	// A HVector is a Vector with the last element set to 1 by default.
478 	// This is handy for 4D representations of positions in 3D graphics
479 	// and RGBA color vectors.
480 	auto h = HVector4d(1, 0.3, 0.4);
481 	assert(h[] == [1, 0.3, 0.4, 1]);
482 	h = [0, 2, 3];
483 	assert(h[] == [0, 2, 3, 1]);
484 
485 	// They can be converted back and forth to normal vectors.
486 	Vector4d v = h;
487 	HVector4d h1 = w;
488 	HVector4d h2 = Vector3d(1, 2, 3);
489 	h = v;
490 	v = h;
491 	h += v;
492 }
493 
494 // Aliases for MatrixNxMt, MatrixNt, and VectorNt. (With t = i,f,d,r.)
495 mixin((){
496 	string code;
497 	auto types = ["i":"int", "f":"float", "d":"double", "r":"real"];
498 	foreach (k, t; types) {
499 		foreach (n; 1 .. 5) foreach (m; 1 .. 5) {
500 			if (n == 1 && m == 1) continue;
501 			code ~= text("alias Matrix!(", t, ",", n, ",", m, ") Matrix", n, "x", m, k, ";\n");
502 		}
503 		foreach (m; ["Matrix", "Vector", "HVector"]) foreach (n; 2 .. 5) {
504 			code ~= text("alias ", m, "!(", t, ",", n, ") ", m, n, k, ";\n");
505 		}
506 	}
507 	return code;
508 }());
509