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