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...