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 }