1 /**
2 Usefull functions to work in 3d math
3 */
4 module zmath.math3d;
5 
6 import zmath.matrix;
7 import zmath.vector;
8 import zmath.quaternion;
9 
10 import std.math, std.traits;
11 
12 
13 /**
14  * Checks if a type is a valid Matrix for 3d math over 4d
15  */
16 template is4dMat(M) {
17   immutable bool is4dMat = (isMatrix!M && __traits(compiles,
18         (){
19             M t;
20             static assert(M.dim == 4);
21         }
22     ));
23 }
24 
25 unittest {
26   assert(is4dMat!Mat4f);
27   assert(! is4dMat!float);
28   assert(is4dMat!Mat4d);
29   assert(! is4dMat!Mat2f);
30 }
31 
32 /**
33  * Creates a projection matrix (similar to gluPerspective)
34  * Params:
35  * fov = Field of View in radians (PI_2 -> 90º)
36  * aspect = Aspect ratio (x/y)
37  * zMin = Near clipping plane
38  * zMax = Far clipping plane
39  * Returns a perspective projection matrix
40  */
41 @nogc M perspectiveMat(M=Mat4f, T=float, U=float, V=float, W=float)
42                           (T fov, U aspect, V zMin, W zMax) pure nothrow
43 if (is4dMat!M && isNumeric!T && isNumeric!U && isNumeric!V && isNumeric!W)
44 in {
45   import std.math : PI;
46 
47   assert (zMin > 0, "Zmin equal or less that zero");
48   assert (zMax > 0, "Zmax equal or less that zero");
49   assert (fov > 0 && fov <= 2 * PI, "Not valid FOV");
50   assert (aspect > 0, "Not valid aspect ratio");
51 } body  {
52   import std.math : tan;
53 
54   M mat = M.ZERO; // all set to 0
55 
56   auto f = 1.0f / tan(fov / 2.0f);
57   auto d = 1.0f / (zMin - zMax);
58 
59   mat[0,0] = f / aspect;
60   mat[1,1] = f;
61   mat[2,2] = (zMax + zMin) * d;
62   mat[2,3] = 2.0f * d * zMax * zMin;
63   mat[3,2] = -1;
64 
65   return mat;
66 }
67 
68 unittest {
69   const auto proy = perspectiveMat(PI_4, 800.0 / 600.0, 1.0, 100.0);
70   // dfmt off
71   const auto expected = Mat4f([
72      1.81066,       0,       0,       0,
73            0, 2.41421,       0,       0,
74            0,       0, -1.0202, -2.0202,
75            0,       0,      -1,       0
76   ]);
77   // dfmt on
78   assert (proy.approxEqual(expected), "Expected\n" ~ expected.toString() ~ " but obtained\n" ~ proy.toString() );
79   // Check value take from glOrtho
80 }
81 
82 /**
83  * Creates a orthographic projection matrix
84  * Params:
85  * xMin = Left clipping plane
86  * xMax = Right clipping plane
87  * yMin = Bottom clipping plane
88  * yMax = Top clipping plane
89  * zMin = Near clipping plane
90  * zMax = Far clipping plane
91  * Returns a orthographic projection matrix
92  */
93 @nogc M orthoMat(M=Mat4f,T=float, U=float, V=float, W=float, X=float, Y=float)
94                     (T xMin, U xMax, V yMin, W yMax, X zMin = 0, Y zMax = 1) pure nothrow
95 if (is4dMat!M && isNumeric!T && isNumeric!U && isNumeric!V && isNumeric!W
96     && isNumeric!X && isNumeric!Y)
97 in {
98   assert (xMin < xMax, "Invalid values for xMin and xMax");
99   assert (yMin < yMax, "Invalid values for yMin and yMax");
100   assert (zMin != zMax, "Invalid values for zMin and zMax");
101 } body {
102   M mat = M.ZERO;
103 
104   const widthRatio = 1.0L / (xMax - xMin);
105   const heightRatio = 1.0L / (yMax- yMin);
106   const depthRatio = 1.0L / (zMax - zMin);
107 
108   const sx = 2.0 * widthRatio;
109   const sy = 2.0 * heightRatio;
110   const sz = 2.0 * depthRatio;
111 
112   const tx = -(xMax + xMin) * widthRatio;
113   const ty = -(yMax + yMin) * heightRatio;
114   const tz = -(zMax + zMin) * depthRatio;
115 
116   mat[0,0] = sx;
117   mat[1,1] = sy;
118   mat[2,2] = sz;
119   mat[3,3] = 1;
120 
121   mat[0,3] = tx;
122   mat[1,3] = ty;
123   mat[2,3] = tz;
124   return mat;
125 }
126 
127 /**
128  * Creates a orthographic projection matrix
129  * Params:
130  * width = Width of visible zone
131  * height = Height of visible zone
132  * deep = Deep of visible zone
133  * Returns a orthographic projection matrix
134  */
135 @nogc M orthoMat(M=Mat4f,T=float, U=float, V=float)
136                   (T width =1, U height = 1, V deep = 1) pure nothrow
137 if (is4dMat!M && isNumeric!T && isNumeric!U && isNumeric!V)
138 in {
139   assert (width > 0);
140   assert (height > 0);
141   assert (deep > 0);
142 } body  {
143   auto x = width / 2.0L; // Max precision
144   auto y = height / 2.0L;
145   auto z = deep / 2.0L;
146   return orthoMat!M(-x, x, -y, y, z, -z);
147 }
148 
149 
150 unittest {
151   auto ortho = orthoMat(-2, 2, -2, 2, 0, 10);
152   // dfmt off
153   auto expected = Mat4f([
154       0.5,   0,    0,  0,
155         0, 0.5,    0, -0,
156         0,   0,  0.2, -1,
157         0,   0,    0,  1
158   ]);
159   // dfmt on
160   assert (ortho.approxEqual(expected), "Expected\n" ~ expected.toString() ~ " but obtained\n" ~ ortho.toString() );
161 
162   ortho = orthoMat(-10, 10, -10, 10, -1, 100f);
163   // dfmt off
164   expected = Mat4f([
165       0.1,   0,        0,         0,
166         0, 0.1,        0,         0,
167         0,   0, 0.019802, -0.980198,
168         0,   0,        0,         1
169   ]);
170   // dfmt on
171   assert (ortho.approxEqual(expected), "Expected\n" ~ expected.toString() ~ " but obtained\n" ~ ortho.toString() );
172 
173   ortho = orthoMat(2, 2, 10f);
174   // dfmt off
175   expected = Mat4f([
176       1, 0,    0,  0,
177       0, 1,    0, -0,
178       0, 0,  0.2, -0,
179       0, 0,    0,  1
180   ]);
181   // dfmt on
182   assert (ortho.approxEqual(expected), "Expected\n" ~ expected.toString() ~ " but obtained\n" ~ ortho.toString() );
183 }
184 
185 /**
186  * Creates a translation matrix from a 2d/3d Vector
187  * Params:
188  * v = 2d/3d Vector
189  * Returns a translation matrix
190  */
191 @nogc M translateMat(M=Mat4f, V=Vec3f) (V v) pure nothrow
192 if (isVector!V && V.dim <= 3 && is4dMat!M) {
193   M m = M.IDENTITY;
194   m[0, 3] = v.x;
195   m[1, 3] = v.y;
196   m[2, 3] = v.z;
197   return m;
198 }
199 
200 /**
201  * Creates a translation matrix from xyz coords
202  * Params:
203  * x = X coord
204  * y = Y coord
205  * z = Z coord
206  * Returns a translation matrix
207  */
208 @nogc M translateMat(M=Mat4f, T=float) (T x, T y, T z) pure nothrow
209 if (is4dMat!M && is(T : real)) {
210   return translateMat!(M, Vector!(T, 3))(Vector!(T, 3) (x,y,z));
211 }
212 
213 unittest {
214   auto m1 = translateMat(1f, 2f, 3f);
215   auto m2 = translateMat(Vec3f(1, 2, 3));
216   assert(m1 == m2);
217   assert(m2 == Mat4f([
218         1, 0, 0, 1,
219         0, 1, 0, 2,
220         0, 0, 1, 3,
221         0, 0, 0, 1,
222   ]), "Failed with\n" ~ m2.toString);
223 }
224 
225 /**
226  * Creates a uniform 3d scale matrix
227  * Params:
228  * s = scale factor
229  * Returns a scale matrix
230  */
231 @nogc M scaleMat(M=Mat4f, T=float) (T s) pure nothrow
232 if (is4dMat!M && is(T : real)) {
233   return M.IDENTITY * s;
234 }
235 
236 /**
237  * Creates a not uniform 3d scale matrix
238  * Params:
239  * x = X axis scale
240  * y = Y axis scale
241  * z = Z axis scale
242  * Returns a scale matrix
243  */
244 @nogc M scaleMat(M=Mat4f, T=float) (T x, T y, T z) pure nothrow
245 if (is4dMat!M && is(T : real)) {
246   M m = M.IDENTITY;
247   m[0,0] = x;
248   m[1,1] = y;
249   m[2,2] = z;
250   return m;
251 }
252 
253 /**
254  * Creates a not uniform 3d scale matrix
255  * Params:
256  * v = Vector with scale factor. If is a 2d Vector, z axis scale factor is 1
257  * Returns a scale matrix
258  */
259 @nogc M scaleMat(M=Mat4f, V=Vec3f) (V v) pure nothrow
260 if (isVector!V && V.dim <= 3 && is4dMat!M) {
261   M m = M.IDENTITY;
262   m[0,0] = v.x;
263   m[1,1] = v.y;
264   static if (V.dim == 2) {
265     m[2,2] = 1;
266   } else {
267     m[2,2] = v.z;
268   }
269   return m;
270 }
271 
272 unittest {
273   import std.conv : to;
274   auto m = scaleMat(10);
275   assert (m[0,0] == 10);
276   assert (m[1,1] == 10);
277   assert (m[2,2] == 10);
278   assert (m[3,3] == 10);
279 
280   m = scaleMat(10, 10, .5);
281   assert (m[0,0] == 10);
282   assert (m[1,1] == 10);
283   assert (m[2,2] == .5);
284   assert (m[3,3] == 1);
285 
286   m = scaleMat(Vec2f(4,4));
287   assert (m[0,0] == 4);
288   assert (m[1,1] == 4);
289   assert (m[2,2] == 1);
290   assert (m[3,3] == 1);
291 }
292 
293 /**
294  * Creates a rotation matrix from a axis and angle in radians
295  * Params:
296  * v = Rotation axis
297  * angle = angle in radians
298  * Returns a 3d rotation matrix
299  */
300 @nogc M rotMat(M=Mat4f, V=Vec3f, T=float) (V v, T angle) pure nothrow
301     if (isVector!V && V.dim <= 3 && is4dMat!M && __traits(isFloating, T)) {
302   import std.math : approxEqual, sin, cos;
303 
304   auto mag = v.sq_length;
305   if (mag == 0) {
306     return M.IDENTITY;
307   } else if (! approxEqual(mag, 1)) {
308     v.normalize;
309   }
310 
311   const s = sin(angle);
312   const c = cos(angle);
313   const oneMinusC = 1.0 - c;
314 
315   const xx = v.x * v.x;
316   const yy = v.y * v.y;
317   const zz = v.z * v.z;
318 
319   const xy = v.x * v.y;
320   const yz = v.y * v.z;
321   const zx = v.z * v.x;
322 
323   const xs = v.x * s;
324   const ys = v.y * s;
325   const zs = v.z * s;
326 
327   M m = M.IDENTITY;
328   m[0,0] = (oneMinusC * xx) + c;
329   m[0,1] = (oneMinusC * xy) - zs;
330   m[0,2] = (oneMinusC * zx) + ys;
331 
332   m[1,0] = (oneMinusC * xy) + zs;
333   m[1,1] = (oneMinusC * yy) + c;
334   m[1,2] = (oneMinusC * yz) - xs;
335 
336   m[2,0] = (oneMinusC * zx) - ys;
337   m[2,1] = (oneMinusC * yz) + xs;
338   m[2,2] = (oneMinusC * zz) + c;
339 
340   return m;
341 }
342 
343 /**
344  * Creates a rotation matrix from a axis and angle in radians
345  * Params:
346  * x = X coord of rotation axis
347  * y = Y coord of rotation axis
348  * z = Z coord of rotation axis
349  * angle = angle in radians
350  * Returns a 3d rotation matrix
351  */
352 @nogc M rotMat(M=Mat4f,T=float, U=float) (T x, T y, T z, U angle) pure nothrow
353 if (is4dMat!M && is(T : real) && is(U :real)) {
354   return rotMat!(M,Vector!(3,T),T) (Vector!(3,T)(x,y,z), angle);
355 }
356 
357 /**
358  * Creates a rotation matrix from a Quaternion
359  * Params:
360  * q = Quaternion that represents a rotation
361  * Returns a 3d rotation matrix
362  */
363 M rotMat(M=Mat4f,Q=Qua_f) (Q q)
364 if (is4dMat!M && isQuaternion!Q) {
365   return cast(M) q;
366 }
367 
368 unittest {
369   import std.conv : to;
370 
371   auto m = rotMat(Vec3f.X_AXIS, 0f);
372   assert (approxEqual(m.determinant , 1), "Expected rotation Matrix determinant to be 1, but obtained " ~
373       to!string(m.determinant) ~ "\n" ~ m.toString());
374   assert (m.approxEqual(Mat4f.IDENTITY));
375 
376   auto q = Qua_f(0, 0, PI_2);
377   m = rotMat(q);
378   assert (approxEqual(m.determinant , 1) );
379   // dfmt off
380   assert (m.approxEqual( Mat4f([
381           0, -1, 0, 0,
382           1,  0, 0, 0,
383           0,  0, 1, 0,
384           0,  0, 0, 1
385   ])));
386   // dfmt on
387 }
388 
389 /**
390  * Look At projection
391  * Based on gfm code
392  */
393 @nogc M lookAt(M=Mat4f, V=Vec3f, T=float) (V eye, V target, V up) pure nothrow
394 if (is4dMat!M && isNumeric!T && isVector!V && V.dim <= 3)
395 {
396     auto z = (eye - target);
397     z.normalize();
398     auto x = (-up & z);
399     x.normalize();
400     auto y = z & -x; // Cross product
401 
402     return M([-x.x,        -x.y,        -x.z,      x * eye,
403                y.x,         y.y,         y.z,   -(y * eye),
404                z.x,         z.y,         z.z,   -(z * eye),
405                 0f,          0f,          0f,           1f]);
406 }
407 
408 // TODO More usefull functions, etc...