1 /// Affine transforms module 2 module gfx.math.transform; 3 4 import gfx.math.mat; 5 import gfx.math.vec; 6 import std.meta : allSatisfy; 7 import std.traits : CommonType, isFloatingPoint, isNumeric; 8 9 pure @safe @nogc nothrow: 10 11 /// Build a translation matrix. 12 auto translation(X, Y)(in X x, in Y y) 13 { 14 alias ResMat = Mat3x3!(CommonType!(X, Y)); 15 return ResMat( 16 1, 0, x, 17 0, 1, y, 18 0, 0, 1, 19 ); 20 } 21 22 /// ditto 23 auto translation(V)(in V v) if (isVec2!V) 24 { 25 return Mat3x3!(V.Component) ( 26 1, 0, v.x, 27 0, 1, v.y, 28 0, 0, 1, 29 ); 30 } 31 32 /// ditto 33 auto affineTranslation(X, Y)(in X x, in Y y) 34 { 35 alias ResMat = Mat2x3!(CommonType!(X, Y)); 36 37 return ResMat( 38 1, 0, x, 39 0, 1, y 40 ); 41 } 42 43 /// ditto 44 auto affineTranslation(V)(in V v) if (isVec2!V) 45 { 46 alias ResMat = Mat2x3!(V.Component); 47 48 return ResMat( 49 1, 0, v.x, 50 0, 1, v.y 51 ); 52 } 53 54 /// ditto 55 auto translation(X, Y, Z)(in X x, in Y y, in Z z) 56 { 57 alias ResMat = Mat4x4!(CommonType!(X, Y, Z)); 58 return ResMat( 59 1, 0, 0, x, 60 0, 1, 0, y, 61 0, 0, 1, z, 62 0, 0, 0, 1, 63 ); 64 } 65 66 /// ditto 67 auto translation(V)(in V v) if (isVec3!V) 68 { 69 return Mat4x4!(V.Component) ( 70 1, 0, 0, v.x, 71 0, 1, 0, v.y, 72 0, 0, 1, v.z, 73 0, 0, 0, 1, 74 ); 75 } 76 77 unittest 78 { 79 import gfx.math.approx : approxUlp; 80 81 immutable v2 = dvec(4, 6); 82 assert( approxUlp(translation(2, 7) * dvec(v2, 1), dvec(6, 13, 1)) ); 83 84 immutable v3 = dvec(5, 6, 7); 85 assert( approxUlp(translation(7, 4, 1) * dvec(v3, 1), dvec(12, 10, 8, 1)) ); 86 } 87 88 /// Append a translation transform inferred from arguments to the matrix m. 89 /// This is equivalent to the expression $(D_CODE translation(...) * m) 90 /// but actually save computation by knowing 91 /// where the ones and zeros are in a pure translation matrix. 92 M translate(M, X, Y)(in M m, in X x, in Y y) 93 if (isMat!(3, 3, M) && allSatisfy!(isNumeric, X, Y)) 94 { 95 return M ( 96 // row 1 97 m[0, 0] + m[2, 0] * x, 98 m[0, 1] + m[2, 1] * x, 99 m[0, 2] + m[2, 2] * x, 100 // row 2 101 m[1, 0] + m[2, 0] * y, 102 m[1, 1] + m[2, 1] * y, 103 m[1, 2] + m[2, 2] * y, 104 // row 3 105 m[2, 0], m[2, 1], m[2, 2] 106 ); 107 } 108 109 /// ditto 110 M translate(M, X, Y)(in M m, in X x, in Y y) 111 if (isMat!(2, 3, M) && allSatisfy!(isNumeric, X, Y)) 112 { 113 return M ( 114 // row 1 115 m[0, 0], 116 m[0, 1], 117 m[0, 2] + x, 118 // row 2 119 m[1, 0], 120 m[1, 1], 121 m[1, 2] + y, 122 ); 123 } 124 125 /// ditto 126 M translate(M, V)(in M m, in V v) 127 if ((isMat!(2, 3, M) || isMat!(3, 3, M)) && isVec!(2, V)) 128 { 129 return translate (m, v[0] ,v[1]); 130 } 131 132 /// ditto 133 M translate (M, X, Y, Z)(in M m, in X x, in Y y, in Z z) 134 if (isMat!(4, 4, M) && allSatisfy!(isNumeric, X, Y, Z)) 135 { 136 return M ( 137 // row 1 138 m[0, 0] + m[3, 0] * x, 139 m[0, 1] + m[3, 1] * x, 140 m[0, 2] + m[3, 2] * x, 141 m[0, 3] + m[3, 3] * x, 142 // row 2 143 m[1, 0] + m[3, 0] * y, 144 m[1, 1] + m[3, 1] * y, 145 m[1, 2] + m[3, 2] * y, 146 m[1, 3] + m[3, 3] * y, 147 // row 3 148 m[2, 0] + m[3, 0] * z, 149 m[2, 1] + m[3, 1] * z, 150 m[2, 2] + m[3, 2] * z, 151 m[2, 3] + m[3, 3] * z, 152 // row 4 153 m[3, 0], m[3, 1], m[3, 2], m[3, 3] 154 ); 155 } 156 157 /// ditto 158 M translate (M, X, Y, Z)(in M m, in X x, in Y y, in Z z) 159 if (isMat!(3, 4, M) && allSatisfy!(isNumeric, X, Y, Z)) 160 { 161 return M ( 162 // row 1 163 m[0, 0] + m[3, 0] * x, 164 m[0, 1] + m[3, 1] * x, 165 m[0, 2] + m[3, 2] * x, 166 m[0, 3] + m[3, 3] * x, 167 // row 2 168 m[1, 0] + m[3, 0] * y, 169 m[1, 1] + m[3, 1] * y, 170 m[1, 2] + m[3, 2] * y, 171 m[1, 3] + m[3, 3] * y, 172 // row 3 173 m[2, 0] + m[3, 0] * z, 174 m[2, 1] + m[3, 1] * z, 175 m[2, 2] + m[3, 2] * z, 176 m[2, 3] + m[3, 3] * z, 177 ); 178 } 179 180 /// ditto 181 M translate(M, V)(in M m, in V v) 182 if ((isMat!(3, 4, M) || isMat!(4, 4, M)) && isVec!(3, V)) 183 { 184 return translate (m, v[0] ,v[1], v[2]); 185 } 186 187 /// 188 unittest 189 { 190 import gfx.math.approx : approxUlp; 191 192 immutable m = DMat3( 1, 2, 3, 4, 5, 6, 7, 8, 9 ); 193 194 immutable expected = translation(7, 12) * m; // full multiplication 195 immutable result = translate(m, 7, 12); // simplified multiplication 196 197 assert (approxUlp(expected, result)); 198 } 199 /// 200 unittest 201 { 202 import gfx.math.approx : approxUlp; 203 204 immutable m = DMat4( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ); 205 206 immutable expected = translation(7, 12, 18) * m; // full multiplication 207 immutable result = translate(m, 7, 12, 18); // simplified multiplication 208 209 assert (approxUlp(expected, result)); 210 } 211 212 213 /// Build a pure 3d rotation matrix with angle in radians 214 auto rotationPure(T, V) (in T angle, in V axis) 215 if (isFloatingPoint!T && isVec!(3, V)) 216 { 217 static assert ( 218 isFloatingPoint!(V.Component), 219 "rotationPure must be passed a floating point axis" 220 ); 221 import std.math : cos, sin; 222 const u = normalize(axis); 223 const c = cos(angle); 224 const s = sin(angle); 225 const c1 = 1 - c; 226 return Mat3x3!(V.Component) ( 227 // row 1 228 c1 * u.x * u.x + c, 229 c1 * u.x * u.y - s * u.z, 230 c1 * u.x * u.z + s * u.y, 231 // row 2 232 c1 * u.y * u.x + s * u.z, 233 c1 * u.y * u.y + c, 234 c1 * u.y * u.z - s * u.x, 235 // row 3 236 c1 * u.z * u.x - s * u.y, 237 c1 * u.z * u.y + s * u.x, 238 c1 * u.z * u.z + c, 239 ); 240 } 241 242 /// Build a rotation matrix. 243 /// angle in radians. 244 Mat3x3!T rotation(T) (in T angle) if (isFloatingPoint!T) 245 { 246 import std.math : cos, sin; 247 const c = cast(T) cos(angle); 248 const s = cast(T) sin(angle); 249 return Mat3x3!T ( 250 c, -s, 0, 251 s, c, 0, 252 0, 0, 1 253 ); 254 } 255 256 Mat2x3!T affineRotation(T) (in T angle) if (isFloatingPoint!T) 257 { 258 import std.math : cos, sin; 259 const c = cast(T) cos(angle); 260 const s = cast(T) sin(angle); 261 return Mat2x3!T ( 262 c, -s, 0, 263 s, c, 0, 264 ); 265 } 266 267 /// ditto 268 auto rotation(T, V) (in T angle, in V axis) 269 if (isVec!(3, V) && isFloatingPoint!T) 270 { 271 static assert ( 272 isFloatingPoint!(V.Component), 273 "rotation must be passed a floating point axis" 274 ); 275 const m = rotationPure(angle, axis); 276 return mat( 277 vec(m[0], 0), 278 vec(m[1], 0), 279 vec(m[2], 0), 280 vec(0, 0, 0, 1) 281 ); 282 } 283 284 /// ditto 285 auto rotation(T) (in T angle, in T x, in T y, in T z) 286 if (isFloatingPoint!T) 287 { 288 return rotation(angle, vec(x, y, z)); 289 } 290 291 /// Append a rotation transform inferred from arguments to the matrix m. 292 /// This is equivalent to the expression $(D_CODE rotation(...) * m) 293 /// but actually save computation by knowing 294 /// where the ones and zeros are in a pure rotation matrix. 295 M rotate (M, T) (in M m, in T angle) 296 if (isMat!(3, 3, M) && isFloatingPoint!T) 297 { 298 import std.math : cos, sin; 299 immutable c = cos(angle); 300 immutable s = sin(angle); 301 return M ( 302 // row 1 303 c * m[0, 0] - s * m[1, 0], 304 c * m[0, 1] - s * m[1, 1], 305 c * m[0, 2] - s * m[1, 2], 306 // row 2 307 s * m[0, 0] + c * m[1, 0], 308 s * m[0, 1] + c * m[1, 1], 309 s * m[0, 2] + c * m[1, 2], 310 // row 3 311 m[2, 0], m[2, 1], m[2, 2] 312 ); 313 } 314 315 /// ditto 316 M rotate (M, T) (in M m, in T angle) 317 if (isMat!(2, 3, M) && isFloatingPoint!T) 318 { 319 import std.math : cos, sin; 320 immutable c = cos(angle); 321 immutable s = sin(angle); 322 return M ( 323 // row 1 324 c * m[0, 0] - s * m[1, 0], 325 c * m[0, 1] - s * m[1, 1], 326 c * m[0, 2] - s * m[1, 2], 327 // row 2 328 s * m[0, 0] + c * m[1, 0], 329 s * m[0, 1] + c * m[1, 1], 330 s * m[0, 2] + c * m[1, 2] 331 ); 332 } 333 334 /// ditto 335 M rotate (M, T, V) (in M m, in T angle, in V axis) 336 if (isMat!(4, 4, M) && isFloatingPoint!T && isVec!(3, V)) 337 { 338 static assert ( 339 isFloatingPoint!(V.Component), 340 "rotate must be passed a floating point axis" 341 ); 342 immutable r = rotationPure(angle, axis); 343 return M ( 344 // row 1 345 r[0, 0]*m[0, 0] + r[0, 1]*m[1, 0] + r[0, 2]*m[2, 0], 346 r[0, 0]*m[0, 1] + r[0, 1]*m[1, 1] + r[0, 2]*m[2, 1], 347 r[0, 0]*m[0, 2] + r[0, 1]*m[1, 2] + r[0, 2]*m[2, 2], 348 r[0, 0]*m[0, 3] + r[0, 1]*m[1, 3] + r[0, 2]*m[2, 3], 349 // row 2 350 r[1, 0]*m[0, 0] + r[1, 1]*m[1, 0] + r[1, 2]*m[2, 0], 351 r[1, 0]*m[0, 1] + r[1, 1]*m[1, 1] + r[1, 2]*m[2, 1], 352 r[1, 0]*m[0, 2] + r[1, 1]*m[1, 2] + r[1, 2]*m[2, 2], 353 r[1, 0]*m[0, 3] + r[1, 1]*m[1, 3] + r[1, 2]*m[2, 3], 354 // row 3 355 r[2, 0]*m[0, 0] + r[2, 1]*m[1, 0] + r[2, 2]*m[2, 0], 356 r[2, 0]*m[0, 1] + r[2, 1]*m[1, 1] + r[2, 2]*m[2, 1], 357 r[2, 0]*m[0, 2] + r[2, 1]*m[1, 2] + r[2, 2]*m[2, 2], 358 r[2, 0]*m[0, 3] + r[2, 1]*m[1, 3] + r[2, 2]*m[2, 3], 359 // row 4 360 m[3, 0], m[3, 1], m[3, 2], m[3, 3] 361 ); 362 } 363 364 /// ditto 365 M rotate (M, T, V) (in M m, in T angle, in V axis) 366 if (isMat!(3, 4, M) && isVec!(3, V) && isFloatingPoint!T) 367 { 368 static assert ( 369 isFloatingPoint!(V.Component), 370 "rotate must be passed a floating point axis" 371 ); 372 const r = rotationPure(angle, axis); 373 return M ( 374 // row 1 375 r[0, 0]*m[0, 0] + r[0, 1]*m[1, 0] + r[0, 2]*m[2, 0], 376 r[0, 0]*m[0, 1] + r[0, 1]*m[1, 1] + r[0, 2]*m[2, 1], 377 r[0, 0]*m[0, 2] + r[0, 1]*m[1, 2] + r[0, 2]*m[2, 2], 378 r[0, 0]*m[0, 3] + r[0, 1]*m[1, 3] + r[0, 2]*m[2, 3], 379 // row 2 380 r[1, 0]*m[0, 0] + r[1, 1]*m[1, 0] + r[1, 2]*m[2, 0], 381 r[1, 0]*m[0, 1] + r[1, 1]*m[1, 1] + r[1, 2]*m[2, 1], 382 r[1, 0]*m[0, 2] + r[1, 1]*m[1, 2] + r[1, 2]*m[2, 2], 383 r[1, 0]*m[0, 3] + r[1, 1]*m[1, 3] + r[1, 2]*m[2, 3], 384 // row 3 385 r[2, 0]*m[0, 0] + r[2, 1]*m[1, 0] + r[2, 2]*m[2, 0], 386 r[2, 0]*m[0, 1] + r[2, 1]*m[1, 1] + r[2, 2]*m[2, 1], 387 r[2, 0]*m[0, 2] + r[2, 1]*m[1, 2] + r[2, 2]*m[2, 2], 388 r[2, 0]*m[0, 3] + r[2, 1]*m[1, 3] + r[2, 2]*m[2, 3], 389 ); 390 } 391 392 /// ditto 393 M rotate (M, T) (in M m, in T angle, in T x, in T y, in T z) 394 if ((isMat!(3, 4, M) || isMat!(4, 4, M)) && isFloatingPoint!T) 395 { 396 return rotate(m, angle, vec(x, y, z)); 397 } 398 399 /// 400 unittest 401 { 402 import gfx.math.approx : approxUlp; 403 import std.math : PI; 404 405 immutable m = DMat3( 1, 2, 3, 4, 5, 6, 7, 8, 9 ); 406 407 immutable expected = rotation!double(PI) * m; // full multiplication 408 immutable result = rotate(m, PI); // simplified multiplication 409 410 assert (approxUlp(expected, result)); 411 } 412 /// 413 unittest 414 { 415 import gfx.math.approx : approxUlp; 416 import std.math : PI; 417 418 immutable m = DMat4( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ); 419 immutable angle = PI; 420 immutable v = fvec(3, 4, 5); 421 422 immutable expected = rotation(angle, v) * m; // full multiplication 423 immutable result = rotate(m, angle, v); // simplified multiplication 424 425 assert (approxUlp(expected, result)); 426 } 427 428 /// Build a rotation matrix from Euler angles 429 /// The convention taken is Xa, Zb, Xc 430 Mat3!T eulerAngles(T) (in T a, in T b, in T c) 431 if (isFloatingPoint!T) 432 { 433 import std.math : cos, sin; 434 435 immutable sa = sin(a); 436 immutable sb = sin(b); 437 immutable sc = sin(c); 438 439 immutable ca = cos(a); 440 immutable cb = cos(b); 441 immutable cc = cos(c); 442 443 return Mat3!T ( 444 cb, -cc*sb, sb*sc, 445 ca*sb, ca*cb*cc - sa*sc, -cc*sa - ca*cb*sc, 446 sa*sb, ca*sc + cb*cc*sa, ca*cc - cb*sa*sc, 447 ); 448 } 449 450 auto eulerAngles(V) (in V angles) 451 if (isVec!(3, V) && isFloatingPoint!(V.Component)) 452 { 453 return eulerAngles(angles[0], angles[1], angles[2]); 454 } 455 456 /// ditto 457 458 /// 459 unittest { 460 import gfx.math.approx : approxUlpAndAbs; 461 import std.math : PI; 462 463 const v = fvec(0, 0, 1); 464 // rotate PI/2 around X, then no rotation around Z, then again PI/2 around X 465 const m = eulerAngles(float(PI / 2), 0f, float(PI / 2)); 466 const result = m * v; 467 const expected = fvec(0, 0, -1); 468 469 assert(approxUlpAndAbs(result, expected)); 470 } 471 472 473 /// Build a scale matrix. 474 Mat3!(CommonType!(X, Y)) scale(X, Y) (in X x, in Y y) 475 if (allSatisfy!(isNumeric, X, Y)) 476 { 477 return Mat3!(CommonType!(X, Y))( 478 x, 0, 0, 479 0, y, 0, 480 0, 0, 1, 481 ); 482 } 483 484 /// ditto 485 auto scale(V) (in V v) if (isVec2!V) 486 { 487 return Mat3!(V.Component) ( 488 v.x, 0, 0, 489 0, v.y, 0, 490 0, 0, 1, 491 ); 492 } 493 494 /// ditto 495 Mat2x3!(CommonType!(X, Y)) affineScale(X, Y) (in X x, in Y y) 496 if (allSatisfy!(isNumeric, X, Y)) 497 { 498 return Mat2x3!(CommonType!(X, Y))( 499 x, 0, 0, 500 0, y, 0, 501 ); 502 } 503 504 /// ditto 505 auto affineScale(V) (in V v) if (isVec2!V) 506 { 507 return Mat2x3!(V.Component) ( 508 v.x, 0, 0, 509 0, v.y, 0, 510 ); 511 } 512 513 /// ditto 514 Mat4!(CommonType!(X, Y, Z)) scale (X, Y, Z) (in X x, in Y y, in Z z) 515 if (allSatisfy!(isNumeric, X, Y, Z)) 516 { 517 return Mat4!(CommonType!(X, Y, Z))( 518 x, 0, 0, 0, 519 0, y, 0, 0, 520 0, 0, z, 0, 521 0, 0, 0, 1, 522 ); 523 } 524 525 /// ditto 526 auto scale(V) (in V v) if (isVec3!V) 527 { 528 return Mat4!(V.Component) ( 529 v.x, 0, 0, 0, 530 0, v.y, 0, 0, 531 0, 0, v.z, 0, 532 0, 0, 0, 1, 533 ); 534 } 535 536 /// Append a scale transform inferred from arguments to the matrix m. 537 /// This is equivalent to the expression $(D_CODE scale(...) * m) 538 /// but actually save computation by knowing 539 /// where the ones and zeros are in a pure scale matrix. 540 M scale (M, X, Y)(in M m, in X x, in Y y) 541 if (isMat!(3, 3, M) && allSatisfy!(isNumeric, X, Y)) 542 { 543 return M ( 544 // row 1 545 m[0, 0] * x, 546 m[0, 1] * x, 547 m[0, 2] * x, 548 // row 2 549 m[1, 0] * y, 550 m[1, 1] * y, 551 m[1, 2] * y, 552 // row 3 553 m[2, 0], m[2, 1], m[2, 2] 554 ); 555 } 556 557 /// ditto 558 M scale (M, X, Y)(in M m, in X x, in Y y) 559 if (isMat!(2, 3, M) && allSatisfy!(isNumeric, X, Y)) 560 { 561 return M ( 562 // row 1 563 m[0, 0] * x, 564 m[0, 1] * x, 565 m[0, 2] * x, 566 // row 2 567 m[1, 0] * y, 568 m[1, 1] * y, 569 m[1, 2] * y, 570 ); 571 } 572 573 /// ditto 574 M scale (M, V)(in M m, in V v) 575 if ((isMat!(2, 3, M) || isMat!(3, 3, M)) && isVec!(2, V)) 576 { 577 return scale(m, v[0], v[1]); 578 } 579 580 /// ditto 581 M scale (M, X, Y, Z)(in M m, in X x, in Y y, in Z z) 582 if (isMat!(4, 4, M) && allSatisfy!(isNumeric, X, Y, Z)) 583 { 584 return M ( 585 // row 1 586 m[0, 0] * x, 587 m[0, 1] * x, 588 m[0, 2] * x, 589 m[0, 3] * x, 590 // row 2 591 m[1, 0] * y, 592 m[1, 1] * y, 593 m[1, 2] * y, 594 m[1, 3] * y, 595 // row 3 596 m[2, 0] * z, 597 m[2, 1] * z, 598 m[2, 2] * z, 599 m[2, 3] * z, 600 // row 4 601 m[3, 0], m[3, 1], m[3, 2], m[3, 3] 602 ); 603 } 604 605 /// ditto 606 M scale (M, X, Y, Z)(in M m, in X x, in Y y, in Z z) 607 if (isMat!(3, 4, M) && allSatisfy!(isNumeric, X, Y, Z)) 608 { 609 return M ( 610 // row 1 611 m[0, 0] * x, 612 m[0, 1] * x, 613 m[0, 2] * x, 614 m[0, 3] * x, 615 // row 2 616 m[1, 0] * y, 617 m[1, 1] * y, 618 m[1, 2] * y, 619 m[1, 3] * y, 620 // row 3 621 m[2, 0] * z, 622 m[2, 1] * z, 623 m[2, 2] * z, 624 m[2, 3] * z, 625 ); 626 } 627 628 /// ditto 629 M scale (M, V)(in M m, in V v) 630 if ((isMat!(3, 4, M) || isMat!(4, 4, M)) && isVec!(3, V)) 631 { 632 return scale(m, v[0], v[1], v[2]); 633 } 634 635 636 /// 637 unittest 638 { 639 import gfx.math.approx : approxUlp; 640 641 immutable m = DMat3( 1, 2, 3, 4, 5, 6, 7, 8, 9 ); 642 643 immutable expected = scale(4, 5) * m; // full multiplication 644 immutable result = scale(m, 4, 5); // simplified multiplication 645 646 assert (approxUlp(expected, result)); 647 } 648 /// 649 unittest 650 { 651 import gfx.math.approx : approxUlp; 652 653 immutable m = DMat4( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ); 654 655 immutable expected = scale(4, 5, 6) * m; // full multiplication 656 immutable result = scale(m, 4, 5, 6); // simplified multiplication 657 658 assert (approxUlp(expected, result)); 659 } 660 661 /// Affine matrix multiplication. 662 /// 663 /// Perform a multiplication of two 2x3 or two 3x4 matrices as if their last row 664 /// were [ 0, 0, 1] or [ 0, 0, 0, 1 ]. 665 /// Allows manipulation of smaller matrices when only affine transformation are 666 /// required. 667 /// Note: translation, rotation, scaling, shearing and any combination of those 668 /// are affine transforms. Projection is not affine. 669 /// I.e. for 2D, an affine transform is held in 2x3 matrix, 2x2 for rotation and 670 /// scaling and an additional column for translation. 671 /// The same applies with 3D and 3x4 matrices. 672 /// 673 /// This is not implemented as an operator as it is not a mathematical 674 /// operation (ml.columnLength != mr.rowLength). 675 auto affineMult(ML, MR)(in ML ml, in MR mr) 676 if (areMat!(2, 3, ML, MR) || areMat!(3, 4, ML, MR)) 677 { 678 alias Comp = CommonType!(ML.Component, MR.Component); 679 enum rowLength = ML.rowLength; 680 enum columnLength = ML.columnLength; 681 alias ResMat = Mat!(Comp, rowLength, columnLength); 682 683 ResMat res = void; 684 static foreach(r; 0 .. rowLength) 685 { 686 static foreach (c; 0 .. columnLength) 687 {{ 688 Comp resComp = 0; 689 static foreach (rc; 0 .. rowLength) // that is columnCount-1 690 { 691 resComp += ml[r, rc] * mr[rc, c]; 692 } 693 static if (c == columnLength-1) 694 { 695 resComp += ml[r, c]; // that is the last one in the last row 696 } 697 res[r, c] = resComp; 698 }} 699 } 700 return res; 701 } 702 703 /// 704 unittest 705 { 706 import gfx.math.approx : approxUlp; 707 708 /// full matrices 709 immutable fm1 = FMat3x3( 710 1, 2, 3, 711 4, 5, 6, 712 0, 0, 1 713 ); 714 immutable fm2 = DMat3x3( 715 7, 8, 9, 716 10, 11, 12, 717 0, 0, 1 718 ); 719 720 /// affine matrices 721 immutable am1 = FMat2x3( 722 1, 2, 3, 723 4, 5, 6, 724 ); 725 immutable am2 = DMat2x3( 726 7, 8, 9, 727 10, 11, 12, 728 ); 729 730 immutable expected = (fm1 * fm2).slice!(0, 2, 0, 3); 731 immutable result = affineMult(am1, am2); 732 assert( approxUlp(expected, result) ); 733 } 734 735 736 /// Transform a vector by a matrix in homogenous coordinates. 737 auto transform(V, M)(in V v, in M m) 738 if (isVec!(2, V) && isMat!(3, 3, M)) 739 { 740 return (m * vec(v, 1)).xy; 741 } 742 /// ditto 743 auto transform(V, M)(in V v, in M m) 744 if (isVec!(2, V) && isMat!(2, 3, M)) 745 { 746 return m * vec(v, 1); 747 } 748 /// ditto 749 auto transform(V, M)(in V v, in M m) 750 if (isVec!(3, V) && isMat!(4, 4, M)) 751 { 752 return (m * vec(v, 1)).xyz; 753 } 754 /// ditto 755 auto transform(V, M)(in V v, in M m) 756 if (isVec!(3, V) && isMat!(3, 4, M)) 757 { 758 return m * vec(v, 1); 759 } 760 /// ditto 761 auto transform(V, M)(in V v, in M m) 762 if (isVec!(4, V) && isMat!(4, 4, M)) 763 { 764 return m * v; 765 } 766 767 unittest 768 { 769 import gfx.math.approx : approxUlp; 770 771 // 2x3 matrix can hold affine 2D transforms 772 immutable transl = DMat2x3( 773 1, 0, 3, 774 0, 1, 2, 775 ); 776 assert( approxUlp(transform(dvec(3, 5), transl), dvec(6, 7)) ); 777 } 778 779 /// 780 unittest 781 { 782 import gfx.math.approx : approxUlp, approxUlpAndAbs; 783 import std.math : PI; 784 785 immutable v = dvec(2, 0); 786 auto m = DMat2x3.identity; 787 788 m = m.rotate(PI/2); 789 assert ( approxUlpAndAbs(transform(v, m), dvec(0, 2)) ); 790 791 m = m.translate(2, 2); 792 assert ( approxUlp(transform(v, m), dvec(2, 4)) ); 793 794 m = m.scale(2, 2); 795 assert ( approxUlp(transform(v, m), dvec(4, 8)) ); 796 } 797 798 /// 799 unittest 800 { 801 import gfx.math.approx : approxUlp; 802 803 auto st = scale!float(2, 2).translate(3, 1); 804 assert( approxUlp(transform(fvec(0, 0), st), fvec(3, 1)) ); 805 assert( approxUlp(transform(fvec(1, 1), st), fvec(5, 3)) ); 806 807 auto ts = translation!float(3, 1).scale(2, 2); 808 assert( approxUlp(transform(fvec(0, 0), ts), fvec(6, 2)) ); 809 assert( approxUlp(transform(fvec(1, 1), ts), fvec(8, 4)) ); 810 }