1 module dath.matrix; 2 3 version(unittest) import fluent.asserts; 4 5 import std.traits; 6 import dath.core; 7 import dath.vector; 8 9 public alias Mat2f = Mat!(float, 2); 10 public alias Mat3f = Mat!(float, 3); 11 public alias Mat4f = Mat!(float, 4); 12 13 public alias Mat2 = Mat2f; 14 public alias Mat3 = Mat3f; 15 public alias Mat4 = Mat4f; 16 17 public alias Mat2d = Mat!(double, 2); 18 public alias Mat3d = Mat!(double, 3); 19 public alias Mat4d = Mat!(double, 4); 20 21 public alias Mat2i = Mat!(int, 2); 22 public alias Mat3i = Mat!(int, 3); 23 public alias Mat4i = Mat!(int, 4); 24 25 public alias Mat2u = Mat!(uint, 2); 26 public alias Mat3u = Mat!(uint, 3); 27 public alias Mat4u = Mat!(uint, 4); 28 29 /++ 30 + A square NxN matrix. Supports any numeric type. 31 +/ 32 public struct Mat(T, ulong n) if (n >= 2 && isNumeric!T) 33 { 34 union 35 { 36 T[n*n] v; // all values 37 T[n][n] c; // all components 38 } 39 40 public this(T...)(T args) @nogc pure nothrow 41 { 42 static foreach (arg; args) 43 { 44 static assert(isNumeric!(typeof(arg)), "All values must be numeric."); 45 } 46 47 static assert(args.length > 0, "No args provided."); 48 49 static assert(args.length == 1 || args.length == n*n, "Number of args must be either 1 or N*N."); 50 51 static if (args.length == 1) 52 { 53 v[] = args[0]; 54 } 55 else 56 { 57 v = [args]; 58 } 59 } 60 61 /++ 62 + Get the value at [i, j] 63 +/ 64 public T opIndex(int i, int j) @nogc pure const nothrow 65 { 66 return c[i][j]; 67 } 68 69 /++ 70 + Set the value at [i, j] 71 +/ 72 public T opIndexAssign(T value, int i, int j) @nogc pure nothrow 73 { 74 return c[i][j] = value; 75 } 76 77 /++ 78 + Returns this matrix * scalar 79 +/ 80 public auto opBinary(string s) (const float scalar) @nogc pure const nothrow if (s == "*") 81 { 82 Mat!(T, n) res; 83 res.v = v[] * scalar; 84 return res; 85 } 86 87 /++ 88 + Returns this matrix * scalar 89 +/ 90 public void opOpAssign(string s) (const float scalar) @nogc pure nothrow if (s == "*") 91 { 92 v[] *= scalar; 93 } 94 95 /++ 96 + Returns this matrix * vector 97 +/ 98 public auto opBinary(string s) (const Vec!(T, n) vector) @nogc pure const nothrow if (s == "*") 99 { 100 Vec!(T, n) res; 101 102 for (int i = 0; i < n; i++) 103 { 104 float sum = 0f; 105 for (int j = 0; j < n; j++) 106 { 107 sum += this[i, j] * vector[j]; 108 } 109 res[i] = sum; 110 } 111 112 return res; 113 } 114 115 /++ 116 + Returns this matrix * matrix 117 +/ 118 public auto opBinary(string s) (const Mat!(T, n) other) @nogc pure const nothrow if (s == "*") 119 { 120 Mat!(T, n) res; 121 122 for (int i = 0; i < n; i++) 123 { 124 for (int j = 0; j < n; j++) 125 { 126 float sum = 0f; 127 for (int k = 0; k < n; k++) 128 { 129 sum += this[i, k] * other[k, j]; 130 } 131 res[i, j] = sum; 132 } 133 } 134 135 return res; 136 } 137 138 /++ 139 + Returns this matrix * matrix 140 +/ 141 public void opOpAssign (string s) (const Mat!(T, n) other) @nogc pure nothrow if (s == "*") 142 { 143 auto res = this * other; 144 this.v = res.v; 145 } 146 147 /++ 148 + Returns sum or sub of two matrices 149 +/ 150 public auto opBinary(string s)(const Mat!(T, n) other) @nogc pure const nothrow if (s == "+" || s == "-") 151 { 152 Mat!(T, n) res; 153 154 for (int i = 0; i < n; i++) 155 { 156 for (int j = 0; j < n; j++) 157 { 158 mixin("res[i, j] = this[i, j] " ~ s ~ " other[i, j];"); 159 } 160 } 161 162 return res; 163 } 164 165 /++ 166 + Internal data as a pointer, use for sending data to shaders. 167 +/ 168 public auto ptr() @nogc pure const nothrow 169 { 170 return v.ptr; 171 } 172 } 173 174 /++ 175 + creates an identity matrix 176 +/ 177 public Mat!(T, n) identity(T, ulong n)() @nogc pure nothrow 178 { 179 Mat!(T, n) res; 180 for (int i = 0; i < n; i++) 181 { 182 for (int j = 0; j < n; j++) 183 { 184 res[i, j] = i == j ? 1 : 0; 185 } 186 } 187 return res; 188 } 189 190 /++ 191 + Creates a scaling matrix 192 +/ 193 public Mat!(T, n) scaling(T, ulong n)(Vec!(T, n-1) v) @nogc pure nothrow 194 { 195 auto res = identity!(T, n); 196 for (int i = 0; i + 1 < n; i++) 197 { 198 res[i, i] = v[i]; 199 } 200 return res; 201 } 202 203 /++ 204 + Creates a rotation matrix, angle in radians 205 +/ 206 public auto rotation(T)(float angle, Vec!(T, 3) axis) @nogc pure nothrow 207 { 208 import std.math : sin, cos; 209 210 auto res = identity!(T, 4)(); 211 float c = cos(angle); 212 float c1 = 1 - c; 213 float s = sin(angle); 214 215 auto a = axis.normalized(); 216 217 res[0, 0] = a.x * a.x * c1 + c; 218 res[0, 1] = a.x * a.y * c1 - a.z * s; 219 res[0, 2] = a.x * a.z * c1 + a.y * s; 220 res[1, 0] = a.y * a.x * c1 + a.z * s; 221 res[1, 1] = a.y * a.y * c1 + c; 222 res[1, 2] = a.y * a.z * c1 - a.x * s; 223 res[2, 0] = a.z * a.x * c1 - a.y * s; 224 res[2, 1] = a.z * a.y * c1 + a.x * s; 225 res[2, 2] = a.z * a.z * c1 + c; 226 227 return res; 228 } 229 230 /++ 231 + Creates a translation matrix 232 +/ 233 public auto translation(T, ulong n)(Vec!(T, n-1) v) @nogc pure nothrow 234 { 235 auto res = identity!(T, n)(); 236 for (int i = 0; i + 1 < n; i++) 237 { 238 res[i, n-1] = res[i, n-1] + v[i]; 239 } 240 return res; 241 } 242 243 /++ 244 + Creates a look-at matrix 245 +/ 246 public auto lookAt(Vec3 eye, Vec3 target, Vec3 up) @nogc pure nothrow 247 { 248 Vec3 z = (eye - target).normalized(); 249 Vec3 x = cross(-up, z).normalized(); 250 Vec3 y = cross(z, -x); 251 252 return Mat4(-x.x, -x.y, -x.z, dot(x, eye), 253 y.x, y.y, y.z, -dot(y, eye), 254 z.x, z.y, z.z, -dot(z, eye), 255 0, 0, 0, 1); 256 } 257 258 /++ 259 + Creates an orthographic projection matrix 260 +/ 261 public auto orthographic(float left, float right, float bottom, float top, float near, float far) @nogc pure nothrow 262 { 263 float dx = right - left; 264 float dy = top - bottom; 265 float dz = far - near; 266 267 float tx = -(right + left) / dx; 268 float ty = -(top + bottom) / dy; 269 float tz = -(far + near) / dz; 270 271 return Mat4(2/dx, 0f, 0f, tx, 272 0f, 2/dy, 0f, ty, 273 0f, 0f, -2/dz, tz, 274 0f, 0f, 0f, 1f); 275 } 276 277 /++ 278 + Creates a perspective projection matrix 279 +/ 280 public auto perspective(float fov_in_radians, float aspect, float near, float far) @nogc pure nothrow 281 { 282 import core.stdc.math : tan; 283 284 float f = 1 / tan(fov_in_radians / 2); 285 float d = 1 / (near - far); 286 287 return Mat4(f / aspect, 0f, 0f, 0f, 288 0f, f, 0f, 0f, 289 0f, 0f, (far + near) * d, 2 * d * far * near, 290 0f, 0f, -1f, 0f); 291 } 292 293 /++ 294 + Returns the inverse of the provided matrix, if no inverse can be found it returns a `Mat4(inf)` 295 +/ 296 public Mat4 inverse(const Mat4 a) @nogc pure nothrow 297 { 298 Mat4 t = a; 299 300 float det2_01_01 = t[0, 0] * t[1, 1] - t[0, 1] * t[1, 0]; 301 float det2_01_02 = t[0, 0] * t[1, 2] - t[0, 2] * t[1, 0]; 302 float det2_01_03 = t[0, 0] * t[1, 3] - t[0, 3] * t[1, 0]; 303 float det2_01_12 = t[0, 1] * t[1, 2] - t[0, 2] * t[1, 1]; 304 float det2_01_13 = t[0, 1] * t[1, 3] - t[0, 3] * t[1, 1]; 305 float det2_01_23 = t[0, 2] * t[1, 3] - t[0, 3] * t[1, 2]; 306 307 float det3_201_012 = t[2, 0] * det2_01_12 - t[2, 1] * det2_01_02 + t[2, 2] * det2_01_01; 308 float det3_201_013 = t[2, 0] * det2_01_13 - t[2, 1] * det2_01_03 + t[2, 3] * det2_01_01; 309 float det3_201_023 = t[2, 0] * det2_01_23 - t[2, 2] * det2_01_03 + t[2, 3] * det2_01_02; 310 float det3_201_123 = t[2, 1] * det2_01_23 - t[2, 2] * det2_01_13 + t[2, 3] * det2_01_12; 311 312 float det = - det3_201_123 * t[3, 0] + det3_201_023 * t[3, 1] - det3_201_013 * t[3, 2] + det3_201_012 * t[3, 3]; 313 float invDet = 1 / det; 314 315 float det2_03_01 = t[0, 0] * t[3, 1] - t[0, 1] * t[3, 0]; 316 float det2_03_02 = t[0, 0] * t[3, 2] - t[0, 2] * t[3, 0]; 317 float det2_03_03 = t[0, 0] * t[3, 3] - t[0, 3] * t[3, 0]; 318 float det2_03_12 = t[0, 1] * t[3, 2] - t[0, 2] * t[3, 1]; 319 float det2_03_13 = t[0, 1] * t[3, 3] - t[0, 3] * t[3, 1]; 320 float det2_03_23 = t[0, 2] * t[3, 3] - t[0, 3] * t[3, 2]; 321 float det2_13_01 = t[1, 0] * t[3, 1] - t[1, 1] * t[3, 0]; 322 float det2_13_02 = t[1, 0] * t[3, 2] - t[1, 2] * t[3, 0]; 323 float det2_13_03 = t[1, 0] * t[3, 3] - t[1, 3] * t[3, 0]; 324 float det2_13_12 = t[1, 1] * t[3, 2] - t[1, 2] * t[3, 1]; 325 float det2_13_13 = t[1, 1] * t[3, 3] - t[1, 3] * t[3, 1]; 326 float det2_13_23 = t[1, 2] * t[3, 3] - t[1, 3] * t[3, 2]; 327 328 float det3_203_012 = t[2, 0] * det2_03_12 - t[2, 1] * det2_03_02 + t[2, 2] * det2_03_01; 329 float det3_203_013 = t[2, 0] * det2_03_13 - t[2, 1] * det2_03_03 + t[2, 3] * det2_03_01; 330 float det3_203_023 = t[2, 0] * det2_03_23 - t[2, 2] * det2_03_03 + t[2, 3] * det2_03_02; 331 float det3_203_123 = t[2, 1] * det2_03_23 - t[2, 2] * det2_03_13 + t[2, 3] * det2_03_12; 332 333 float det3_213_012 = t[2, 0] * det2_13_12 - t[2, 1] * det2_13_02 + t[2, 2] * det2_13_01; 334 float det3_213_013 = t[2, 0] * det2_13_13 - t[2, 1] * det2_13_03 + t[2, 3] * det2_13_01; 335 float det3_213_023 = t[2, 0] * det2_13_23 - t[2, 2] * det2_13_03 + t[2, 3] * det2_13_02; 336 float det3_213_123 = t[2, 1] * det2_13_23 - t[2, 2] * det2_13_13 + t[2, 3] * det2_13_12; 337 338 float det3_301_012 = t[3, 0] * det2_01_12 - t[3, 1] * det2_01_02 + t[3, 2] * det2_01_01; 339 float det3_301_013 = t[3, 0] * det2_01_13 - t[3, 1] * det2_01_03 + t[3, 3] * det2_01_01; 340 float det3_301_023 = t[3, 0] * det2_01_23 - t[3, 2] * det2_01_03 + t[3, 3] * det2_01_02; 341 float det3_301_123 = t[3, 1] * det2_01_23 - t[3, 2] * det2_01_13 + t[3, 3] * det2_01_12; 342 343 Mat4 res; 344 345 res[0, 0] = - det3_213_123 * invDet; 346 res[1, 0] = + det3_213_023 * invDet; 347 res[2, 0] = - det3_213_013 * invDet; 348 res[3, 0] = + det3_213_012 * invDet; 349 350 res[0, 1] = + det3_203_123 * invDet; 351 res[1, 1] = - det3_203_023 * invDet; 352 res[2, 1] = + det3_203_013 * invDet; 353 res[3, 1] = - det3_203_012 * invDet; 354 355 res[0, 2] = + det3_301_123 * invDet; 356 res[1, 2] = - det3_301_023 * invDet; 357 res[2, 2] = + det3_301_013 * invDet; 358 res[3, 2] = - det3_301_012 * invDet; 359 360 res[0, 3] = - det3_201_123 * invDet; 361 res[1, 3] = + det3_201_023 * invDet; 362 res[2, 3] = - det3_201_013 * invDet; 363 res[3, 3] = + det3_201_012 * invDet; 364 365 return res; 366 } 367 368 @("Creating matrices") 369 unittest 370 { 371 auto t1 = Mat2(2f); 372 const t2 = Mat2(1f, 2f, 3f, 4f); 373 374 t1[0, 0].should.equal(2f); 375 t1[0, 1].should.equal(2f); 376 t1[1, 0].should.equal(2f); 377 t1[1, 1].should.equal(2f); 378 379 t2[0, 0].should.equal(1f); 380 t2[0, 1].should.equal(2f); 381 t2[1, 0].should.equal(3f); 382 t2[1, 1].should.equal(4f); 383 384 t1[0, 0] = 5f; 385 t1[0, 0].should.equal(5f); 386 } 387 388 @("Matrix multiplication by scalar") 389 unittest 390 { 391 const t1 = Mat2(1f, 2f, 3f, 4f); 392 (t1 * 2f).should.equal(Mat2(2, 4, 6, 8)); 393 } 394 395 @("Matrix multiplication by vector") 396 unittest 397 { 398 const m1 = Mat2(1f, 2f, 3f, 4f); 399 const v1 = Vec2(4f, 6f); 400 (m1 * v1).should.equal(Vec2(16f, 36f)); 401 } 402 403 @("Matrix multiplication by matrix") 404 unittest 405 { 406 auto m1 = Mat2(1f, 2f, 3f, 4f); 407 const m2 = Mat2(5f, 6f, 7f, 8f); 408 (m1 * m2).should.equal(Mat2(19, 22, 43, 50)); 409 m1 *= m2; 410 m1.should.equal(Mat2(19, 22, 43, 50)); 411 } 412 413 @("Adding 2 matrices") 414 unittest 415 { 416 const m1 = Mat2(1f, 2f, 3f, 4f); 417 const m2 = Mat2(5f, 6f, 7f, 8f); 418 (m1 + m2).should.equal(Mat2(6, 8, 10, 12)); 419 } 420 421 @("Subtracting 2 matrices") 422 unittest 423 { 424 const m1 = Mat2(5f, 6f, 7f, 8f); 425 const m2 = Mat2(1f, 2f, 3f, 4f); 426 (m1 - m2).should.equal(Mat2(4)); 427 } 428 429 @("Identity matrix") 430 unittest 431 { 432 const m1 = identity!(float, 2)(); 433 m1.should.equal(Mat2(1, 0, 0, 1)); 434 } 435 436 @("Scaling matrix") 437 unittest 438 { 439 const scaling = scaling!(float, 2)(Vec!(float, 1)(3f)); 440 scaling.should.equal(Mat2(3, 0, 0, 1)); 441 442 const m1 = Mat2(1f, 2f, 3f, 4f); 443 (m1 * scaling).should.equal(Mat2(3, 2, 9, 4)); 444 } 445 446 @("Translation matrix") 447 unittest 448 { 449 const trans = translation!(float, 3)(Vec2(4f)); 450 trans.should.equal(Mat3(1, 0, 4, 0, 1, 4, 0, 0, 1)); 451 452 const m1 = Mat3(3f); 453 (m1 * trans).should.equal(Mat3(3, 3, 27, 3, 3, 27, 3, 3, 27)); 454 } 455 456 @("Matrix inverse") 457 unittest 458 { 459 const m1 = Mat4(5f, 6f, 6f, 8f, 2f, 2f, 2f, 8f, 6f, 6f, 2f, 8f, 2f, 3f, 6f, 7f); 460 (inverse(m1) * m1).should.equal(identity!(float, 4)); 461 }