1 module sbylib.math.matrix; 2 3 import sbylib.math; 4 import std.traits; 5 import std.range : isInputRange, RangeElementType = ElementType; 6 import std.format : format; 7 8 alias mat1x1 = Matrix!(float, 1, 1); 9 alias mat1x2 = Matrix!(float, 1, 2); 10 alias mat1x3 = Matrix!(float, 1, 3); 11 alias mat1x4 = Matrix!(float, 1, 4); 12 alias mat2x1 = Matrix!(float, 2, 1); 13 alias mat2x2 = Matrix!(float, 2, 2); 14 alias mat2x3 = Matrix!(float, 2, 3); 15 alias mat2x4 = Matrix!(float, 2, 4); 16 alias mat3x1 = Matrix!(float, 3, 1); 17 alias mat3x2 = Matrix!(float, 3, 2); 18 alias mat3x3 = Matrix!(float, 3, 3); 19 alias mat3x4 = Matrix!(float, 3, 4); 20 alias mat4x1 = Matrix!(float, 4, 1); 21 alias mat4x2 = Matrix!(float, 4, 2); 22 alias mat4x3 = Matrix!(float, 4, 3); 23 alias mat4x4 = Matrix!(float, 4, 4); 24 25 alias mat2 = mat2x2; 26 alias mat3 = mat3x3; 27 alias mat4 = mat4x4; 28 29 enum isMatrix(T) = isInstanceOf!(Matrix, T); 30 31 /** 32 Matrix type 33 */ 34 struct Matrix(T, uint U, uint V) 35 if (__traits(isArithmetic, T)) 36 { 37 private T[U*V] element; 38 39 /** 40 Row number of this matrix type 41 */ 42 enum Row = U; 43 44 /** 45 Column number of this matrix type 46 */ 47 enum Column = V; 48 49 /** 50 Element type of this matrix type 51 */ 52 alias ElementType = T; 53 54 alias array this; 55 56 /** 57 Constructor by element type. 58 This matrix's each element is filled by given value. 59 60 Params: 61 e = a value which fills the matrix 62 */ 63 this(T e) { 64 foreach (ref el; this.element) el = e; 65 } 66 67 unittest { 68 import std.random : uniform; 69 70 foreach (k; 0..100) { 71 const e = uniform(-1.0f, +1.0f); 72 const m = mat3(e); 73 static foreach (i; 0..3) { 74 static foreach (j; 0..3) { 75 assert(m[i,j] == e); 76 } 77 } 78 } 79 } 80 81 /** 82 Constructor by array. 83 This matrix's all elements are filled by given array. 84 85 Params: 86 elements = values which fills the matrix 87 */ 88 this(T[U*V] elements...) { 89 static foreach (i; 0..U*V) { 90 this[i/V,i%V] = elements[i]; 91 } 92 } 93 94 /** 95 Constructor by InputRange. 96 This matrix's all elements are filled by given range. 97 98 Params: 99 elements = values which fills the matrix 100 */ 101 this(Range)(Range r) 102 if (isInputRange!(Range) && is(RangeElementType!(Range) : T)) 103 { 104 import std.range : front, popFront, empty; 105 106 static foreach (i; 0..U*V) { 107 this.element[i] = r.front; 108 r.popFront; 109 } 110 assert(r.empty); 111 } 112 113 unittest { 114 import std.range : iota; 115 const a = mat2(iota(4)); 116 assert(a[0,0] == 0); 117 assert(a[1,0] == 1); 118 assert(a[0,1] == 2); 119 assert(a[1,1] == 3); 120 } 121 122 123 /** 124 Constructor by array of array. 125 This matrix's all elements are filled by given array. 126 127 Params: 128 elements = values which fills the matrix 129 */ 130 this(T[U][V] elements...) { 131 static foreach (i; 0..U) { 132 static foreach (j; 0..V) { 133 this[i,j] = elements[j][i]; 134 } 135 } 136 } 137 138 unittest { 139 const a = mat2([1,3],[2,4]); 140 assert(a[0,0] == 1); 141 assert(a[0,1] == 2); 142 assert(a[1,0] == 3); 143 assert(a[1,1] == 4); 144 } 145 146 static if (V > 1) { 147 /** 148 Constructor by array of vector. 149 This matrix's all elements are filled by given 150 Each vectors are dealed as column. 151 152 Params: 153 vectors = values which fills the matrix 154 */ 155 this(Vector!(T, U)[V] vectors...) { 156 static foreach (i; 0..U) { 157 static foreach (j; 0..V) { 158 this[i,j] = vectors[j][i]; 159 } 160 } 161 } 162 163 unittest { 164 const a = mat2(vec2(1,3),vec2(2,4)); 165 assert(a[0,0] == 1); 166 assert(a[0,1] == 2); 167 assert(a[1,0] == 3); 168 assert(a[1,1] == 4); 169 } 170 } 171 172 /** 173 Unary operation for "+". 174 175 this is identity function 176 177 Returns: a matrix which is equal to this matrix 178 */ 179 Matrix!(T,U,V) opUnary(string op)() const 180 if (op == "+") 181 { 182 return this; 183 } 184 185 unittest { 186 import std.algorithm : map; 187 import std.range : iota; 188 import std.random : uniform; 189 190 foreach (i; 0..100) { 191 const m = mat4(iota(16).map!(_ => uniform(-1.0f, +1.0f))); 192 assert(approxEqual(m * +1, +m)); 193 } 194 } 195 196 /** 197 Unary operation for "-". 198 199 Returns: a matrix whose sign is inverted 200 */ 201 Matrix!(T,U,V) opUnary(string op)() const 202 if (op == "-") 203 { 204 Matrix!(T,U,V) result; 205 static foreach (i; 0..U) { 206 static foreach (j; 0..V) { 207 result[i,j] = -this[i,j]; 208 } 209 } 210 return result; 211 } 212 213 unittest { 214 import std.algorithm : map; 215 import std.range : iota; 216 import std.random : uniform; 217 218 foreach (i; 0..100) { 219 const m = mat4(iota(16).map!(_ => uniform(-1.0f, +1.0f))); 220 assert(approxEqual(m * -1, -m)); 221 } 222 } 223 224 /** 225 Binary operator between the same size matrix ("+" or "-"). 226 Another type matrix is allowed. 227 228 Params: 229 m = target matrix 230 231 Returns: calculated matrix 232 */ 233 Matrix!(CommonType!(T,S), U,V) opBinary(string op, S)(Matrix!(S,U,V) m) const 234 if (op == "+" || op == "-") 235 { 236 Matrix!(CommonType!(T,S),U,V) result; 237 static foreach (i; 0..U) { 238 static foreach (j; 0..V) { 239 mixin(format!q{ 240 result[i,j] = this[i,j] %s m[i,j]; 241 }(op)); 242 } 243 } 244 return result; 245 } 246 247 unittest { 248 import std.algorithm : map; 249 import std.range : iota; 250 import std.random : uniform; 251 import std.math : approxEqual; 252 253 foreach (k; 0..100) { 254 const a = mat4(iota(16).map!(_ => uniform(-1.0f, +1.0f))); 255 const b = mat4(iota(16).map!(_ => uniform(-1.0f, +1.0f))); 256 257 const c = a + b; 258 const d = a - b; 259 260 static foreach (i; 0..4) { 261 static foreach (j; 0..4) { 262 assert(approxEqual(a[i,j] + b[i,j], c[i,j])); 263 assert(approxEqual(a[i,j] - b[i,j], d[i,j])); 264 } 265 } 266 } 267 } 268 269 /** 270 Multiply operator between matrix. 271 Another type matrix is allowed. 272 273 Params: 274 m = target matrix 275 276 Returns: calculated matrix 277 */ 278 Matrix!(CommonType!(T,S),U,P) opBinary(string op, S, uint P)(Matrix!(S,V,P) m) const 279 if (op == "*") 280 { 281 Matrix!(CommonType!(T,S),U,P) result; 282 static foreach (i; 0..U) { 283 static foreach (j; 0..P) { 284 result[i,j] = 0; 285 static foreach (k; 0..V) { 286 result[i,j] += this[i,k] * m[k,j]; 287 } 288 } 289 } 290 return result; 291 } 292 293 unittest { 294 import std.algorithm : map; 295 import std.range : iota; 296 import std.random : uniform; 297 298 foreach (i; 0..100) { 299 const a = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 300 const b = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 301 const c = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 302 assert(approxEqual((a * b) * c, a * (b * c))); 303 } 304 } 305 306 /** 307 Binary operation between scalar type ("*" or "/"). 308 309 Params: 310 e = target scalar value 311 312 Returns: calculated matrix 313 */ 314 Matrix!(CommonType!(T,S),U,V) opBinary(string op, S)(S e) const 315 if (__traits(isArithmetic, S) && (op == "*" || op == "/")) 316 { 317 Matrix!(CommonType!(T,S),U,V) result; 318 static foreach (i; 0..U) { 319 static foreach (j; 0..V) { 320 result[i,j] = mixin(format!q{ 321 this[i,j] %s e 322 }(op)); 323 } 324 } 325 return result; 326 } 327 328 unittest { 329 import std.algorithm : map; 330 import std.range : iota; 331 import std.random : uniform; 332 import std.math : approxEqual; 333 334 foreach (k; 0..100) { 335 const m = mat4(16.iota.map!(_ => uniform(-1.0f, +1.0f))); 336 const s = uniform(-1.0f, +1.0f); 337 const m2 = m * s; 338 const m3 = m / s; 339 static foreach (i; 0..4) { 340 static foreach (j; 0..4) { 341 assert(approxEqual(m[i,j] * s, m2[i,j])); 342 assert(approxEqual(m[i,j] / s, m3[i,j])); 343 } 344 } 345 } 346 } 347 348 /** 349 Binary operation between vector type ("*" only). 350 351 Params: 352 e = target scalar value 353 354 Returns: calculated vector 355 */ 356 Vector!(CommonType!(T,S),U) opBinary(string op, S)(Vector!(S,V) v) const 357 if (op == "*") 358 { 359 Vector!(CommonType!(T,S),U) result; 360 static foreach (i; 0..U) { 361 result[i] = 0; 362 static foreach (j; 0..V) { 363 result[i] += this[i,j] * v[j]; 364 } 365 } 366 return result; 367 } 368 369 unittest { 370 import std.algorithm : map; 371 import std.range : iota; 372 import std.random : uniform; 373 import sbylib.math.vector : dot, approxEqual; 374 foreach (i; 0..100) { 375 const m = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 376 const v = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 377 const v2 = m * v; 378 379 assert(approxEqual(vec3(dot(m.row[0], v), dot(m.row[1], v), dot(m.row[2], v)), v2)); 380 } 381 } 382 383 /** 384 operator assign between matrix type ("+" or "-") 385 386 Params: 387 m = target matrix 388 389 Returns: calculated this matrix 390 */ 391 Matrix opOpAssign(string op, S)(Matrix!(S,U,V) m) 392 if (op == "+" || op == "-") 393 { 394 static foreach (i; 0..U) { 395 static foreach (j; 0..V) { 396 mixin(format!q{ 397 this[i,j] %s= m[i,j]; 398 }(op)); 399 } 400 } 401 return this; 402 } 403 404 unittest { 405 import std.algorithm : map; 406 import std.range : iota; 407 import std.random : uniform; 408 409 foreach (i; 0..100) { 410 auto m = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 411 const m2 = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 412 const m3 = m + m2; 413 m += m2; 414 415 assert(approxEqual(m, m3)); 416 } 417 } 418 419 /** 420 operator assign between matrix type (only "*") 421 422 Params: 423 m = target matrix 424 425 Returns: calculated this matrix 426 */ 427 Matrix opOpAssign(string op, S)(Matrix!(S,U,V) m) 428 if (op == "*") 429 { 430 Matrix result = this * m; 431 static foreach (i; 0..U) { 432 static foreach (j; 0..V) { 433 this[i,j] = result[i,j]; 434 } 435 } 436 return this; 437 } 438 439 unittest { 440 import std.algorithm : map; 441 import std.range : iota; 442 import std.random : uniform; 443 foreach (i; 0..100) { 444 auto m = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 445 const m2 = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 446 const m3 = m * m2; 447 m *= m2; 448 449 assert(approxEqual(m, m3)); 450 } 451 } 452 453 /** 454 operator assign between scalar type ("*" or "/") 455 456 Params: 457 e = target scalar value 458 459 Returns: calculated this matrix 460 */ 461 Matrix opOpAssign(string op, S)(S e) 462 if (__traits(isArithmetic, S) && (op == "*" || op == "/")) 463 { 464 static foreach (i; 0..U) { 465 static foreach (j; 0..V) { 466 mixin(format!q{ 467 this[i,j] %s= e; 468 }(op)); 469 } 470 } 471 return this; 472 } 473 474 unittest { 475 import std.algorithm : map; 476 import std.range : iota; 477 import std.random : uniform; 478 479 foreach (i; 0..100) { 480 auto m = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 481 const s = uniform(-1.0f, +1.0f); 482 const m2 = m * s; 483 m *= s; 484 485 assert(approxEqual(m, m2)); 486 } 487 } 488 489 /** 490 indexing operation 491 492 Params: 493 i = column index 494 j = row index 495 496 Returns: selected value 497 */ 498 T opIndex(size_t i, size_t j) const 499 in (i < U && j < V) 500 { 501 return element[i+j*U]; 502 } 503 504 unittest { 505 import std.algorithm : map; 506 import std.range : iota; 507 import std.random : uniform; 508 509 foreach (k; 0..100) { 510 vec3[3] v; 511 static foreach (i; 0..3) { 512 v[i] = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 513 } 514 const m = mat3(v); 515 516 static foreach (i; 0..3) { 517 static foreach (j; 0..3) { 518 assert(m[i,j] == v[j][i]); 519 } 520 } 521 } 522 } 523 524 /** 525 indexing operation 526 527 Params: 528 i = column index 529 j = row index 530 531 Returns: selected value 532 */ 533 ref T opIndex(size_t i, size_t j) 534 in(i < U && j < V) 535 { 536 return element[i+j*U]; 537 } 538 539 unittest { 540 import std.algorithm : map; 541 import std.range : iota; 542 import std.random : uniform; 543 544 foreach (k; 0..100) { 545 mat3 m; 546 547 static foreach (i; 0..3) {{ 548 static foreach (j; 0..3) {{ 549 const v = uniform(-1.0f, +1.0f); 550 m[i,j] = v; 551 assert(m[i,j] == v); 552 }} 553 }} 554 } 555 } 556 557 /** 558 Get raw array data. 559 560 Returns: raw array data 561 */ 562 T[U*V] array() inout { 563 return element; 564 } 565 566 unittest { 567 import std.range : iota; 568 const a = mat2(iota(4)); 569 assert(a.array == [0,1,2,3]); 570 } 571 572 /** 573 Get column array as array of vector 574 575 Returns: column array 576 */ 577 Vector!(T,U)[V] column() const { 578 Vector!(T,U)[V] result; 579 static foreach (j; 0..V) { 580 static foreach (i; 0..U) { 581 result[j][i] = this[i,j]; 582 } 583 } 584 return result; 585 } 586 587 unittest { 588 import std.range : iota; 589 const a = mat2(iota(4)); 590 assert(a.column[0] == vec2(0,1)); 591 assert(a.column[1] == vec2(2,3)); 592 } 593 594 /** 595 Get row array as array of vector 596 597 Returns: row array 598 */ 599 Vector!(T,V)[U] row() const { 600 Vector!(T,V)[U] result; 601 static foreach (i; 0..U) { 602 static foreach (j; 0..V) { 603 result[i][j] = this[i,j]; 604 } 605 } 606 return result; 607 } 608 609 unittest { 610 import std.range : iota; 611 const a = mat2(iota(4)); 612 assert(a.row[0] == vec2(0,2)); 613 assert(a.row[1] == vec2(1,3)); 614 } 615 616 /** 617 Converts to string 618 619 Returns: string representation of this matrix 620 */ 621 string toString() const { 622 string res; 623 static foreach (i; 0..U) { 624 res ~= "\n\t"; 625 static foreach (j; 0..V) { 626 static if (__traits(isFloating, T)) 627 res ~= format!"%10f,"(this[i,j]); 628 else 629 res ~= format!"%d,"(this[i,j]); 630 } 631 } 632 return res; 633 } 634 635 static if (U == V) { 636 /** 637 Returns identity matrix. 638 639 Returns: identity matrix 640 */ 641 static Matrix identity() { 642 Matrix result; 643 static foreach (i; 0..U) { 644 static foreach (j; 0..U) { 645 static if (i == j) 646 result[i,j] = 1; 647 else 648 result[i,j] = 0; 649 } 650 } 651 return result; 652 } 653 654 unittest { 655 import std.algorithm : map; 656 import std.range : iota; 657 import std.random : uniform; 658 import sbylib.math.vector : approxEqual; 659 660 foreach (i; 0..100) { 661 const v2 = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 662 assert(approxEqual(v2, mat2.identity * v2)); 663 664 const v3 = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 665 assert(approxEqual(v3, mat3.identity * v3)); 666 667 const v4 = vec4(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 668 assert(approxEqual(v4, mat4.identity * v4)); 669 } 670 } 671 672 /** 673 Returns scale matrix. 674 675 Params: 676 vec = scale vector 677 678 Returns: scale matrix 679 */ 680 static Matrix scale(const Vector!(T,U) vec) { 681 Matrix result; 682 static foreach (i; 0..U) { 683 static foreach (j; 0..U) { 684 static if (i == j) 685 result[i,j] = vec[i]; 686 else 687 result[i,j] = 0; 688 } 689 } 690 return result; 691 } 692 693 unittest { 694 import std.algorithm : map; 695 import std.range : iota; 696 import std.random : uniform; 697 import sbylib.math.vector : approxEqual; 698 699 foreach (i; 0..100) { 700 const v2 = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 701 const s2 = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 702 assert(approxEqual(v2 * s2, mat2.scale(s2) * v2)); 703 704 const v3 = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 705 const s3 = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 706 assert(approxEqual(v3 * s3, mat3.scale(s3) * v3)); 707 } 708 } 709 static if (U == 3 || U == 4) { 710 /** 711 Returns translation matrix. 712 713 Params: 714 vec = translation vector 715 716 Returns: translation matrix 717 */ 718 static Matrix translate(Vector!(T,U-1) vec) { 719 Matrix result; 720 static foreach (i; 0..U) { 721 static foreach (j; 0..U) { 722 static if (i == j) { 723 result[i,j] = 1; 724 } else static if (j == U-1) { 725 static if (i < U-1) { 726 result[i,j] = vec[i]; 727 } else { 728 result[i,j] = 1; 729 } 730 } else { 731 result[i,j] = 0; 732 } 733 } 734 } 735 return result; 736 } 737 738 unittest { 739 import std.algorithm : map; 740 import std.range : iota; 741 import std.random : uniform; 742 import sbylib.math.vector : approxEqual; 743 744 foreach (i; 0..100) { 745 const p2 = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 746 const t2 = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 747 assert(approxEqual(p2 + t2, (mat3.translate(t2) * vec3(p2,1)).xy)); 748 749 const p3 = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 750 const t3 = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 751 assert(approxEqual(p3 + t3, (mat4.translate(t3) * vec4(p3,1)).xyz)); 752 } 753 } 754 755 } 756 static if (U == 3) { 757 /** 758 Returns rotation matrix decided by its rotation axis and angle. 759 760 Params: 761 axis = rotation axis vector, which must be normalized 762 angle = rotation angle 763 764 Returns: rotation matrix 765 */ 766 static Matrix axisAngle(const Vector!(T,3) axis, const Angle angle) { 767 const c = cos(angle); 768 const s = sin(angle); 769 Matrix result; 770 static foreach (i; 0..3) { 771 static foreach (j; 0..3) { 772 static if (i == j) { 773 result[i,j] = axis[i]*axis[j]*(1-c)+c; 774 } else static if (j == (i+1)%3) { 775 result[i,j] = axis[i]*axis[j]*(1-c)+axis[(i+2)%3]*s; 776 } else { 777 result[i,j] = axis[i]*axis[j]*(1-c)-axis[(i+1)%3]*s; 778 } 779 } 780 } 781 return result; 782 } 783 784 unittest { 785 import std.algorithm : map; 786 import std.random : uniform; 787 import std.range : iota; 788 import std.math : abs; 789 import sbylib.math.vector : dot; 790 791 foreach (i; 0..100) { 792 const axis = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 793 auto point = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 794 point = normalize(point - dot(point, axis) * axis); 795 const r = mat3.axisAngle(axis, 90.deg); 796 const point2 = r * point; 797 assert(abs(dot(point, point2)) < 1e-5); 798 } 799 } 800 801 /** 802 Returns rotation matrix which converts a vector to another vector. 803 804 Params: 805 from = before vector, which must be normalized 806 to = after vector, which must be normalized 807 808 Returns: rotation matrix 809 */ 810 static Matrix rotFromTo(const Vector!(T,3) from, const Vector!(T,3) to) { 811 import std.algorithm : clamp; 812 813 const v = cross(from, to).normalize; 814 const c = dot(from, to); 815 816 if (v.hasNaN) { 817 if (c > 0) return Matrix.identity; 818 else return Matrix.scale(vec3(-1)); 819 } 820 const angle = -acos(clamp(c, -1, +1)); 821 return axisAngle(v, angle); 822 } 823 824 unittest { 825 import std.algorithm : map; 826 import std.random : uniform; 827 import std.range : iota; 828 import sbylib.math.vector : approxEqual; 829 830 foreach (i; 0..100) { 831 const a = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 832 const b = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 833 const c = rotFromTo(a,b) * a; 834 assert(approxEqual(b,c), format!"b = %s, b' = %s"(b,c)); 835 } 836 } 837 838 /** 839 Returns view transform matrix. 840 841 Params: 842 vec = backward vector 843 up = up vector 844 845 Returns: view transformation matrix 846 */ 847 static Matrix lookAt(const Vector!(T,3) vec, const Vector!(T,3) up) { 848 const side = normalize(cross(up, vec)); 849 const up2 = normalize(cross(vec, side)); 850 return Matrix(side, up2, vec); 851 } 852 853 } else static if (U == 4) { 854 855 /** 856 Returns rotation matrix decided by its rotation axis and angle. 857 858 Params: 859 axis = rotation axis vector, which must be normalized 860 angle = rotation angle 861 862 Returns: rotation matrix 863 */ 864 static Matrix axisAngle(const Vector!(T,3) v, const Angle angle) { 865 return Matrix!(T,3,3).axisAngle(v, angle).toMatrix4(); 866 } 867 868 unittest { 869 import std.algorithm : map; 870 import std.random : uniform; 871 import std.range : iota; 872 import std.math : abs; 873 import sbylib.math.vector : dot; 874 foreach (i; 0..100) { 875 const axis = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 876 auto point = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 877 point = normalize(point - dot(point, axis) * axis); 878 const r = mat4.axisAngle(axis, 90.deg); 879 const point2 = (r * vec4(point, 1)).xyz; 880 assert(abs(dot(point, point2)) < 1e-5); 881 } 882 } 883 884 /** 885 Returns rotation matrix which converts a vector to another vector. 886 887 Params: 888 from = before vector, which must be normalized 889 to = after vector, which must be normalized 890 891 Returns: rotation matrix 892 */ 893 static Matrix rotFromTo(const Vector!(T,3) from, const Vector!(T,3) to) { 894 return Matrix!(T,3,3).rotFromTo(from, to).toMatrix4(); 895 } 896 897 unittest { 898 import std.algorithm : map; 899 import std.random : uniform; 900 import std.range : iota; 901 import sbylib.math.vector : approxEqual; 902 903 foreach (i; 0..100) { 904 const a = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 905 const b = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 906 const c = (mat4.rotFromTo(a,b) * vec4(a,1)).xyz; 907 assert(approxEqual(b,c), format!"b = %s, b' = %s"(b,c)); 908 } 909 } 910 911 /** 912 Returns view transform matrix. 913 914 Params: 915 eye = point of view 916 vec = backward vector 917 up = up vector 918 919 Returns: view transformation matrix 920 */ 921 static Matrix lookAt(const Vector!(T,3) eye, const Vector!(T,3) vec, const Vector!(T,3) up) { 922 const side = normalize(cross(up, vec)); 923 const up2 = normalize(cross(vec, side)); 924 return Matrix(Vector!(T,4)(side, 0), Vector!(T,4)(up2, 0), Vector!(T,4)(vec, 0), 925 Vector!(T,4)(-dot(eye,side),-dot(eye,up2),-dot(eye,vec),1)); 926 } 927 928 /** 929 Returns orthographic projection transform matrix. 930 931 Params: 932 width = width of orthographic view area 933 height = height of orthographic view area 934 nearZ = near side z value of orthographic view area 935 farZ = far side z value of orthographic view area 936 937 Returns: orthographic projection transformation matrix 938 */ 939 static Matrix ortho(T width, T height, T nearZ, T farZ) { 940 return Matrix( 941 2 / width, 0, 0, 0, 942 0, 2 / height, 0, 0, 943 0, 0, 1 / (nearZ - farZ), nearZ / (nearZ - farZ), 944 0, 0, 0, 1 945 ); 946 } 947 948 /** 949 Returns perspective projection transform matrix. 950 951 Params: 952 aspectWperH = bottom side aspect retio 953 fovy = FOV of y direction 954 nearZ = near side z value of orthographic view area 955 farZ = far side z value of orthographic view area 956 957 Returns: perspective projection transform matrix 958 */ 959 static Matrix perspective(T aspectWperH, Angle fovy, T nearZ, T farZ) { 960 return Matrix( 961 1 / (aspectWperH * tan(fovy/2)), 0, 0, 0, 962 0, 1 / (tan(fovy/2)), 0, 0, 963 0, 0, (nearZ+farZ)/(nearZ-farZ), 2 * nearZ * farZ / (nearZ - farZ), 964 0, 0, -1, 0); 965 } 966 967 /** 968 Returns TRS transform matrix. 969 970 Params: 971 pos = translation vector, which represents T 972 rot = rotation matrix, which represents R 973 scale = scale vector, which represents S 974 975 Returns: perspective projection transform matrix 976 */ 977 static Matrix makeTRS(const Vector!(T,3) trans, const Matrix!(T,3,3) rot, const Vector!(T,3) scale) { 978 return Matrix( 979 vec4(scale[0] * rot.column[0],0), 980 vec4(scale[1] * rot.column[1],0), 981 vec4(scale[2] * rot.column[2],0), 982 vec4(trans,1)); 983 } 984 985 unittest { 986 import std.algorithm : map; 987 import std.random : uniform; 988 import std.range : iota; 989 import sbylib.math.vector : approxEqual; 990 991 foreach (i; 0..100) { 992 const p = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 993 const t = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 994 const r = mat3.axisAngle(vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize, 995 uniform(0,180.0).deg); 996 const s = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 997 998 assert(approxEqual((mat4.makeTRS(t,r,s) * vec4(p,1)).xyz, r * (s * p) + t)); 999 } 1000 } 1001 1002 /** 1003 Returns inverse TRS transform matrix. 1004 1005 Params: 1006 pos = translation vector, which represents T 1007 rot = rotation matrix, which represents R 1008 scale = scale vector, which represents S 1009 1010 Returns: perspective projection transform matrix 1011 */ 1012 static Matrix makeInvertTRS(const Vector!(T,3) pos, const Matrix!(T,3,3) rot, const Vector!(T, 3) scale) { 1013 return Matrix( 1014 rot[0,0] / scale[0], rot[1,0] / scale[0], rot[2,0] / scale[0], -dot(rot.column[0], pos) / scale[0], 1015 rot[0,1] / scale[1], rot[1,1] / scale[1], rot[2,1] / scale[1], -dot(rot.column[1], pos) / scale[1], 1016 rot[0,2] / scale[2], rot[1,2] / scale[2], rot[2,2] / scale[2], -dot(rot.column[2], pos) / scale[2], 1017 0,0,0, 1); 1018 } 1019 1020 unittest { 1021 import std.algorithm : map; 1022 import std.random : uniform; 1023 import std.range : iota; 1024 1025 foreach (i; 0..100) { 1026 const t = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1027 const r = mat3.axisAngle(vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize, uniform(0,180.0).deg); 1028 const s = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1029 1030 assert(approxEqual(mat4.makeTRS(t,r,s) * mat4.makeInvertTRS(t,r,s), mat4.identity)); 1031 } 1032 } 1033 } 1034 } 1035 } 1036 1037 /** 1038 Returns transposed matrix. 1039 1040 Params: 1041 m = target matrix 1042 1043 Returns: transposed matrix 1044 */ 1045 Matrix!(T,V,U) transpose(T, uint U, uint V)(const Matrix!(T,U,V) m) { 1046 Matrix!(T,V,U) r; 1047 static foreach (i;0..V) { 1048 static foreach (j;0..U) { 1049 r[i,j] = m[j,i]; 1050 } 1051 } 1052 return r; 1053 } 1054 1055 unittest { 1056 assert(approxEqual(transpose(mat2x3(1,2,3, 4,5,6)), mat3x2(1,4, 2,5, 3,6))); 1057 } 1058 1059 /** 1060 Returns shrinked 3x3 matrix 1061 The last row and column of the target are removed. 1062 1063 Params: 1064 m = target matrix 1065 1066 Returns: 3x3 matrix 1067 */ 1068 Matrix!(T,3,3) toMatrix3(T)(const Matrix!(T,4,4) m) { 1069 return Matrix!(T,3,3)(m.column[0].xyz, m.column[1].xyz, m.column[2].xyz); 1070 } 1071 1072 unittest { 1073 import std.algorithm : map; 1074 import std.random : uniform; 1075 import std.range : iota; 1076 import sbylib.math.vector : approxEqual; 1077 1078 foreach (i; 0..100) { 1079 const p = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1080 const m = mat4(16.iota.map!(_ => uniform(-1.0f, +1.0f))); 1081 1082 assert(approxEqual((m * vec4(p,0)).xyz, m.toMatrix3() * p)); 1083 } 1084 } 1085 1086 /** 1087 Returns expanded 4x4 matrix 1088 The last row and column of the result matrix are almost 0, but 3,3 element is 1. 1089 1090 Params: 1091 m = target matrix 1092 1093 Returns: 4x4 matrix 1094 */ 1095 Matrix!(T,4,4) toMatrix4(T)(const Matrix!(T,3,3) m) { 1096 Matrix!(T,4,4) result; 1097 static foreach (i; 0..3) { 1098 static foreach (j; 0..3) { 1099 result[i,j] = m[i,j]; 1100 } 1101 } 1102 static foreach (i; 0..3) { 1103 result[i,3] = 0; 1104 result[3,i] = 0; 1105 } 1106 result[3,3] = 1; 1107 return result; 1108 } 1109 1110 unittest { 1111 import std.algorithm : map; 1112 import std.random : uniform; 1113 import std.range : iota; 1114 import sbylib.math.vector : approxEqual; 1115 1116 foreach (i; 0..100) { 1117 const p = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1118 const m = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 1119 1120 assert(approxEqual(m * p, (m.toMatrix4() * vec4(p,1)).xyz)); 1121 } 1122 } 1123 1124 /** 1125 Converts to quaternion. 1126 1127 Returns: converted quaternion 1128 */ 1129 Quaternion!T toQuaternion(T)(const Matrix!(T,3,3) m) { 1130 import std.math : sqrt, sgn; 1131 1132 auto q0 = ( m[0,0] + m[1,1] + m[2,2] + 1.0f) / 4.0f; 1133 auto q1 = ( m[0,0] - m[1,1] - m[2,2] + 1.0f) / 4.0f; 1134 auto q2 = (-m[0,0] + m[1,1] - m[2,2] + 1.0f) / 4.0f; 1135 auto q3 = (-m[0,0] - m[1,1] + m[2,2] + 1.0f) / 4.0f; 1136 if(q0 < 0.0f) q0 = 0.0f; 1137 if(q1 < 0.0f) q1 = 0.0f; 1138 if(q2 < 0.0f) q2 = 0.0f; 1139 if(q3 < 0.0f) q3 = 0.0f; 1140 q0 = sqrt(q0); 1141 q1 = sqrt(q1); 1142 q2 = sqrt(q2); 1143 q3 = sqrt(q3); 1144 if(q0 >= q1 && q0 >= q2 && q0 >= q3) { 1145 q1 *= sgn(m[2,1] - m[1,2]); 1146 q2 *= sgn(m[0,2] - m[2,0]); 1147 q3 *= sgn(m[1,0] - m[0,1]); 1148 } else if(q1 >= q0 && q1 >= q2 && q1 >= q3) { 1149 q0 *= sgn(m[2,1] - m[1,2]); 1150 q2 *= sgn(m[1,0] + m[0,1]); 1151 q3 *= sgn(m[0,2] + m[2,0]); 1152 } else if(q2 >= q0 && q2 >= q1 && q2 >= q3) { 1153 q0 *= sgn(m[0,2] - m[2,0]); 1154 q1 *= sgn(m[1,0] + m[0,1]); 1155 q3 *= sgn(m[2,1] + m[1,2]); 1156 } else if(q3 >= q0 && q3 >= q1 && q3 >= q2) { 1157 q0 *= sgn(m[1,0] - m[0,1]); 1158 q1 *= sgn(m[2,0] + m[0,2]); 1159 q2 *= sgn(m[2,1] + m[1,2]); 1160 } else { 1161 assert(false); 1162 } 1163 1164 auto result = Quaternion!T(q1, q2, q3, q0); 1165 return result.normalize; 1166 } 1167 1168 unittest { 1169 import std.algorithm : map; 1170 import std.random : uniform; 1171 import std.range : iota; 1172 import sbylib.math.vector : approxEqual; 1173 import sbylib.math.quaternion : rotate; 1174 1175 foreach (i; 0..100) { 1176 const p = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1177 const r = mat3.axisAngle(vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize, 1178 uniform(0,180.0).deg); 1179 1180 assert(approxEqual(r * p, rotate(r.toQuaternion(), p), 1e-3)); 1181 } 1182 } 1183 1184 /** 1185 Get diagonal component from matrix 1186 1187 Returns: diagonal vector of the matrix 1188 */ 1189 Vector!(T,U) diagonal(T,uint U)(const Matrix!(T,U,U) m) { 1190 Vector!(T,U) result; 1191 static foreach (i; 0..U) { 1192 result[i] = m[i,i]; 1193 } 1194 return result; 1195 } 1196 1197 unittest { 1198 import std.range : iota; 1199 import sbylib.math.vector : approxEqual; 1200 1201 assert(approxEqual(diagonal(mat3(iota(9))), vec3(0,4,8))); 1202 } 1203 1204 /** 1205 Get translation information from matrix 1206 1207 Returns: translation vector for the matrix 1208 */ 1209 Vector!(T,U-1) getTranslation(T,uint U)(const Matrix!(T,U,U) m) 1210 if (U > 0) 1211 { 1212 Vector!(T,U-1) result; 1213 static foreach (i; 0..U-1) { 1214 result[i] = m[i,U-1]; 1215 } 1216 return result; 1217 } 1218 1219 unittest { 1220 import std.algorithm : map; 1221 import std.random : uniform; 1222 import std.range : iota; 1223 import sbylib.math.vector : approxEqual; 1224 1225 foreach (i; 0..100) { 1226 const t = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1227 const r = mat3.identity(); 1228 const s = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1229 1230 const m = mat4.makeTRS(t,r,s); 1231 1232 assert(approxEqual(diagonal(m).xyz, s)); 1233 assert(approxEqual(getTranslation(m), t)); 1234 } 1235 } 1236 1237 /** 1238 Get the determinant value of 2x2 matrix 1239 1240 Params: 1241 m = target matrix 1242 1243 Returns: determinant value of the target matrix 1244 */ 1245 T determinant(T)(const Matrix!(T,2,2) m) { 1246 return m[0,0]*m[1,1] - m[0,1]*m[1,0]; 1247 } 1248 1249 /** 1250 Get the determinant value of 3x3 matrix 1251 1252 Params: 1253 m = target matrix 1254 1255 Returns: determinant value of the target matrix 1256 */ 1257 T determinant(T)(const Matrix!(T,3,3) m) { 1258 return 1259 + m[0,0]*m[1,1]*m[2,2] 1260 + m[0,1]*m[1,2]*m[2,0] 1261 + m[0,2]*m[1,0]*m[2,1] 1262 - m[0,0]*m[1,2]*m[2,1] 1263 - m[0,1]*m[1,0]*m[2,2] 1264 - m[0,2]*m[1,1]*m[2,0]; 1265 } 1266 1267 /** 1268 Get the determinant value of 4x4 matrix 1269 1270 Params: 1271 m = target matrix 1272 1273 Returns: determinant value of the target matrix 1274 */ 1275 T determinant(T)(const Matrix!(T,4,4) m) { 1276 const e2233_2332 = m[2,2] * m[3,3] - m[2,3] * m[3,2]; 1277 const e2133_2331 = m[2,1] * m[3,3] - m[2,3] * m[3,1]; 1278 const e2132_2231 = m[2,1] * m[3,2] - m[2,2] * m[3,1]; 1279 const e2033_2330 = m[2,0] * m[3,3] - m[2,3] * m[3,0]; 1280 const e2032_2230 = m[2,0] * m[3,2] - m[2,2] * m[3,0]; 1281 const e2031_2130 = m[2,0] * m[3,1] - m[2,1] * m[3,0]; 1282 return 1283 m[0,0] * (m[1,1] * e2233_2332 - m[1,2] * e2133_2331 + m[1,3] * e2132_2231) - 1284 m[0,1] * (m[1,0] * e2233_2332 - m[1,2] * e2033_2330 + m[1,3] * e2032_2230) + 1285 m[0,2] * (m[1,0] * e2133_2331 - m[1,1] * e2033_2330 + m[1,3] * e2031_2130) - 1286 m[0,3] * (m[1,0] * e2132_2231 - m[1,1] * e2032_2230 + m[1,2] * e2031_2130) 1287 ; 1288 } 1289 1290 /** 1291 Get 2x2 inverse matrix 1292 1293 Params: 1294 m = target matrix 1295 1296 Returns: inverse matrix of the target matrix 1297 */ 1298 Matrix!(T,2,2) invert(T)(const Matrix!(T,2,2) m) { 1299 auto det = determinant(m); 1300 if (det != 0) det = 1 / det; 1301 Matrix!(T,2,2) r; 1302 r[0,0] = +m[1,1] * det; 1303 r[0,1] = -m[0,1] * det; 1304 r[1,0] = -m[1,0] * det; 1305 r[1,1] = +m[0,0] * det; 1306 return r; 1307 } 1308 1309 unittest { 1310 import std.algorithm : map; 1311 import std.range : iota; 1312 import std.random : uniform; 1313 foreach (i; 0..100) { 1314 const m = mat2(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 1315 assert(determinant(m) == 0 || approxEqual(m * invert(m), mat2.identity, 1e-3)); 1316 } 1317 } 1318 1319 /** 1320 Get 3x3 inverse matrix 1321 1322 Params: 1323 m = target matrix 1324 1325 Returns: inverse matrix of the target matrix 1326 */ 1327 Matrix!(T,3,3) invert(T)(const Matrix!(T,3,3) m) { 1328 auto det = determinant(m); 1329 if (det != 0) det = 1 / det; 1330 Matrix!(T,3,3) r; 1331 r[0,0] = (m[1,1]*m[2,2] - m[1,2]*m[2,1]) * det; 1332 r[0,1] = (m[0,2]*m[2,1] - m[0,1]*m[2,2]) * det; 1333 r[0,2] = (m[0,1]*m[1,2] - m[0,2]*m[1,1]) * det; 1334 r[1,0] = (m[1,2]*m[2,0] - m[1,0]*m[2,2]) * det; 1335 r[1,1] = (m[0,0]*m[2,2] - m[0,2]*m[2,0]) * det; 1336 r[1,2] = (m[0,2]*m[1,0] - m[0,0]*m[1,2]) * det; 1337 r[2,0] = (m[1,0]*m[2,1] - m[1,1]*m[2,0]) * det; 1338 r[2,1] = (m[0,1]*m[2,0] - m[0,0]*m[2,1]) * det; 1339 r[2,2] = (m[0,0]*m[1,1] - m[0,1]*m[1,0]) * det; 1340 return r; 1341 } 1342 1343 unittest { 1344 import std.algorithm : map; 1345 import std.range : iota; 1346 import std.random : uniform; 1347 foreach (i; 0..100) { 1348 const m = mat3(9.iota.map!(_ => uniform(-1.0f, +1.0f))); 1349 assert(determinant(m) == 0 || approxEqual(m * invert(m), mat3.identity, 1e-3)); 1350 } 1351 } 1352 1353 /** 1354 Get 4x4 inverse matrix 1355 1356 Params: 1357 m = target matrix 1358 1359 Returns: inverse matrix of the target matrix 1360 */ 1361 Matrix!(T,4,4) invert(T)(const Matrix!(T,4,4) m) { 1362 auto det = determinant(m); 1363 if (det != 0) det = 1 / det; 1364 const e2233_2332 = m[2,2] * m[3,3] - m[2,3] * m[3,2]; 1365 const e2133_2331 = m[2,1] * m[3,3] - m[2,3] * m[3,1]; 1366 const e2132_2231 = m[2,1] * m[3,2] - m[2,2] * m[3,1]; 1367 const e1233_1332 = m[1,2] * m[3,3] - m[1,3] * m[3,2]; 1368 const e1133_1331 = m[1,1] * m[3,3] - m[1,3] * m[3,1]; 1369 const e1132_1231 = m[1,1] * m[3,2] - m[1,2] * m[3,1]; 1370 const e1322_1223 = m[1,3] * m[2,2] - m[1,2] * m[2,3]; 1371 const e1123_1321 = m[1,1] * m[2,3] - m[1,3] * m[2,1]; 1372 const e1122_1221 = m[1,1] * m[2,2] - m[1,2] * m[2,1]; 1373 const e2033_2330 = m[2,0] * m[3,3] - m[2,3] * m[3,0]; 1374 const e2032_2230 = m[2,0] * m[3,2] - m[2,2] * m[3,0]; 1375 const e1033_1330 = m[1,0] * m[3,3] - m[1,3] * m[3,0]; 1376 const e1032_1230 = m[1,0] * m[3,2] - m[1,2] * m[3,0]; 1377 const e1023_1320 = m[1,0] * m[2,3] - m[1,3] * m[2,0]; 1378 const e1022_1220 = m[1,0] * m[2,2] - m[1,2] * m[2,0]; 1379 const e2031_2130 = m[2,0] * m[3,1] - m[2,1] * m[3,0]; 1380 const e1031_1130 = m[1,0] * m[3,1] - m[1,1] * m[3,0]; 1381 const e1021_1120 = m[1,0] * m[2,1] - m[1,1] * m[2,0]; 1382 const t00 = m[1,1] * e2233_2332 - m[1,2] * e2133_2331 + m[1,3] * e2132_2231; 1383 const t01 = -m[0,1] * e2233_2332 + m[0,2] * e2133_2331 - m[0,3] * e2132_2231; 1384 const t02 = m[0,1] * e1233_1332 - m[0,2] * e1133_1331 + m[0,3] * e1132_1231; 1385 const t03 = m[0,1] * e1322_1223 + m[0,2] * e1123_1321 - m[0,3] * e1122_1221; 1386 const t10 = -m[1,0] * e2233_2332 + m[1,2] * e2033_2330 - m[1,3] * e2032_2230; 1387 const t11 = m[0,0] * e2233_2332 - m[0,2] * e2033_2330 + m[0,3] * e2032_2230; 1388 const t12 = -m[0,0] * e1233_1332 + m[0,2] * e1033_1330 - m[0,3] * e1032_1230; 1389 const t13 = -m[0,0] * e1322_1223 - m[0,2] * e1023_1320 + m[0,3] * e1022_1220; 1390 const t20 = m[1,0] * e2133_2331 - m[1,1] * e2033_2330 + m[1,3] * e2031_2130; 1391 const t21 = -m[0,0] * e2133_2331 + m[0,1] * e2033_2330 - m[0,3] * e2031_2130; 1392 const t22 = m[0,0] * e1133_1331 - m[0,1] * e1033_1330 + m[0,3] * e1031_1130; 1393 const t23 = -m[0,0] * e1123_1321 + m[0,1] * e1023_1320 - m[0,3] * e1021_1120; 1394 const t30 = -m[1,0] * e2132_2231 + m[1,1] * e2032_2230 - m[1,2] * e2031_2130; 1395 const t31 = m[0,0] * e2132_2231 - m[0,1] * e2032_2230 + m[0,2] * e2031_2130; 1396 const t32 = -m[0,0] * e1132_1231 + m[0,1] * e1032_1230 - m[0,2] * e1031_1130; 1397 const t33 = m[0,0] * e1122_1221 - m[0,1] * e1022_1220 + m[0,2] * e1021_1120; 1398 Matrix!(T,4,4) r; 1399 r[0,0] = det * t00; 1400 r[0,1] = det * t01; 1401 r[0,2] = det * t02; 1402 r[0,3] = det * t03; 1403 r[1,0] = det * t10; 1404 r[1,1] = det * t11; 1405 r[1,2] = det * t12; 1406 r[1,3] = det * t13; 1407 r[2,0] = det * t20; 1408 r[2,1] = det * t21; 1409 r[2,2] = det * t22; 1410 r[2,3] = det * t23; 1411 r[3,0] = det * t30; 1412 r[3,1] = det * t31; 1413 r[3,2] = det * t32; 1414 r[3,3] = det * t33; 1415 return r; 1416 } 1417 1418 unittest { 1419 import std.algorithm : map; 1420 import std.range : iota; 1421 import std.random : uniform; 1422 foreach (i; 0..100) { 1423 const m = mat4(16.iota.map!(_ => uniform(-1.0f, +1.0f))); 1424 assert(determinant(m) == 0 || approxEqual(m * invert(m), mat4.identity, 1e-3)); 1425 } 1426 } 1427 1428 /** 1429 Calculate the diagonalize matrix of the target matrix. 1430 1431 Params: 1432 m = target matrix, which must be real symmetric 1433 1434 Returns: the diagonalize matrix 1435 */ 1436 Matrix!(T,U,U) diagonalizeForRealSym(T, uint U)(Matrix!(T,U,U) m, T eps = 1e-5) 1437 if (__traits(isFloating, T)) 1438 { 1439 import std.math : abs, sqrt; 1440 1441 T getMaxValue(ref Matrix!(T,U,U) m, out uint p, out uint q) { 1442 T max = 0; 1443 foreach (i; 0..U) { 1444 foreach (j; 0..U) { 1445 if (i == j) continue; 1446 if (m[i,j].abs > max) { 1447 max = m[i,j].abs; 1448 p = i; 1449 q = j; 1450 } 1451 } 1452 } 1453 return max; 1454 } 1455 1456 auto result = Matrix!(T,U,U).identity; 1457 T max; 1458 uint p, q; 1459 uint bp = 114514, bq = 114514; 1460 while (true) { 1461 if ((max = getMaxValue(m, p, q)) < eps) break; 1462 if (p == bp && q == bq) break; 1463 const pp = m[p,p]; 1464 const pq = m[p,q]; 1465 const qq = m[q,q]; 1466 T alpha = (pp - qq) / 2.0; 1467 T beta = -pq; 1468 T gamma = abs(alpha) / sqrt(alpha*alpha+beta*beta); 1469 T s = sqrt((1.0-gamma) / 2.0); 1470 const c = sqrt((1.0+gamma) / 2.0); 1471 if (alpha * beta < 0) s = -s; 1472 static foreach (i; 0..U) {{ 1473 const tmp = c * m[p, i] - s * m[q, i]; 1474 m[q, i] = s * m[p, i] + c * m[q, i]; 1475 m[p, i] = tmp; 1476 }} 1477 static foreach (i; 0..U) { 1478 m[i,p] = m[p,i]; 1479 m[i,q] = m[q,i]; 1480 } 1481 m[p,p] = c*c*pp + s*s*qq - 2*s*c*pq; 1482 m[p,q] = s*c*(pp-qq) + (c*c-s*s) * pq; 1483 m[q,p] = m[p,q]; 1484 m[q,q] = s*s*pp + c*c*qq + 2*s*c*pq; 1485 static foreach (i; 0..U) {{ 1486 const tmp = c*result[i,p]-s*result[i,q]; 1487 result[i,q] = s*result[i,p] + c*result[i,q]; 1488 result[i,p] = tmp; 1489 }} 1490 bp = p; 1491 bq = q; 1492 } 1493 return result; 1494 } 1495 1496 unittest { 1497 import std.algorithm : map; 1498 import std.array : array; 1499 import std.range : iota; 1500 import std.random : uniform; 1501 import sbylib.math.vector : vapproxEqual = approxEqual; 1502 foreach (k; 0..100) { 1503 // m is real symmetric matrix 1504 auto m = mat4(16.iota.map!(_ => uniform(-1.0f, +1.0f))); 1505 static foreach (i; 0..4) { 1506 static foreach (j; 0..4) { 1507 m[i,j] = m[j,i]; 1508 } 1509 } 1510 assert(m.isSymmetric); 1511 1512 // p is diagonalize matrix of m 1513 const p = diagonalizeForRealSym(m); 1514 1515 // a is diagonalized matrix 1516 const a = transpose(p) * m * p; 1517 1518 // successfully diagonalized 1519 assert(approxEqual(a, mat4.scale(diagonal(a)), 1e-3)); 1520 1521 // if m is real symmetric, p is orthognal matrix 1522 assert(p.isOrthogonal); 1523 1524 static foreach (i; 0..4) {{ 1525 const eigenVector = p.column[i]; 1526 const eigenValue = a[i,i]; 1527 1528 assert(vapproxEqual(m * eigenVector, eigenVector * eigenValue, 1e-3)); 1529 }} 1530 } 1531 } 1532 1533 /** 1534 Returns true if the given matrix is symmetric. 1535 1536 Params: 1537 m = target matrix 1538 eps = criteria of approximation 1539 1540 Returns: true if the given matrix is symmetric 1541 */ 1542 bool isSymmetric(T, uint U)(const Matrix!(T,U,U) m, T eps = 1e-5) { 1543 return approxEqual(m, transpose(m), eps); 1544 } 1545 1546 /** 1547 Returns true if the given matrix is orthogonal. 1548 1549 Params: 1550 m = target matrix 1551 eps = criteria of approximation 1552 1553 Returns: true if the given matrix is orthogonal 1554 */ 1555 bool isOrthogonal(T, uint U)(const Matrix!(T,U,U) m, T eps = 1e-5) { 1556 return approxEqual(invert(m), transpose(m), eps); 1557 } 1558 1559 /** 1560 Returns true if the distance of each element is less than eps. 1561 1562 Params: 1563 a = target point 1564 b = target point 1565 eps = criteria of approximation 1566 1567 Returns: true if 2 vectors are approximately equal. 1568 */ 1569 bool approxEqual(T,uint U,uint V)(const Matrix!(T,U,V) a, const Matrix!(T,U,V) b, T eps = 1e-5) { 1570 import std.math : abs; 1571 1572 static foreach (i; 0..U) { 1573 static foreach (j; 0..V) { 1574 if (abs(a[i,j] - b[i,j]) > eps) return false; 1575 } 1576 } 1577 return true; 1578 }