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 }