1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.math; 13 14 import retrograde.entity; 15 import retrograde.option; 16 import retrograde.stringid; 17 18 import std.conv; 19 import std.math; 20 import std.range; 21 22 enum real TwoPI = (2 * PI); 23 24 const UnitVector3D standardUpVector = UnitVector3D(0, 1, 0); 25 const UnitVector3D standardSideVector = UnitVector3D(1, 0, 0); 26 27 class Position2IComponent : EntityComponent { 28 mixin EntityComponentIdentity!"Position2IComponent"; 29 30 private Vector2I _position; 31 32 public @property position() { 33 return _position; 34 } 35 36 public @property position(Vector2I newPosition) { 37 _position = newPosition; 38 } 39 40 this() { 41 this(Vector2I(0, 0)); 42 } 43 44 this(int x, int y) { 45 this(Vector2I(x, y)); 46 } 47 48 this(Vector2I position) { 49 this.position = position; 50 } 51 } 52 53 class Position2DComponent : EntityComponent, Snapshotable { 54 mixin EntityComponentIdentity!"Position2DComponent"; 55 56 private Vector2D _position; 57 58 public @property position() { 59 return _position; 60 } 61 62 public @property position(Vector2D newPosition) { 63 _position = newPosition; 64 } 65 66 this() { 67 this(Vector2D(0, 0)); 68 } 69 70 this(int x, int y) { 71 this(Vector2D(x, y)); 72 } 73 74 this(Vector2D position) { 75 this.position = position; 76 } 77 78 public string[string] getSnapshotData() { 79 return [ 80 "position": _position.toString() 81 ]; 82 } 83 } 84 85 class Position3DComponent : EntityComponent { 86 mixin EntityComponentIdentity!"Position3DComponent"; 87 88 public Vector3D position; 89 90 this() { 91 this(Vector3D(0)); 92 } 93 94 this(Vector3D position) { 95 this.position = position; 96 } 97 } 98 99 class OrientationR2Component : EntityComponent, Snapshotable { 100 mixin EntityComponentIdentity!"OrientationR2Component"; 101 102 private double _angle; 103 104 public @property double angle() { 105 return _angle; 106 } 107 108 public @property void angle(double angle) { 109 this._angle = wrapAngle(angle); 110 } 111 112 this() { 113 this(0); 114 } 115 116 this(double angleInRadian) { 117 angle = angleInRadian; 118 } 119 120 public string[string] getSnapshotData() { 121 return [ 122 "angle": to!string(angle) 123 ]; 124 } 125 } 126 127 class OrientationR3Component : EntityComponent { 128 mixin EntityComponentIdentity!"OrientationR3Component"; 129 130 public QuaternionD orientation; 131 132 this() { 133 this(QuaternionD.nullRotation); 134 } 135 136 this (QuaternionD orientation) { 137 this.orientation = orientation; 138 } 139 } 140 141 class Scale3DComponent : EntityComponent { 142 mixin EntityComponentIdentity!"Scale3DComponent"; 143 144 public Vector3D scale; 145 146 this() { 147 this(Vector3D(1)); 148 } 149 150 this (Vector3D scale) { 151 this.scale = scale; 152 } 153 } 154 155 class RelativePosition2DComponent : EntityComponent, Snapshotable { 156 mixin EntityComponentIdentity!"RelativePosition2DComponent"; 157 158 public Vector2D relativePosition; 159 160 this() { 161 this(Vector2D(0, 0)); 162 } 163 164 this(int x, int y) { 165 this(Vector2D(x, y)); 166 } 167 168 this(Vector2D relativePosition) { 169 this.relativePosition = relativePosition; 170 } 171 172 public override string[string] getSnapshotData() { 173 return [ 174 "relative-position": relativePosition.toString() 175 ]; 176 } 177 } 178 179 class RelativePositionProcessor : EntityProcessor { 180 private HierarchialEntityCollection hierarchialEntities = new HierarchialEntityCollection(); 181 182 public override bool acceptsEntity(Entity entity) { 183 return entity.hasComponent!RelativePosition2DComponent 184 && entity.hasComponent!Position2DComponent; 185 } 186 187 protected override void processAcceptedEntity(Entity entity) { 188 hierarchialEntities.addEntity(entity); 189 } 190 191 protected override void processRemovedEntity(Entity entity) { 192 hierarchialEntities.removeEntity(entity.id); 193 } 194 195 public override void update() { 196 hierarchialEntities.updateHierarchy(); 197 hierarchialEntities.forEachChild((entity) { 198 auto parentPosition = Vector2D(0); 199 double parentOrientation = 0; 200 if (entity.parent !is null) { 201 parentPosition = entity.parent.getFromComponent!Position2DComponent(component => component.position, Vector2D(0)); 202 parentOrientation = entity.parent.getFromComponent!OrientationR2Component(component => component.angle, 0); 203 } 204 205 auto relativePosition = entity.getFromComponent!RelativePosition2DComponent(c => c.relativePosition); 206 entity.withComponent!Position2DComponent((component) { 207 auto transformVector = parentPosition.toTranslationMatrix() * createRotationMatrix(parentOrientation) * Vector3D(relativePosition, 1); 208 component.position = transformVector.downgrade(); 209 }); 210 }); 211 } 212 } 213 214 class RelativeOrientationR2Component : EntityComponent, Snapshotable { 215 mixin EntityComponentIdentity!"RelativeOrientationR2Component"; 216 217 private double _relativeAngle; 218 219 public @property double relativeAngle() { 220 return _relativeAngle; 221 } 222 223 public @property void relativeAngle(double relativeAngle) { 224 this._relativeAngle = wrapAngle(relativeAngle); 225 } 226 227 this() { 228 this(0); 229 } 230 231 this(scalar relativeAngle) { 232 this.relativeAngle = relativeAngle; 233 } 234 235 public override string[string] getSnapshotData() { 236 return [ 237 "relative-angle": to!string(relativeAngle) 238 ]; 239 } 240 } 241 242 class RelativeOrientationProcessor : EntityProcessor { 243 private HierarchialEntityCollection hierarchialEntities = new HierarchialEntityCollection(); 244 245 public override bool acceptsEntity(Entity entity) { 246 return entity.hasComponent!OrientationR2Component 247 && entity.hasComponent!RelativeOrientationR2Component; 248 } 249 250 protected override void processAcceptedEntity(Entity entity) { 251 hierarchialEntities.addEntity(entity); 252 } 253 254 protected override void processRemovedEntity(Entity entity) { 255 hierarchialEntities.removeEntity(entity.id); 256 } 257 258 public override void update() { 259 hierarchialEntities.updateHierarchy(); 260 hierarchialEntities.forEachChild((entity) { 261 scalar parentAngle = 0; 262 if (entity.parent !is null) { 263 parentAngle = entity.parent.getFromComponent!OrientationR2Component(c => c.angle, 0); 264 } 265 266 auto relativeAngle = entity.getFromComponent!RelativeOrientationR2Component(c => c.relativeAngle); 267 entity.withComponent!OrientationR2Component((c) { 268 c.angle = parentAngle + relativeAngle; 269 }); 270 }); 271 } 272 } 273 274 alias scalar = double; 275 276 struct Vector(T, uint N) if (N > 0) { 277 private T[N] components; 278 279 public alias _N = N; 280 public alias _T = T; 281 282 public @property const T x() { 283 return components[0]; 284 } 285 286 public @property x(T x) { 287 components[0] = x; 288 } 289 290 static if (N >= 2) { 291 public @property const T y() { 292 return components[1]; 293 } 294 295 public @property y(T y) { 296 components[1] = y; 297 } 298 } 299 300 static if (N >= 3) { 301 public @property const T z() { 302 return components[2]; 303 } 304 305 public @property z(T z) { 306 components[2] = z; 307 } 308 } 309 310 static if (N >= 4) { 311 public @property const T w() { 312 return components[3]; 313 } 314 315 public @property w(T w) { 316 components[3] = w; 317 } 318 } 319 320 static if (N >= 2) { 321 public Vector normalize() { 322 auto currentMagnitude = magnitude; 323 if (currentMagnitude == 0) { 324 return Vector(0); 325 } 326 327 if (currentMagnitude == 1) { 328 return this; 329 } 330 331 Vector normalizedVector; 332 foreach (i ; 0 .. N) { 333 normalizedVector[i] = cast(T) (this[i] / currentMagnitude); 334 } 335 return normalizedVector; 336 } 337 } 338 339 this(T val) { 340 this.components[0 .. N] = val; 341 } 342 343 this(T[] components...) { 344 assert(components.length == N, "Cannot initialize a vector with a different amount of components than available."); 345 this.components = components; 346 } 347 348 static if (N >= 2) { 349 this(Vector!(T, N-1) smallerVector, T extraComponent) { 350 this.components = smallerVector.components ~ [extraComponent]; 351 } 352 } 353 354 static if (N >= 2) { 355 alias length = magnitude; 356 357 public @property scalar magnitude() { 358 scalar powSum = 0; 359 foreach(component ; components) { 360 powSum += component * component; 361 } 362 363 return sqrt(powSum); 364 } 365 } 366 367 public void round() { 368 foreach (i, component; components) { 369 components[i] = cast(T) std.math.round(component); 370 } 371 } 372 373 static if (N == 2) { 374 public @property scalar angle() { 375 auto angle = atan2(cast(scalar) y, cast(scalar) x); 376 if (angle < 0) { 377 angle = (2*PI) + angle; 378 } 379 return angle; 380 } 381 } 382 383 Vector opUnary(string s)() if (s == "-") { 384 return this * -1; 385 } 386 387 Vector opBinary(string op)(Vector rhs) if (rhs._N == N && (op == "+" || op == "-")) { 388 Vector vec; 389 foreach(i ; 0..N) { 390 mixin("vec[i] = cast(T) (components[i] " ~ op ~ " rhs[i]);"); 391 } 392 393 return vec; 394 } 395 396 Vector opBinary(string op)(scalar rhs) if (op == "*" || op == "/") { 397 Vector vec; 398 foreach(i ; 0..N) { 399 mixin("vec[i] = cast(T) (components[i] " ~ op ~ " rhs);"); 400 } 401 402 return vec; 403 } 404 405 Vector opBinaryRight(string op)(scalar lhs) if (op == "*") { 406 return this * lhs; 407 } 408 409 bool opEquals()(auto ref const Vector other) const if (other._N == N) { 410 foreach(i ; 0 .. N) { 411 if (this[i] != other[i]) { 412 return false; 413 } 414 } 415 return true; 416 } 417 418 T dot()(Vector other) if (other._N == N) { 419 T dotProduct = 0; 420 foreach (i ; 0..N) { 421 dotProduct += this[i] * other[i]; 422 } 423 return dotProduct; 424 } 425 426 Vector cross()(Vector other) if (N == 3 && other._N == N) { 427 return Vector( 428 (this.y * other.z) - (this.z * other.y), 429 (this.z * other.x) - (this.x * other.z), 430 (this.x * other.y) - (this.y * other.x) 431 ); 432 } 433 434 Vector reflect()(Vector normal) if (N >= 2 && normal._N == N) { 435 normal = normal.normalize(); 436 return this - ((2 * normal).dot(this) * normal); 437 } 438 439 Vector refract()(T refractionIndex, Vector normal) if (N >= 2 && normal._N == N) { 440 auto normalizedThis = this.normalize(); 441 normal = normal.normalize(); 442 443 auto dotProduct = normal.dot(normalizedThis); 444 auto k = 1 - (refractionIndex * refractionIndex) * (1 - (dotProduct * dotProduct)); 445 if (k < 0) { 446 return Vector(0); 447 } 448 449 return refractionIndex * normalizedThis - (refractionIndex * normal.dot(normalizedThis) + sqrt(k)) * normal; 450 } 451 452 TargetVectorType opCast(TargetVectorType)() if(TargetVectorType._N == N) { 453 auto resultVector = TargetVectorType(); 454 foreach(i ; 0 .. N) { 455 resultVector[i] = cast(TargetVectorType._T) this[i]; 456 } 457 return resultVector; 458 } 459 460 T opIndex(size_t index) const { 461 return components[index]; 462 } 463 464 T opIndexAssign(T value, size_t index) { 465 return components[index] = value; 466 } 467 468 string toString() { 469 string[] componentStrings; 470 471 foreach(i ; 0 .. N) { 472 componentStrings ~= to!string(this[i]); 473 } 474 475 return "(" ~ componentStrings.join(", ") ~ ")"; 476 } 477 478 static if (N >= 2) { 479 public Vector!(T, N-1) downgrade() { 480 return Vector!(T, N-1)(this.components[0..$-1]); 481 } 482 } 483 } 484 485 alias Vector2I = Vector!(int, 2); 486 alias Vector2U = Vector!(uint, 2); 487 alias Vector2L = Vector!(long, 2); 488 alias Vector2UL = Vector!(ulong, 2); 489 alias Vector2F = Vector!(float, 2); 490 alias Vector2D = Vector!(double, 2); 491 alias Vector2R = Vector!(real, 2); 492 493 alias Vector3I = Vector!(int, 3); 494 alias Vector3U = Vector!(uint, 3); 495 alias Vector3L = Vector!(long, 3); 496 alias Vector3UL = Vector!(ulong, 3); 497 alias Vector3F = Vector!(float, 3); 498 alias Vector3D = Vector!(double, 3); 499 alias Vector3R = Vector!(real, 3); 500 501 alias Vector4D = Vector!(double, 4); 502 503 struct UnitVector(VectorType) { 504 private VectorType _vector; 505 506 this(VectorType vector) { 507 this._vector = vector.normalize(); 508 } 509 510 this(VectorType._T[] components...) { 511 assert(components.length == VectorType._N, "Cannot initialize a unit vector with a different amount of components than its vector type has."); 512 this(VectorType(components)); 513 } 514 515 public const @property VectorType vector() { 516 return _vector; 517 } 518 } 519 520 alias UnitVector2D = UnitVector!Vector2D; 521 alias UnitVector2F = UnitVector!Vector2F; 522 523 alias UnitVector3D = UnitVector!Vector3D; 524 alias UnitVector3F = UnitVector!Vector3F; 525 526 alias UnitVector4D = UnitVector!Vector4D; 527 528 struct Rectangle(T) { 529 public Vector!(T, 2) position; 530 private Vector!(T, 2) size; 531 532 alias _T = T; 533 534 public this(T x, T y, T width, T height) { 535 position.x = x; 536 position.y = y; 537 size.x = width; 538 size.y = height; 539 } 540 541 public this(Vector!(T, 2) position, T width, T height) { 542 this.position = position; 543 size.x = width; 544 size.y = height; 545 } 546 547 public @property const T x() { 548 return position.x; 549 } 550 551 public @property void x(T x) { 552 position.x = x; 553 } 554 555 public @property const T y() { 556 return position.y; 557 } 558 559 public @property void y(T y) { 560 position.y = y; 561 } 562 563 public @property const T width() { 564 return size.x; 565 } 566 567 public @property void width(T width) { 568 size.x = width; 569 } 570 571 public @property const T height() { 572 return size.y; 573 } 574 575 public @property void height(T height) { 576 size.y = height; 577 } 578 579 TargetRectangleType opCast(TargetRectangleType)() { 580 auto resultRectangle = TargetRectangleType(); 581 582 resultRectangle.position = cast(Vector!(TargetRectangleType._T, 2)) position; 583 resultRectangle.width = cast(TargetRectangleType._T) width; 584 resultRectangle.height = cast(TargetRectangleType._T) height; 585 586 return resultRectangle; 587 } 588 589 } 590 591 alias RectangleI = Rectangle!int; 592 alias RectangleU = Rectangle!uint; 593 alias RectangleL = Rectangle!long; 594 alias RectangleUL = Rectangle!ulong; 595 alias RectangleF = Rectangle!float; 596 alias RectangleD = Rectangle!double; 597 598 struct Matrix(T, uint Rows, uint Columns) if (Rows > 0 && Columns > 0) { 599 private T[Columns * Rows] data; 600 601 public alias _T = T; 602 public alias _Rows = Rows; 603 public alias _Columns = Columns; 604 public alias _VectorType = Vector!(T, Rows); 605 606 static if (Rows == Columns) { 607 private static Matrix identityMatrix; 608 609 public static @property Matrix identity() { 610 return identityMatrix; 611 } 612 613 static this() { 614 foreach (row ; 0 .. Rows) { 615 foreach (column ; 0 .. Columns) { 616 identityMatrix[row, column] = column == row ? 1 : 0; 617 } 618 } 619 } 620 } 621 622 this(T initialValue) { 623 data[0 .. data.length] = initialValue; 624 } 625 626 this(T[] initialValues...) { 627 assert(initialValues.length == data.length, "Cannot initialize a matrix with a different size of data than available."); 628 data = initialValues; 629 } 630 631 T opIndex(size_t row, size_t column) const { 632 return data[row * Columns + column]; 633 } 634 635 T opIndex(size_t index) const { 636 return data[index]; 637 } 638 639 T opIndexAssign(T value, size_t row, size_t column) { 640 return data[row * Columns + column] = value; 641 } 642 643 T opIndexAssign(T value, size_t index) { 644 return data[index] = value; 645 } 646 647 Matrix opUnary(string s)() if (s == "-") { 648 return this * -1; 649 } 650 651 _VectorType opBinary(string op)(_VectorType rhs) if (op == "*") { 652 _VectorType vector = _VectorType(0); 653 foreach (row ; 0 .. Rows) { 654 foreach (column ; 0 .. Columns) { 655 vector[row] = vector[row] + this[row, column] * rhs[column]; 656 } 657 } 658 659 return vector; 660 } 661 662 Matrix opBinary(string op)(scalar rhs) if (op == "*") { 663 Matrix matrix; 664 foreach(index ; 0 .. data.length) { 665 matrix[index] = this[index] * rhs; 666 } 667 return matrix; 668 } 669 670 Matrix opBinaryRight(string op)(scalar lhs) if (op == "*") { 671 return this * lhs; 672 } 673 674 Matrix!(T, Rows, OtherColumns) opBinary(string op, uint OtherRows, uint OtherColumns)(Matrix!(T, OtherRows, OtherColumns) rhs) if (op == "*" && Columns == OtherRows) { 675 Matrix!(T, Rows, OtherColumns) resultMatrix; 676 auto transposedRhs = rhs.transpose(); 677 Vector!(T, Columns)[OtherColumns] columnCache; 678 foreach(thisRow ; 0 .. Rows) { 679 auto rowVector = getRowVector(thisRow); 680 foreach(otherColumn ; 0 .. OtherColumns) { 681 if (thisRow == 0) { 682 columnCache[otherColumn] = transposedRhs.getRowVector(otherColumn); 683 } 684 685 resultMatrix[thisRow, otherColumn] = rowVector.dot(columnCache[otherColumn]); 686 } 687 } 688 689 return resultMatrix; 690 } 691 692 Matrix opBinary(string op)(Matrix rhs) if ((op == "+" || op == "-") && Columns == rhs._Columns && Rows == rhs._Rows) { 693 Matrix resultMatrix; 694 foreach (index; 0 .. data.length) { 695 mixin("resultMatrix[index] = this[index] " ~ op ~ " rhs[index];"); 696 } 697 return resultMatrix; 698 } 699 700 Matrix!(T, Columns, Rows) transpose() { 701 Matrix!(T, Columns, Rows) resultMatrix; 702 foreach(row ; 0 .. Rows) { 703 foreach(column ; 0 .. Columns) { 704 resultMatrix[column, row] = this[row, column]; 705 } 706 } 707 708 return resultMatrix; 709 } 710 711 Vector!(T, Columns) getRowVector(size_t row) { 712 return Vector!(T, Columns)(data[row * Columns .. (row * Columns) + Columns]); 713 } 714 } 715 716 alias Matrix4D = Matrix!(double, 4, 4); 717 alias Matrix3D = Matrix!(double, 3, 3); 718 alias Matrix2D = Matrix!(double, 2, 2); 719 720 public Matrix3D toTranslationMatrix(Vector2D vector) { 721 return Matrix3D( 722 1, 0, vector.x, 723 0, 1, vector.y, 724 0, 0, 1 725 ); 726 } 727 728 public Matrix4D toTranslationMatrix(Vector3D vector) { 729 return Matrix4D( 730 1, 0, 0, vector.x, 731 0, 1, 0, vector.y, 732 0, 0, 1, vector.z, 733 0, 0, 0, 1 734 ); 735 } 736 737 public Matrix3D toScalingMatrix(Vector2D scalingVector) { 738 return Matrix3D( 739 scalingVector.x, 0 , 0, 740 0 , scalingVector.y, 0, 741 0 , 0 , 1 742 ); 743 } 744 745 public Matrix4D toScalingMatrix(Vector3D scalingVector) { 746 return Matrix4D( 747 scalingVector.x, 0 , 0 , 0, 748 0 , scalingVector.y , 0 , 0, 749 0 , 0 , scalingVector.z, 0, 750 0 , 0 , 0 , 1 751 ); 752 } 753 754 public Matrix4D createRotationMatrix(scalar radianAngle, double x, double y, double z) { 755 double x2 = x * x; 756 double y2 = y * y; 757 double z2 = z * z; 758 auto cosAngle = cos(radianAngle); 759 auto sinAngle = sin(radianAngle); 760 auto omc = 1.0f - cosAngle; 761 762 return Matrix4D( 763 x2 * omc + cosAngle , y * x * omc + z * sinAngle, x * z * omc - y * sinAngle, 0, 764 x * y * omc - z * sinAngle, y2 * omc + cosAngle , y * z * omc + x * sinAngle, 0, 765 x * z * omc + y * sinAngle, y * z * omc - x * sinAngle, z2 * omc + cosAngle , 0, 766 0 , 0 , 0 , 1 767 ); 768 } 769 770 public Matrix3D createRotationMatrix(scalar radianAngle) { 771 return Matrix3D( 772 cos(radianAngle) , -sin(radianAngle), 0, 773 sin(radianAngle), cos(radianAngle), 0, 774 0 , 0 , 1 775 ); 776 } 777 778 public Matrix4D createRotationMatrix(scalar radianAngle, Vector3D axis) { 779 return createRotationMatrix(radianAngle, axis.x, axis.y, axis.z); 780 } 781 782 public Matrix4D createXRotationMatrix(double radianAngle) { 783 return Matrix4D( 784 1, 0 , 0 , 0, 785 0, cos(radianAngle) , sin(radianAngle), 0, 786 0, -sin(radianAngle), cos(radianAngle), 0, 787 0, 0 , 0 , 1 788 ); 789 } 790 791 public Matrix4D createYRotationMatrix(double radianAngle) { 792 return Matrix4D( 793 cos(radianAngle), 0, -sin(radianAngle), 0, 794 0 , 1, 0 , 0, 795 sin(radianAngle), 0, cos(radianAngle) , 0, 796 0 , 0, 0 , 1 797 ); 798 } 799 800 public Matrix4D createZRotationMatrix(double radianAngle) { 801 return Matrix4D( 802 cos(radianAngle), -sin(radianAngle), 0, 0, 803 sin(radianAngle), cos(radianAngle) , 0, 0, 804 0 , 0 , 1, 0, 805 0 , 0 , 0, 1 806 ); 807 } 808 809 public Matrix4D createLookatMatrix(Vector3D eyePosition, Vector3D targetPosition, UnitVector3D upVector) { 810 auto forwardVector = (targetPosition - eyePosition).normalize(); 811 auto sideVector = forwardVector.cross(upVector.vector); 812 auto cameraBasedUpVector = sideVector.cross(forwardVector); 813 return Matrix4D( 814 sideVector.x , sideVector.y , sideVector.z , -eyePosition.x, 815 cameraBasedUpVector.x, cameraBasedUpVector.y, cameraBasedUpVector.z, -eyePosition.y, 816 -forwardVector.x , -forwardVector.y , -forwardVector.z , -eyePosition.z, 817 0 , 0 , 0 , 1 818 ); 819 } 820 821 public Matrix4D createFirstPersonViewMatrix(Vector3D eyePosition, double pitchInRadian, double yawInRadian) { 822 double cosPitch = cos(pitchInRadian); 823 double sinPitch = sin(pitchInRadian); 824 double cosYaw = cos(yawInRadian); 825 double sinYaw = sin(yawInRadian); 826 827 auto sideVector = Vector3D(cosYaw, 0, -sinYaw); 828 auto upVector = Vector3D(sinYaw * sinPitch, cosPitch, cosYaw * sinPitch); 829 auto forwardVector = Vector3D(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw); 830 831 return Matrix4D( 832 sideVector.x , sideVector.y , sideVector.z , -sideVector.dot(eyePosition), 833 upVector.x , upVector.y , upVector.z , -upVector.dot(eyePosition), 834 forwardVector.x, forwardVector.y, forwardVector.z, -forwardVector.dot(eyePosition), 835 0 , 0 , 0 , 1 836 ); 837 } 838 839 public Matrix4D createPerspectiveMatrix(double fovyDegrees, double aspectRatio, double near, double far) { 840 double q = 1.0 / tan(degreesToRadians(0.5 * fovyDegrees)); 841 double A = q / aspectRatio; 842 double B = (near + far) / (near - far); 843 double C = (2.0 * near * far) / (near - far); 844 845 return Matrix4D( 846 A, 0, 0 , 0, 847 0, q, 0 , 0, 848 0, 0, B , C, 849 0, 0, -1, 0 850 ); 851 } 852 853 struct Quaternion(T) { 854 private T realPart = 1; 855 VectorType imaginaryVector = VectorType(0); 856 857 public alias _T = T; 858 public alias VectorType = Vector!(T, 3); 859 860 public @property T w() { 861 return realPart; 862 } 863 864 public @property T x() { 865 return imaginaryVector.x; 866 } 867 868 public @property T y() { 869 return imaginaryVector.y; 870 } 871 872 public @property T z() { 873 return imaginaryVector.z; 874 } 875 876 public static const Quaternion nullRotation; 877 878 static this() { 879 nullRotation = Quaternion.createRotation(0, standardUpVector.vector); 880 } 881 882 this(T w, T x, T y, T z) { 883 realPart = w; 884 imaginaryVector = VectorType(x, y, z); 885 } 886 887 public static Quaternion createRotation(double radianAngle, Vector3D axis) { 888 return Quaternion( 889 cos(radianAngle/2), 890 axis.x * sin(radianAngle/2), 891 axis.y * sin(radianAngle/2), 892 axis.z * sin(radianAngle/2) 893 ); 894 } 895 896 Quaternion opBinary(string op)(Quaternion rhs) if (op == "*") { 897 return Quaternion( 898 w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, 899 w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, 900 w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, 901 w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w 902 ); 903 } 904 905 public Matrix4D toRotationMatrix() { 906 //TODO: Check if correct for row-major matrix 907 return Matrix4D( 908 w * w + x * x - y * y - z * z, 2 * x * y - 2 * w * z , 2 * x * z + 2 * w * y , 0, 909 2 * x * y + 2 * w * z , w * w - x * x + y * y - z * z, 2 * y * z + 2 * w * x , 0, 910 2 * x * z - 2 * w * y , 2 * y * z - 2 * w * x , w * w - x * x - y * y + z * z, 0, 911 0 , 0 , 0 , 1 912 ); 913 } 914 915 public Vector3D toEulerAngles() { 916 auto q = this; 917 918 auto sqw = q.w * q.w; 919 auto sqx = q.x * q.x; 920 auto sqy = q.y * q.y; 921 auto sqz = q.z * q.z; 922 923 auto unit = sqx + sqy + sqz + sqw; 924 auto poleTest = q.x * q.y + q.z * q.w; 925 926 if (poleTest > 0.499 * unit) { 927 auto yaw = 2 * atan2(q.x, q.w); 928 auto pitch = PI/2; 929 return Vector3D(pitch, yaw, 0); 930 } 931 932 if (poleTest < -0.499 * unit) { 933 auto yaw = -2 * atan2(q.x, q.w); 934 auto pitch = - PI/2; 935 auto bank = 0; 936 return Vector3D(pitch, yaw, 0); 937 } 938 939 auto yaw = atan2(2 * q.y * q.w - 2 * q.x * q.z, sqx - sqy - sqz + sqw); 940 auto pitch = asin(2 * poleTest / unit); 941 auto roll = atan2(2 * q.x * q.w - 2 * q.y * q.z, -sqx + sqy - sqz + sqw); 942 943 return Vector3D(pitch, yaw, roll); 944 } 945 } 946 947 alias QuaternionD = Quaternion!double; 948 949 public double radiansToDegrees(double radians) { 950 return radians * (180 / PI); 951 } 952 953 public double degreesToRadians(double degrees) { 954 return degrees * (PI / 180); 955 } 956 957 public Vector2D radiansToUnitVector(double radians) { 958 return Vector2D(cos(radians), sin(radians)); 959 } 960 961 public double clamp(double value, double min, double max) { 962 return clamp(value, Some!double(min), Some!double(max)); 963 } 964 965 public double clamp(double value, Option!double min = None!double(), Option!double max = None!double()) { 966 if (!min.isEmpty() && value <= min.get()) { 967 return min.get(); 968 } 969 970 if (!max.isEmpty() && value >= max.get()) { 971 return max.get(); 972 } 973 974 return value; 975 } 976 977 public double wrapAngle(double angle) { 978 if (angle > TwoPI || angle < 0) { 979 auto result = angle % TwoPI; 980 if (result < 0) result = TwoPI - -result; 981 return result; 982 } 983 984 return angle; 985 } 986 987 public double deltaAngle(double sourceAngle, double targetAngle) { 988 auto delta = targetAngle - sourceAngle; 989 return mod(delta + PI, TwoPI) - PI; 990 } 991 992 public double mod(double a, double n) { 993 return (a % n + n) % n; 994 } 995 996 public bool equals(double a, double b, double tolerance = 1.11e-16) { 997 double lowerBound = b - tolerance; 998 double upperBound = b + tolerance; 999 return a >= lowerBound && a <= upperBound; 1000 }