1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  * 
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  * 
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  * 
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  * 
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  * 
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Math.Matrix4x4;
25 
26 private:
27 
28 static import std.math;
29 
30 import Dgame.Math.Vector3;
31 import Dgame.Math.Vector2;
32 import Dgame.Math.Rect;
33 
34 @nogc
35 bool feq(float a, float b) pure nothrow {
36     return std.math.fabs(a - b) > float.epsilon;
37 }
38 
39 @nogc
40 ref Matrix4x4 merge(ref Matrix4x4 lhs, ref const Matrix4x4 rhs) pure nothrow {
41     lhs = Matrix4x4(
42         lhs[0] * rhs[0] + lhs[4] * rhs[1] + lhs[12] * rhs[3],
43         lhs[0] * rhs[4] + lhs[4] * rhs[5] + lhs[12] * rhs[7],
44         lhs[0] * rhs[12] + lhs[4] * rhs[13] + lhs[12] * rhs[15],
45         lhs[1] * rhs[0] + lhs[5] * rhs[1] + lhs[13] * rhs[3],
46         lhs[1] * rhs[4] + lhs[5] * rhs[5] + lhs[13] * rhs[7],
47         lhs[1] * rhs[12] + lhs[5] * rhs[13] + lhs[13] * rhs[15],
48         lhs[3] * rhs[0] + lhs[7] * rhs[1] + lhs[15] * rhs[3],
49         lhs[3] * rhs[4] + lhs[7] * rhs[5] + lhs[15] * rhs[7],
50         lhs[3] * rhs[12] + lhs[7] * rhs[13] + lhs[15] * rhs[15]);
51 
52     return lhs;
53 }
54 
55 public:
56 
57 /**
58  * A Matrix is a structure which may describe different transformation.
59  * Note: Matrix4x4.init is the identity Matrix.
60  *
61  * Author: Randy Schuett (rswhite4@googlemail.com)
62  */
63 struct Matrix4x4 {
64 private:
65     float[16] _values = [
66         1, 0, 0, 0,
67         0, 1, 0, 0,
68         0, 0, 1, 0,
69         0, 0, 0, 1
70     ];
71 
72 public:
73     /**
74      * CTor
75      */
76     @nogc
77     this(float a, float b, float c,
78          float d, float e, float f,
79          float g, float h, float i) pure nothrow
80     {
81         _values[0] = a; _values[4] = b; _values[8] = 0; _values[12] = c;
82         _values[1] = d; _values[5] = e; _values[9] = 0; _values[13] = f;
83         _values[2] = 0; _values[6] = 0; _values[10] = 1; _values[14] = 0;
84         _values[3] = g; _values[7] = h; _values[11] = 0; _values[15] = i;
85     }
86 
87     /**
88      * Returns the inverse Matrix of the current.
89      * If the current matrix has a determinant of approximately zero, the identity Matrix (.init) is returned.
90      */
91     @nogc
92     Matrix4x4 getInverse() const pure nothrow {
93         immutable float my_det = this.det();
94 
95         if (!feq(my_det, 0)) {
96             return Matrix4x4((_values[15] * _values[5] - _values[7] * _values[13]) / my_det,
97                           -(_values[15] * _values[4] - _values[7] * _values[12]) / my_det,
98                           (_values[13] * _values[4] - _values[5] * _values[12]) / my_det,
99                           -(_values[15] * _values[1] - _values[3] * _values[13]) / my_det,
100                           (_values[15] * _values[0] - _values[3] * _values[12]) / my_det,
101                           -(_values[13] * _values[0] - _values[1] * _values[12]) / my_det,
102                           (_values[7] * _values[1] - _values[3] * _values[5]) / my_det,
103                           -(_values[7] * _values[0] - _values[3] * _values[4]) / my_det,
104                           (_values[5] * _values[0] - _values[1] * _values[4]) / my_det);
105         }
106 
107         return Matrix4x4.init;
108     }
109 
110     /**
111      * Reset the current Matrix to the identity Matrix
112      */
113     @nogc
114     ref Matrix4x4 loadIdentity() pure nothrow {
115         _values[0] = 1.0f; _values[4] = 0.0f; _values[8] = 0.0f; _values[12] = 0.0f;
116         _values[1] = 0.0f; _values[5] = 1.0f; _values[9] = 0.0f; _values[13] = 0.0f;
117         _values[2] = 0.0f; _values[6] = 0.0f; _values[10] = 1.0f; _values[14] = 0.0f;
118         _values[3] = 0.0f; _values[7] = 0.0f; _values[11] = 0.0f; _values[15] = 1.0f;
119 
120         return this;
121     }
122 
123     /**
124      * Calculate the determinant
125      */
126     @nogc
127     float det() const pure nothrow {
128         return _values[0] * (_values[15] * _values[5] - _values[7] * _values[13]) -
129                _values[1] * (_values[15] * _values[4] - _values[7] * _values[12]) +
130                _values[3] * (_values[13] * _values[4] - _values[5] * _values[12]);
131     }
132 
133     /**
134      * Translate the Matrix
135      */
136     @nogc
137     ref Matrix4x4 translate(const Vector2f vec) pure nothrow {
138         const Matrix4x4 translation = Matrix4x4(1, 0, vec.x,
139                                             0, 1, vec.y,
140                                             0, 0, 1);
141         return merge(this, translation);
142     }
143 
144     /**
145      * Rotate the Matrix about angle (in degree!)
146      */
147     @nogc
148     ref Matrix4x4 rotate(float angle) pure nothrow {
149         immutable float rad = angle * std.math.PI / 180f;
150         immutable float cos = std.math.cos(rad);
151         immutable float sin = std.math.sin(rad);
152 
153         const Matrix4x4 rotation = Matrix4x4(cos, -sin, 0,
154                                         sin, cos, 0,
155                                         0, 0, 1);
156         return merge(this, rotation);
157     }
158 
159     /**
160      * Rotate the Matrix about angle (in degree!) about the given center position
161      */
162     @nogc
163     ref Matrix4x4 rotate(float angle, const Vector2f center) pure nothrow {
164         immutable float rad = angle * std.math.PI / 180f;
165         immutable float cos = std.math.cos(rad);
166         immutable float sin = std.math.sin(rad);
167 
168         const Matrix4x4 rotation = Matrix4x4(cos, -sin, center.x * (1 - cos) + center.y * sin,
169                                          sin, cos, center.y * (1 - cos) - center.x * sin,
170                                          0, 0, 1);
171         return merge(this, rotation);
172     }
173 
174     /**
175      * Scale the Matrix about factor scale
176      */
177     @nogc
178     ref Matrix4x4 scale(const Vector2f scale) pure nothrow {
179         const Matrix4x4 scaling = Matrix4x4(scale.x, 0, 0,
180                                         0, scale.y, 0,
181                                         0, 0, 1);
182 
183         return merge(this, scaling);
184     }
185 
186     /**
187      * Scale the Matrix about factor scale about the given center position
188      */
189     @nogc
190     ref Matrix4x4 scale(const Vector2f scale, const Vector2f center) pure nothrow {
191         const Matrix4x4 scaling = Matrix4x4(scale.x, 0, center.x * (1 - scale.x),
192                                         0, scale.y, center.y * (1 - scale.y),
193                                         0, 0, 1);
194         return merge(this, scaling);
195     }
196 
197     /**
198      * Calculates a View-Matrix
199      *
200      * See: <a href="http://3dgep.com/understanding-the-view-matrix/#Look_At_Camera">here</a>
201      */
202     @nogc
203     void lookAt(const Vector3f eye, const Vector3f look, const Vector3f up) pure nothrow {
204         const Vector3f dir = (look - eye).normalize();
205         const Vector3f right = dir.cross(up).normalize();
206         const Vector3f up2 = right.cross(dir).normalize();
207 
208         Matrix4x4 mat;
209         mat[0] = right.x;
210         mat[4] = right.y;
211         mat[8] = right.z;
212         mat[12] = -right.dot(eye);
213 
214         mat[1] = up2.x;
215         mat[5] = up2.y;
216         mat[9] = up2.z;
217         mat[13] = -up2.dot(eye);
218 
219         mat[2] = -dir.x;
220         mat[6] = -dir.y;
221         mat[10] = -dir.z;
222         mat[14] = dir.dot(eye);
223 
224         mat[3] = 0;
225         mat[7] = 0;
226         mat[11] = 0;
227         mat[15] = 1;
228 
229         merge(this, mat);
230     }
231 
232     /**
233      * Calculate a perspective projection
234      *
235      * See: <a href="http://www.songho.ca/opengl/gl_projectionmatrix.html#perspective">here</a>
236      */
237     @nogc
238     void perspective(float fov, float ratio, float nearp, float farp) pure nothrow {
239         immutable float f = 1f / std.math.tan(fov * (std.math.PI / 360f));
240 
241         Matrix4x4 mat;
242         mat[0] = f / ratio;
243         mat[5] = f;
244         mat[10] = (farp + nearp) / (nearp - farp);
245         mat[11] = -1;
246         mat[14] = (2 * farp * nearp) / (nearp - farp);
247         mat[15] = 0;
248 
249         merge(this, mat);
250     }
251 
252     /**
253      * Calculate a prthographic projection
254      *
255      * See: <a href="http://www.songho.ca/opengl/gl_projectionmatrix.html#ortho">here</a>
256      */
257     @nogc
258     bool ortho(const Rect rect, float zNear = 1, float zFar = -1) pure nothrow {
259         if (!rect.isEmpty()) {
260             immutable float inv_z = 1.0 / (zFar - zNear);
261             immutable float inv_y = 1.0 / (cast(float) rect.x - rect.height);
262             immutable float inv_x = 1.0 / (cast(float) rect.width - rect.y);
263 
264             Matrix4x4 mat;
265             // first column
266             mat[0] = 2.0 * inv_x;
267             // second
268             mat[5] = 2.0 * inv_y;
269             // third
270             mat[10] = -2.0 * inv_z;
271             // fourth
272             mat[12] = -(cast(float) rect.width + rect.y) * inv_x;
273             mat[13] = -(cast(float) rect.x + rect.height) * inv_y;
274             mat[14] = -(zFar + zNear) * inv_z;
275 
276             merge(this, mat);
277 
278             return true;
279         }
280 
281         return false;
282     }
283 
284     /**
285      * Returns the 16 values of the Matrix by ref
286      */
287     @nogc
288     ref inout(float[16]) getValues() inout pure nothrow {
289         return _values;
290     }
291 
292     /**
293      * Returns a specific value by index
294      */
295     @nogc
296     ref inout(float) opIndex(ubyte index) inout pure nothrow {
297         return _values[index];
298     }
299 
300     /**
301      * Supported operations: only *
302      */
303     @nogc
304     Matrix4x4 opBinary(string op : "*")(ref const Matrix4x4 mat) const pure nothrow {
305         Matrix4x4 cpy = this;
306         merge(cpy, mat);
307 
308         return cpy;
309     }
310 
311     /**
312      * Supported operations: only *=
313      */
314     @nogc
315     ref Matrix4x4 opOpAssign(string op : "*")(ref const Matrix4x4 math) pure nothrow {
316         return merge(this, math);
317     }
318 
319     /**
320      * Compares two Matrices approximately
321      */
322     @nogc
323     bool opEquals(ref const Matrix4x4 mat) const pure nothrow {
324         for (ubyte i = 0; i < 16; i++) {
325             if (!feq(mat[i], _values[i]))
326                 return false;
327         }
328 
329         return true;
330     }
331 }