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 @safe pure: 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 429 /// Build a scale matrix. 430 Mat3!(CommonType!(X, Y)) scale(X, Y) (in X x, in Y y) 431 if (allSatisfy!(isNumeric, X, Y)) 432 { 433 return Mat3!(CommonType!(X, Y))( 434 x, 0, 0, 435 0, y, 0, 436 0, 0, 1, 437 ); 438 } 439 440 /// ditto 441 auto scale(V) (in V v) if (isVec2!V) 442 { 443 return Mat3!(V.Component) ( 444 v.x, 0, 0, 445 0, v.y, 0, 446 0, 0, 1, 447 ); 448 } 449 450 /// ditto 451 Mat2x3!(CommonType!(X, Y)) affineScale(X, Y) (in X x, in Y y) 452 if (allSatisfy!(isNumeric, X, Y)) 453 { 454 return Mat2x3!(CommonType!(X, Y))( 455 x, 0, 0, 456 0, y, 0, 457 ); 458 } 459 460 /// ditto 461 auto affineScale(V) (in V v) if (isVec2!V) 462 { 463 return Mat2x3!(V.Component) ( 464 v.x, 0, 0, 465 0, v.y, 0, 466 ); 467 } 468 469 /// ditto 470 Mat4!(CommonType!(X, Y, Z)) scale (X, Y, Z) (in X x, in Y y, in Z z) 471 if (allSatisfy!(isNumeric, X, Y, Z)) 472 { 473 return Mat4!(CommonType!(X, Y, Z))( 474 x, 0, 0, 0, 475 0, y, 0, 0, 476 0, 0, z, 0, 477 0, 0, 0, 1, 478 ); 479 } 480 481 /// ditto 482 auto scale(V) (in V v) if (isVec3!V) 483 { 484 return Mat4!(V.Component) ( 485 v.x, 0, 0, 0, 486 0, v.y, 0, 0, 487 0, 0, v.z, 0, 488 0, 0, 0, 1, 489 ); 490 } 491 492 /// Append a scale transform inferred from arguments to the matrix m. 493 /// This is equivalent to the expression $(D_CODE scale(...) * m) 494 /// but actually save computation by knowing 495 /// where the ones and zeros are in a pure scale matrix. 496 M scale (M, X, Y)(in M m, in X x, in Y y) 497 if (isMat!(3, 3, M) && allSatisfy!(isNumeric, X, Y)) 498 { 499 return M ( 500 // row 1 501 m[0, 0] * x, 502 m[0, 1] * x, 503 m[0, 2] * x, 504 // row 2 505 m[1, 0] * y, 506 m[1, 1] * y, 507 m[1, 2] * y, 508 // row 3 509 m[2, 0], m[2, 1], m[2, 2] 510 ); 511 } 512 513 /// ditto 514 M scale (M, X, Y)(in M m, in X x, in Y y) 515 if (isMat!(2, 3, M) && allSatisfy!(isNumeric, X, Y)) 516 { 517 return M ( 518 // row 1 519 m[0, 0] * x, 520 m[0, 1] * x, 521 m[0, 2] * x, 522 // row 2 523 m[1, 0] * y, 524 m[1, 1] * y, 525 m[1, 2] * y, 526 ); 527 } 528 529 /// ditto 530 M scale (M, V)(in M m, in V v) 531 if ((isMat!(2, 3, M) || isMat!(3, 3, M)) && isVec!(2, V)) 532 { 533 return scale(m, v[0], v[1]); 534 } 535 536 /// ditto 537 M scale (M, X, Y, Z)(in M m, in X x, in Y y, in Z z) 538 if (isMat!(4, 4, M) && allSatisfy!(isNumeric, X, Y, Z)) 539 { 540 return M ( 541 // row 1 542 m[0, 0] * x, 543 m[0, 1] * x, 544 m[0, 2] * x, 545 m[0, 3] * x, 546 // row 2 547 m[1, 0] * y, 548 m[1, 1] * y, 549 m[1, 2] * y, 550 m[1, 3] * y, 551 // row 3 552 m[2, 0] * z, 553 m[2, 1] * z, 554 m[2, 2] * z, 555 m[2, 3] * z, 556 // row 4 557 m[3, 0], m[3, 1], m[3, 2], m[3, 3] 558 ); 559 } 560 561 /// ditto 562 M scale (M, X, Y, Z)(in M m, in X x, in Y y, in Z z) 563 if (isMat!(3, 4, M) && allSatisfy!(isNumeric, X, Y, Z)) 564 { 565 return M ( 566 // row 1 567 m[0, 0] * x, 568 m[0, 1] * x, 569 m[0, 2] * x, 570 m[0, 3] * x, 571 // row 2 572 m[1, 0] * y, 573 m[1, 1] * y, 574 m[1, 2] * y, 575 m[1, 3] * y, 576 // row 3 577 m[2, 0] * z, 578 m[2, 1] * z, 579 m[2, 2] * z, 580 m[2, 3] * z, 581 ); 582 } 583 584 /// ditto 585 M scale (M, V)(in M m, in V v) 586 if ((isMat!(3, 4, M) || isMat!(4, 4, M)) && isVec!(3, V)) 587 { 588 return scale(m, v[0], v[1], v[2]); 589 } 590 591 592 /// 593 unittest 594 { 595 import gfx.math.approx : approxUlp; 596 597 immutable m = DMat3( 1, 2, 3, 4, 5, 6, 7, 8, 9 ); 598 599 immutable expected = scale(4, 5) * m; // full multiplication 600 immutable result = scale(m, 4, 5); // simplified multiplication 601 602 assert (approxUlp(expected, result)); 603 } 604 /// 605 unittest 606 { 607 import gfx.math.approx : approxUlp; 608 609 immutable m = DMat4( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ); 610 611 immutable expected = scale(4, 5, 6) * m; // full multiplication 612 immutable result = scale(m, 4, 5, 6); // simplified multiplication 613 614 assert (approxUlp(expected, result)); 615 } 616 617 /// Affine matrix multiplication. 618 /// 619 /// Perform a multiplication of two 2x3 or two 3x4 matrices as if their last row 620 /// were [ 0, 0, 1] or [ 0, 0, 0, 1 ]. 621 /// Allows manipulation of smaller matrices when only affine transformation are 622 /// required. 623 /// Note: translation, rotation, scaling, shearing and any combination of those 624 /// are affine transforms. Projection is not affine. 625 /// I.e. for 2D, an affine transform is held in 2x3 matrix, 2x2 for rotation and 626 /// scaling and an additional column for translation. 627 /// The same applies with 3D and 3x4 matrices. 628 /// 629 /// This is not implemented as an operator as it is not a mathematical 630 /// operation (ml.columnLength != mr.rowLength). 631 auto affineMult(ML, MR)(in ML ml, in MR mr) 632 if (areMat!(2, 3, ML, MR) || areMat!(3, 4, ML, MR)) 633 { 634 alias Comp = CommonType!(ML.Component, MR.Component); 635 enum rowLength = ML.rowLength; 636 enum columnLength = ML.columnLength; 637 alias ResMat = Mat!(Comp, rowLength, columnLength); 638 639 ResMat res = void; 640 static foreach(r; 0 .. rowLength) 641 { 642 static foreach (c; 0 .. columnLength) 643 {{ 644 Comp resComp = 0; 645 static foreach (rc; 0 .. rowLength) // that is columnCount-1 646 { 647 resComp += ml[r, rc] * mr[rc, c]; 648 } 649 static if (c == columnLength-1) 650 { 651 resComp += ml[r, c]; // that is the last one in the last row 652 } 653 res[r, c] = resComp; 654 }} 655 } 656 return res; 657 } 658 659 /// 660 unittest 661 { 662 import gfx.math.approx : approxUlp; 663 664 /// full matrices 665 immutable fm1 = FMat3x3( 666 1, 2, 3, 667 4, 5, 6, 668 0, 0, 1 669 ); 670 immutable fm2 = DMat3x3( 671 7, 8, 9, 672 10, 11, 12, 673 0, 0, 1 674 ); 675 676 /// affine matrices 677 immutable am1 = FMat2x3( 678 1, 2, 3, 679 4, 5, 6, 680 ); 681 immutable am2 = DMat2x3( 682 7, 8, 9, 683 10, 11, 12, 684 ); 685 686 immutable expected = (fm1 * fm2).slice!(0, 2, 0, 3); 687 immutable result = affineMult(am1, am2); 688 assert( approxUlp(expected, result) ); 689 } 690 691 692 /// Transform a vector by a matrix in homogenous coordinates. 693 auto transform(V, M)(in V v, in M m) 694 if (isVec!(2, V) && isMat!(3, 3, M)) 695 { 696 return (m * vec(v, 1)).xy; 697 } 698 /// ditto 699 auto transform(V, M)(in V v, in M m) 700 if (isVec!(2, V) && isMat!(2, 3, M)) 701 { 702 return m * vec(v, 1); 703 } 704 /// ditto 705 auto transform(V, M)(in V v, in M m) 706 if (isVec!(3, V) && isMat!(4, 4, M)) 707 { 708 return (m * vec(v, 1)).xyz; 709 } 710 /// ditto 711 auto transform(V, M)(in V v, in M m) 712 if (isVec!(3, V) && isMat!(3, 4, M)) 713 { 714 return m * vec(v, 1); 715 } 716 /// ditto 717 auto transform(V, M)(in V v, in M m) 718 if (isVec!(4, V) && isMat!(4, 4, M)) 719 { 720 return m * v; 721 } 722 723 unittest 724 { 725 import gfx.math.approx : approxUlp; 726 727 // 2x3 matrix can hold affine 2D transforms 728 immutable transl = DMat2x3( 729 1, 0, 3, 730 0, 1, 2, 731 ); 732 assert( approxUlp(transform(dvec(3, 5), transl), dvec(6, 7)) ); 733 } 734 735 /// 736 unittest 737 { 738 import gfx.math.approx : approxUlp, approxUlpAndAbs; 739 import std.math : PI; 740 741 immutable v = dvec(2, 0); 742 auto m = DMat2x3.identity; 743 744 m = m.rotate(PI/2); 745 assert ( approxUlpAndAbs(transform(v, m), dvec(0, 2)) ); 746 747 m = m.translate(2, 2); 748 assert ( approxUlp(transform(v, m), dvec(2, 4)) ); 749 750 m = m.scale(2, 2); 751 assert ( approxUlp(transform(v, m), dvec(4, 8)) ); 752 } 753 754 /// 755 unittest 756 { 757 import gfx.math.approx : approxUlp; 758 759 auto st = scale!float(2, 2).translate(3, 1); 760 assert( approxUlp(transform(fvec(0, 0), st), fvec(3, 1)) ); 761 assert( approxUlp(transform(fvec(1, 1), st), fvec(5, 3)) ); 762 763 auto ts = translation!float(3, 1).scale(2, 2); 764 assert( approxUlp(transform(fvec(0, 0), ts), fvec(6, 2)) ); 765 assert( approxUlp(transform(fvec(1, 1), ts), fvec(8, 4)) ); 766 }