1 module tests.matrix_4d_test;
2 
3 import std.stdio;
4 import tests.dunit_tests;
5 import matrix_4d;
6 import Math = math;
7 
8 import vector_3d;
9 import vector_4d;
10 
11 import matrix_3d;
12 
13 /*
14  * The MIT License
15  *
16  * Copyright (c) 2015-2021 JOML.
17  ^%$# Translated by jordan4ibanez
18  *
19  * Permission is hereby granted, free of charge, to any person obtaining a copy
20  * of this software and associated documentation files (the "Software"), to deal
21  * in the Software without restriction, including without limitation the rights
22  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23  * copies of the Software, and to permit persons to whom the Software is
24  * furnished to do so, subject to the following conditions:
25  *
26  * The above copyright notice and this permission notice shall be included in
27  * all copies or substantial portions of the Software.
28  *
29  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35  * THE SOFTWARE.
36  */
37 
38 /**
39  * Tests for the {@link Matrix4d} class.
40  * 
41  * @author Kai Burjack
42  */
43 unittest {
44 
45     writeln("\nTESTING MATRIX4D\n");
46 
47     /**
48      * Test that project and unproject are each other's inverse operations.
49      */
50     // testProjectUnproject
51     {
52         /* Build some arbitrary viewport. */
53         int[] viewport = [0, 0, 800, 800];
54 
55         Vector3d expected = Vector3d(1.0f, 2.0f, -3.0f);
56         Vector3d actual = Vector3d();
57 
58         /* Build a perspective projection and then project and unproject. */
59         Matrix4d m = Matrix4d()
60         .perspective(cast(float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f);
61         m.project(expected, viewport, actual);
62         m.unproject(actual, viewport, actual);
63 
64         /* Check for equality of the components */
65         assertEquals(expected.x, actual.x, MANY_OPS_AROUND_ZERO_PRECISION_FLOAT);
66         assertEquals(expected.y, actual.y, MANY_OPS_AROUND_ZERO_PRECISION_FLOAT);
67         assertEquals(expected.z, actual.z, MANY_OPS_AROUND_ZERO_PRECISION_FLOAT);
68     }
69 
70     // testLookAt
71     {
72         Matrix4d m1 = Matrix4d();
73         Matrix4d m2 = Matrix4d();
74 
75         writeln(m1 == m2);
76 
77         m1 = Matrix4d().lookAt(0, 2, 3, 0, 0, 0, 0, 1, 0);
78         m2 = Matrix4d().translate(0, 0, -Math.sqrt(2 * 2 + 3 * 3)).rotateX(Math.atan2(2, 3));
79         
80         writeln(m1);
81         writeln(m2);
82 
83         assertMatrix4dEquals(m1, m2, 1E-2f);
84 
85         m1 = Matrix4d().lookAt(3, 2, 0, 0, 0, 0, 0, 1, 0);
86         m2 = Matrix4d().translate(0, 0, -cast(float) Math.sqrt(2 * 2 + 3 * 3))
87                 .rotateX(cast(float) Math.atan2(2, 3)).rotateY(cast(float) Math.toRadians(-90));
88         assertMatrix4dEquals(m1, m2, 1E-2f);
89     }
90 
91     /**
92      * Test computing the frustum planes with a combined view-projection matrix with translation.
93      */
94     // testFrustumPlanePerspectiveRotateTranslate
95     {
96         Vector4d left = Vector4d();
97         Vector4d right = Vector4d();
98         Vector4d top = Vector4d();
99         Vector4d bottom = Vector4d();
100         Vector4d near = Vector4d();
101         Vector4d far = Vector4d();
102 
103         /*
104          * Build a perspective transformation and
105          * move the camera 5 units "up" and rotate it clock-wise 90 degrees around Y.
106          */
107         Matrix4d m = Matrix4d()
108         .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
109         .rotateY(cast(float) Math.toRadians(90))
110         .translate(0, -5, 0);
111         m.frustumPlane(Matrix4d.PLANE_NX, left);
112         m.frustumPlane(Matrix4d.PLANE_PX, right);
113         m.frustumPlane(Matrix4d.PLANE_NY, bottom);
114         m.frustumPlane(Matrix4d.PLANE_PY, top);
115         m.frustumPlane(Matrix4d.PLANE_NZ, near);
116         m.frustumPlane(Matrix4d.PLANE_PZ, far);
117 
118         Vector4d expectedLeft = Vector4d(1, 0, 1, 0).normalize3();
119         Vector4d expectedRight = Vector4d(1, 0, -1, 0).normalize3();
120         Vector4d expectedTop = Vector4d(1, -1, 0, 5).normalize3();
121         Vector4d expectedBottom = Vector4d(1, 1, 0, -5).normalize3();
122         Vector4d expectedNear = Vector4d(1, 0, 0, -0.1f).normalize3();
123         Vector4d expectedFar = Vector4d(-1, 0, 0, 100.0f).normalize3();
124 
125         assertVector4dEquals(expectedLeft, left, 1E-5f);
126         assertVector4dEquals(expectedRight, right, 1E-5f);
127         assertVector4dEquals(expectedTop, top, 1E-5f);
128         assertVector4dEquals(expectedBottom, bottom, 1E-5f);
129         assertVector4dEquals(expectedNear, near, 1E-5f);
130         assertVector4dEquals(expectedFar, far, 1E-4f);
131     }
132 
133     // testFrustumRay
134     {
135         Vector3d dir = Vector3d();
136         Matrix4d m = Matrix4d()
137                 .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
138                 .rotateY(cast(float) Math.toRadians(90));
139         Vector3d expectedDir;
140         m.frustumRayDir(0, 0, dir);
141         expectedDir = Vector3d(1, -1, -1).normalize();
142         assertVector3dEquals(expectedDir, dir, 1E-5f);
143         m.frustumRayDir(1, 0, dir);
144         expectedDir = Vector3d(1, -1, 1).normalize();
145         assertVector3dEquals(expectedDir, dir, 1E-5f);
146         m.frustumRayDir(0, 1, dir);
147         expectedDir = Vector3d(1, 1, -1).normalize();
148         assertVector3dEquals(expectedDir, dir, 1E-5f);
149         m.frustumRayDir(1, 1, dir);
150         expectedDir = Vector3d(1, 1, 1).normalize();
151         assertVector3dEquals(expectedDir, dir, 1E-5f);
152     }
153 
154     // testFrustumRay2
155     {
156         Vector3d dir = Vector3d();
157         Matrix4d m = Matrix4d()
158                 .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
159                 .rotateZ(cast(float) Math.toRadians(45));
160         Vector3d expectedDir;
161         m.frustumRayDir(0, 0, dir);
162         expectedDir = Vector3d(-cast(float)Math.sqrt(2), 0, -1).normalize();
163         assertVector3dEquals(expectedDir, dir, 1E-5f);
164         m.frustumRayDir(1, 0, dir);
165         expectedDir = Vector3d(0, -cast(float)Math.sqrt(2), -1).normalize();
166         assertVector3dEquals(expectedDir, dir, 1E-5f);
167         m.frustumRayDir(0, 1, dir);
168         expectedDir = Vector3d(0, cast(float)Math.sqrt(2), -1).normalize();
169         assertVector3dEquals(expectedDir, dir, 1E-5f);
170         m.frustumRayDir(1, 1, dir);
171         expectedDir = Vector3d(cast(float)Math.sqrt(2), 0, -1).normalize();
172         assertVector3dEquals(expectedDir, dir, 1E-5f);
173     }
174 
175     // testMatrix4dTranspose
176     {
177         double m00 = 1, m01 = 2, m02 = 3, m03 = 4;
178         double m10 = 5, m11 = 6, m12 = 7, m13 = 8;
179         double m20 = 9, m21 = 10, m22 = 11, m23 = 12;
180         double m30 = 13, m31 = 14, m32 = 15, m33 = 16;
181 
182         Matrix4d m = Matrix4d(m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33);
183         Matrix4d expect = Matrix4d(m00,m10,m20,m30,m01,m11,m21,m31,m02,m12,m22,m32,m03,m13,m23,m33);
184         assertMatrix4dEquals(Matrix4d(m).transpose(),expect, 1E-5f);
185         Matrix4d testUnit = Matrix4d();
186         assertMatrix4dEquals(Matrix4d(m).transpose(testUnit),expect, 1E-5f);
187     }
188 
189     // testMatrix4d3fTranspose
190     {
191         double m00 = 1, m01 = 2, m02 = 3, m03 = 4;
192         double m10 = 5, m11 = 6, m12 = 7, m13 = 8;
193         double m20 = 9, m21 = 10, m22 = 11, m23 = 12;
194         double m30 = 13, m31 = 14, m32 = 15, m33 = 16;
195 
196         Matrix4d m = Matrix4d(m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33);
197         Matrix4d expect = Matrix4d(m00,m10,m20,m03,m01,m11,m21,m13,m02,m12,m22,m23,m30,m31,m32,m33);
198 
199         Matrix4d testUnit3 = Matrix4d(m).transpose3x3();
200 
201         assertMatrix4dEquals(testUnit3,expect, 1E-5f);
202         Matrix3d expect1 = Matrix3d(m00,m10,m20,m01,m11,m21,m02,m12,m22);
203         Matrix4d expect2 = Matrix4d(expect1);
204         Matrix4d testUnit1 = Matrix4d();
205         assertMatrix4dEquals(Matrix4d(m).transpose3x3(testUnit1),expect2, 1E-5f);
206         Matrix3d testUnit2 = Matrix3d();
207         assertMatrix3dEquals(Matrix4d(m).transpose3x3(testUnit2),expect1, 1E-5f);
208     }
209 
210     // testPositiveXRotateY
211     {
212         Vector3d dir = Vector3d();
213         Matrix4d m = Matrix4d();
214         m.rotateY(Math.toRadians(90));
215         m.positiveX(dir);
216         Vector3d testUnit = Vector3d(0, 0, 1);
217         assertVector3dEquals(testUnit, dir, 1E-7f);
218     }
219 
220     // testPositiveYRotateX
221     {
222         Vector3d dir = Vector3d();
223         Matrix4d m = Matrix4d()
224                 .rotateX(cast(float) Math.toRadians(90));
225         m.positiveY(dir);
226         assertVector3dEquals(Vector3d(0, 0, -1), dir, 1E-7f);
227     }
228 
229     // testPositiveZRotateX
230     {
231         Vector3d dir = Vector3d();
232         Matrix4d m = Matrix4d()
233                 .rotateX(cast(float) Math.toRadians(90));
234         m.positiveZ(dir);
235         assertVector3dEquals(Vector3d(0, 1, 0), dir, 1E-7f);
236     }
237 
238     // testPositiveXRotateXY
239     {
240         Vector3d dir = Vector3d();
241         Matrix4d m = Matrix4d()
242                 .rotateY(cast(float) Math.toRadians(90)).rotateX(cast(float) Math.toRadians(45));
243         m.positiveX(dir);
244         assertVector3dEquals(Vector3d(0, 1, 1).normalize(), dir, 1E-7f);
245     }
246 
247     // testPositiveXPerspectiveRotateY
248     {
249         Vector3d dir = Vector3d();
250         Matrix4d m = Matrix4d()
251                 .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
252                 .rotateY(cast(float) Math.toRadians(90));
253         m.positiveX(dir);
254         assertVector3dEquals(Vector3d(0, 0, -1), dir, 1E-7f);
255     }
256 
257     // testPositiveXPerspectiveRotateXY
258     {
259         Vector3d dir = Vector3d();
260         Matrix4d m = Matrix4d()
261                 .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
262                 .rotateY(cast(float) Math.toRadians(90)).rotateX(cast(float) Math.toRadians(45));
263         m.positiveX(dir);
264         assertVector3dEquals(Vector3d(0, -1, -1).normalize(), dir, 1E-7f);
265     }
266 
267     // testPositiveXYZLookAt
268     {
269         Vector3d dir = Vector3d();
270         Matrix4d m = Matrix4d()
271                 .lookAt(0, 0, 0, -1, 0, 0, 0, 1, 0);
272         m.positiveX(dir);
273         assertVector3dEquals(Vector3d(0, 0, -1).normalize(), dir, 1E-7f);
274         m.positiveY(dir);
275         assertVector3dEquals(Vector3d(0, 1, 0).normalize(), dir, 1E-7f);
276         m.positiveZ(dir);
277         assertVector3dEquals(Vector3d(1, 0, 0).normalize(), dir, 1E-7f);
278     }
279 
280     // testPositiveXYZSameAsInvert
281     {
282         Vector3d dir = Vector3d();
283         Vector3d dir2 = Vector3d();
284         Matrix4d m = Matrix4d().rotateXYZ(0.12f, 1.25f, -2.56f);
285         Matrix4d inv = Matrix4d(m).invert();
286         m.positiveX(dir);
287         dir2.set(1, 0, 0);
288         inv.transformDirection(dir2);
289         assertVector3dEquals(dir2, dir, 1E-6f);
290         m.positiveY(dir);
291         dir2.set(0, 1, 0);
292         inv.transformDirection(dir2);
293         assertVector3dEquals(dir2, dir, 1E-6f);
294         m.positiveZ(dir);
295         dir2.set(0, 0, 1);
296         inv.transformDirection(dir2);
297         assertVector3dEquals(dir2, dir, 1E-6f);
298     }
299 
300     // testFrustumCornerIdentity
301     {
302         Matrix4d m = Matrix4d();
303         Vector3d corner = Vector3d();
304         m.frustumCorner(Matrix4d.CORNER_NXNYNZ, corner); // left, bottom, near
305         assertVector3dEquals(Vector3d(-1, -1, -1), corner, 1E-6f);
306         m.frustumCorner(Matrix4d.CORNER_PXNYNZ, corner); // right, bottom, near
307         assertVector3dEquals(Vector3d(1, -1, -1), corner, 1E-6f);
308         m.frustumCorner(Matrix4d.CORNER_PXNYPZ, corner); // right, bottom, far
309         assertVector3dEquals(Vector3d(1, -1, 1), corner, 1E-6f);
310         m.frustumCorner(Matrix4d.CORNER_NXPYPZ, corner); // left, top, far
311         assertVector3dEquals(Vector3d(-1, 1, 1), corner, 1E-6f);
312     }
313 
314     // testFrustumCornerOrthoWide
315     {
316         Matrix4d m = Matrix4d().ortho2D(-2, 2, -1, 1);
317         Vector3d corner = Vector3d();
318         m.frustumCorner(Matrix4d.CORNER_NXNYNZ, corner); // left, bottom, near
319         assertVector3dEquals(Vector3d(-2, -1, 1), corner, 1E-6f);
320         m.frustumCorner(Matrix4d.CORNER_PXNYNZ, corner); // right, bottom, near
321         assertVector3dEquals(Vector3d(2, -1, 1), corner, 1E-6f);
322         m.frustumCorner(Matrix4d.CORNER_PXNYPZ, corner); // right, bottom, far
323         assertVector3dEquals(Vector3d(2, -1, -1), corner, 1E-6f);
324         m.frustumCorner(Matrix4d.CORNER_NXPYPZ, corner); // left, top, far
325         assertVector3dEquals(Vector3d(-2, 1, -1), corner, 1E-6f);
326     }
327 
328     // testFrustumCorner
329     {
330         Matrix4d m = Matrix4d()
331         .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
332         .lookAt(0, 0, 10,
333                 0, 0,  0, 
334                 0, 1,  0);
335         Vector3d corner = Vector3d();
336         m.frustumCorner(Matrix4d.CORNER_NXNYNZ, corner); // left, bottom, near
337         assertVector3dEquals(Vector3d(-0.1f, -0.1f, 10 - 0.1f), corner, 1E-6f);
338         m.frustumCorner(Matrix4d.CORNER_PXNYNZ, corner); // right, bottom, near
339         assertVector3dEquals(Vector3d(0.1f, -0.1f, 10 - 0.1f), corner, 1E-6f);
340         m.frustumCorner(Matrix4d.CORNER_PXNYPZ, corner); // right, bottom, far
341         assertVector3dEquals(Vector3d(100.0f, -100, 10 - 100f), corner, 1E-3f);
342     }
343 
344     // testFrustumCornerWide
345     {
346         Matrix4d m = Matrix4d()
347         .perspective(cast(float) Math.toRadians(90), 2.0f, 0.1f, 100.0f)
348         .lookAt(0, 0, 10,
349                 0, 0,  0, 
350                 0, 1,  0);
351         Vector3d corner = Vector3d();
352         m.frustumCorner(Matrix4d.CORNER_NXNYNZ, corner); // left, bottom, near
353         assertVector3dEquals(Vector3d(-0.2f, -0.1f, 10 - 0.1f), corner, 1E-5f);
354         m.frustumCorner(Matrix4d.CORNER_PXNYNZ, corner); // right, bottom, near
355         assertVector3dEquals(Vector3d(0.2f, -0.1f, 10 - 0.1f), corner, 1E-5f);
356         m.frustumCorner(Matrix4d.CORNER_PXNYPZ, corner); // right, bottom, far
357         assertVector3dEquals(Vector3d(200.0f, -100, 10 - 100f), corner, 1E-3f);
358     }
359 
360     // testFrustumCornerRotate
361     {
362         Matrix4d m = Matrix4d()
363         .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
364         .lookAt(10, 0, 0, 
365                  0, 0, 0, 
366                  0, 1, 0);
367         Vector3d corner = Vector3d();
368         m.frustumCorner(Matrix4d.CORNER_NXNYNZ, corner); // left, bottom, near
369         assertVector3dEquals(Vector3d(10 - 0.1f, -0.1f, 0.1f), corner, 1E-6f);
370         m.frustumCorner(Matrix4d.CORNER_PXNYNZ, corner); // right, bottom, near
371         assertVector3dEquals(Vector3d(10 - 0.1f, -0.1f, -0.1f), corner, 1E-6f);
372         m.frustumCorner(Matrix4d.CORNER_PXNYPZ, corner); // right, bottom, far
373         assertVector3dEquals(Vector3d(-100.0f + 10, -100, -100f), corner, 1E-3f);
374     }
375 
376     // testPerspectiveOrigin
377     {
378         Matrix4d m = Matrix4d()
379         // test symmetric frustum with some modelview translation and rotation
380         .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
381         .lookAt(6, 0, 1, 
382                 0, 0, 0, 
383                 0, 1, 0);
384         Vector3d origin = Vector3d();
385         m.perspectiveOrigin(origin);
386         assertVector3dEquals(Vector3d(6, 0, 1), origin, 1E-5f);
387 
388         // test symmetric frustum with some modelview translation and rotation
389         m = Matrix4d()
390         .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
391         .lookAt(-5, 2, 1, 
392                 0, 1, 0, 
393                 0, 1, 0);
394         m.perspectiveOrigin(origin);
395         assertVector3dEquals(Vector3d(-5, 2, 1), origin, 1E-5f);
396 
397         // test asymmetric frustum
398         m = Matrix4d()
399         .frustum(-0.1f, 0.5f, -0.1f, 0.1f, 0.1f, 100.0f)
400         .lookAt(-5, 2, 1, 
401                 0, 1, 0, 
402                 0, 1, 0);
403         m.perspectiveOrigin(origin);
404         assertVector3dEquals(Vector3d(-5, 2, 1), origin, 1E-5f);
405     }
406 
407     // testPerspectiveFov
408     {
409         Matrix4d m = Matrix4d()
410         .perspective(cast(float) Math.toRadians(45), 1.0f, 0.1f, 100.0f);
411         double fov = m.perspectiveFov();
412         assertEquals(Math.toRadians(45), fov, 1E-5);
413 
414         m = Matrix4d()
415         .perspective(cast(float) Math.toRadians(90), 1.0f, 0.1f, 100.0f)
416         .lookAt(6, 0, 1, 
417                 0, 0, 0, 
418                 0, 1, 0);
419         fov = m.perspectiveFov();
420         assertEquals(Math.toRadians(90), fov, 1E-5);
421     }
422 
423     // testNormal
424     {
425         Matrix4d r = Matrix4d().rotateY(cast(float) Math.PI / 2);
426         Matrix4d s = Matrix4d(r).scale(0.2f);
427         Matrix4d n = Matrix4d();
428         s.normal(n);
429         n.normalize3x3();
430         assertMatrix4dEquals(r, n, 1E-8f);
431     }
432 
433     // testInvertAffine
434     {
435         Matrix4d invm = Matrix4d();
436         Matrix4d m = Matrix4d();
437         m.rotateX(1.2f).rotateY(0.2f).rotateZ(0.1f).translate(1, 2, 3).invertAffine(invm);
438         Vector3d orig = Vector3d(4, -6, 8);
439         Vector3d v = Vector3d();
440         Vector3d w = Vector3d();
441         m.transformPosition(orig, v);
442         invm.transformPosition(v, w);
443         assertVector3dEquals(orig, w, 1E-6f);
444         invm.invertAffine();
445         assertMatrix4dEquals(m, invm, 1E-6f);
446     }
447 
448     // testInvert
449     {
450         Matrix4d invm = Matrix4d();
451         Matrix4d m = Matrix4d();
452         m.perspective(0.1123f, 0.5f, 0.1f, 100.0f).rotateX(1.2f).rotateY(0.2f).rotateZ(0.1f).translate(1, 2, 3).invert(invm);
453         Vector4d orig = Vector4d(4, -6, 8, 1);
454         Vector4d v = Vector4d();
455         Vector4d w = Vector4d();
456         m.transform(orig, v);
457         invm.transform(v, w);
458         assertVector4dEquals(orig, w, 1E-4f);
459         invm.invert();
460         assertMatrix4dEquals(m, invm, 1E-3f);
461     }
462 
463     // testRotateXYZ
464     {
465         Matrix4d m = Matrix4d().rotateX(0.12f).rotateY(0.0623f).rotateZ(0.95f);
466         Matrix4d n = Matrix4d().rotateXYZ(0.12f, 0.0623f, 0.95f);
467         assertMatrix4dEquals(m, n, 1E-6f);
468     }
469 
470     // testRotateZYX
471     {
472         Matrix4d m = Matrix4d().rotateZ(1.12f).rotateY(0.0623f).rotateX(0.95f);
473         Matrix4d n = Matrix4d().rotateZYX(1.12f, 0.0623f, 0.95f);
474         assertMatrix4dEquals(m, n, 1E-6f);
475     }
476 
477     // testRotateYXZ
478     {
479         Matrix4d m = Matrix4d().rotateY(1.12f).rotateX(0.0623f).rotateZ(0.95f);
480         Matrix4d n = Matrix4d().rotateYXZ(1.12f, 0.0623f, 0.95f);
481         assertMatrix4dEquals(m, n, 1E-6f);
482     }
483 
484     // testRotateAffineXYZ
485     {
486         Matrix4d m = Matrix4d().rotateX(0.12f).rotateY(0.0623f).rotateZ(0.95f);
487         Matrix4d n = Matrix4d().rotateAffineXYZ(0.12f, 0.0623f, 0.95f);
488         assertMatrix4dEquals(m, n, 1E-6f);
489     }
490 
491     // testRotateAffineZYX
492     {
493         Matrix4d m = Matrix4d().rotateZ(1.12f).rotateY(0.0623f).rotateX(0.95f);
494         Matrix4d n = Matrix4d().rotateAffineZYX(1.12f, 0.0623f, 0.95f);
495         assertMatrix4dEquals(m, n, 1E-6f);
496     }
497 
498     // testRotateAffineYXZ
499     {
500         Matrix4d m = Matrix4d().rotateY(1.12f).rotateX(0.0623f).rotateZ(0.95f);
501         Matrix4d n = Matrix4d().rotateAffineYXZ(1.12f, 0.0623f, 0.95f);
502         assertMatrix4dEquals(m, n, 1E-6f);
503     }
504 
505     // testRotationXYZ
506     {
507         Matrix4d m = Matrix4d().rotationX(0.32f).rotateY(0.5623f).rotateZ(0.95f);
508         Matrix4d n = Matrix4d().rotationXYZ(0.32f, 0.5623f, 0.95f);
509         assertMatrix4dEquals(m, n, 1E-6f);
510     }
511 
512     // testRotationZYX
513     {
514         Matrix4d m = Matrix4d().rotationZ(0.12f).rotateY(0.0623f).rotateX(0.95f);
515         Matrix4d n = Matrix4d().rotationZYX(0.12f, 0.0623f, 0.95f);
516         assertMatrix4dEquals(m, n, 1E-6f);
517     }
518 
519     // testRotationYXZ
520     {
521         Matrix4d m = Matrix4d().rotationY(0.12f).rotateX(0.0623f).rotateZ(0.95f);
522         Matrix4d n = Matrix4d().rotationYXZ(0.12f, 0.0623f, 0.95f);
523         assertMatrix4dEquals(m, n, 1E-6f);
524     }
525 
526     // testOrthoCrop
527     {
528         Matrix4d lightView = Matrix4d()
529                 .lookAt(0, 5, 0,
530                         0, 0, 0,
531                        -1, 0, 0);
532         Matrix4d crop = Matrix4d();
533         Matrix4d fin = Matrix4d();
534         Matrix4d().ortho2D(-1, 1, -1, 1).invertAffine().orthoCrop(lightView, crop).mulOrthoAffine(lightView, fin);
535         Vector3d p = Vector3d();
536         fin.transformProject(p.set(1, -1, -1));
537         assertEquals(+1.0f, p.x, 1E-6f);
538         assertEquals(-1.0f, p.y, 1E-6f);
539         assertEquals(+1.0f, p.z, 1E-6f);
540         fin.transformProject(p.set(-1, -1, -1));
541         assertEquals(+1.0f, p.x, 1E-6f);
542         assertEquals(+1.0f, p.y, 1E-6f);
543         assertEquals(+1.0f, p.z, 1E-6f);
544     }
545 
546     // testOrthoCropWithPerspective
547     {
548         Matrix4d lightView = Matrix4d()
549                 .lookAt(0, 5, 0,
550                         0, 0, 0,
551                         0, 0, -1);
552         Matrix4d crop = Matrix4d();
553         Matrix4d fin = Matrix4d();
554         Matrix4d().perspective(cast(float) Math.toRadians(90), 1.0f, 5, 10).invertPerspective().orthoCrop(lightView, crop).mulOrthoAffine(lightView, fin);
555         Vector3d p = Vector3d();
556         fin.transformProject(p.set(0, 0, -5));
557         assertEquals(+0.0f, p.x, 1E-6f);
558         assertEquals(-1.0f, p.y, 1E-6f);
559         assertEquals(+0.0f, p.z, 1E-6f);
560         fin.transformProject(p.set(0, 0, -10));
561         assertEquals(+0.0f, p.x, 1E-6f);
562         assertEquals(+1.0f, p.y, 1E-6f);
563         assertEquals(+0.0f, p.z, 1E-6f);
564         fin.transformProject(p.set(-10, 10, -10));
565         assertEquals(-1.0f, p.x, 1E-6f);
566         assertEquals(+1.0f, p.y, 1E-6f);
567         assertEquals(-1.0f, p.z, 1E-6f);
568     }
569 
570     // testRotateTowardsXY
571     {
572         Vector3d v = Vector3d(1, 1, 0).normalize();
573         Matrix4d testUnit = Matrix4d();
574         Matrix4d m1 = Matrix4d().rotateZ(v.angle(Vector3d(1, 0, 0)), testUnit);
575         Matrix4d testUnit2 = Matrix4d();
576         Matrix4d m2 = Matrix4d().rotateTowardsXY(v.x, v.y, testUnit2);
577         assertMatrix4dEquals(m1, m2, 1E-13);
578         Vector3d testUnit3 = Vector3d(0, 1, 0);
579         Vector3d t = m1.transformDirection(testUnit3);
580         Vector3d testUnit4 = Vector3d(-1, 1, 0).normalize();
581         assertVector3dEquals(testUnit4, t, 1E-6f);
582     }
583 
584     // testTestPoint
585     {
586         Matrix4d m = Matrix4d().perspective(cast(float)Math.toRadians(90), 1.0f, 0.1f, 10.0f).lookAt(0, 0, 10, 0, 0, 0, 0, 1, 0).scale(2);
587         assertTrue(m.testPoint(0, 0, 0));
588         assertTrue(m.testPoint(9.999f*0.5f, 0, 0));
589         assertFalse(m.testPoint(10.001f*0.5f, 0, 0));
590     }
591 
592     // testTestAab
593     {
594         Matrix4d m = Matrix4d().perspective(cast(float)Math.toRadians(90), 1.0f, 0.1f, 10.0f).lookAt(0, 0, 10, 0, 0, 0, 0, 1, 0).scale(2);
595         assertTrue(m.testAab(-1, -1, -1, 1, 1, 1));
596         assertTrue(m.testAab(9.999f*0.5f, 0, 0, 10, 1, 1));
597         assertFalse(m.testAab(10.001f*0.5f, 0, 0, 10, 1, 1));
598     }
599 
600     // testTransformTranspose
601     {
602         Matrix4d m = Matrix4d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
603         Matrix4d testUnit = Matrix4d();
604         Vector4d testUnit2 = Vector4d(4, 5, 6, 7);
605         Vector4d testUnit3 = Vector4d(4, 5, 6, 7);
606         assertVector4dEquals(
607                 m.transformTranspose(testUnit2), 
608                 m.transpose(testUnit).transform(testUnit3),
609                 1E-6f);
610     }
611 
612     // testGet
613     {
614         Matrix4d m = Matrix4d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
615         for (int c = 0; c < 4; c++)
616             for (int r = 0; r < 4; r++)
617                 assertEquals(c*4+r+1, m.get(c, r), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
618     }
619 
620     // testSet
621     {
622         assertMatrix4dEquals(Matrix4d().zero().set(0, 0, 3), Matrix4d(3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
623         assertMatrix4dEquals(Matrix4d().zero().set(0, 1, 3), Matrix4d(0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
624         assertMatrix4dEquals(Matrix4d().zero().set(0, 2, 3), Matrix4d(0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
625         assertMatrix4dEquals(Matrix4d().zero().set(0, 3, 3), Matrix4d(0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
626         assertMatrix4dEquals(Matrix4d().zero().set(1, 0, 3), Matrix4d(0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
627         assertMatrix4dEquals(Matrix4d().zero().set(1, 1, 3), Matrix4d(0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
628         assertMatrix4dEquals(Matrix4d().zero().set(1, 2, 3), Matrix4d(0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
629         assertMatrix4dEquals(Matrix4d().zero().set(1, 3, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
630         assertMatrix4dEquals(Matrix4d().zero().set(2, 0, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
631         assertMatrix4dEquals(Matrix4d().zero().set(2, 1, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
632         assertMatrix4dEquals(Matrix4d().zero().set(2, 2, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
633         assertMatrix4dEquals(Matrix4d().zero().set(2, 3, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
634         assertMatrix4dEquals(Matrix4d().zero().set(3, 0, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
635         assertMatrix4dEquals(Matrix4d().zero().set(3, 1, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
636         assertMatrix4dEquals(Matrix4d().zero().set(3, 2, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
637         assertMatrix4dEquals(Matrix4d().zero().set(3, 3, 3), Matrix4d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3), STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
638     }
639 
640     /**
641      * https://github.com/JOML-CI/JOML/issues/266
642      */
643     // testMulPerspectiveAffine
644     {
645         Matrix4d t = Matrix4d().lookAt(2, 3, 4, 5, 6, 7, 8, 9, 11);
646         Matrix4d p = Matrix4d().perspective(60.0f * cast(float)Math.PI / 180.0f, 4.0f/3.0f, 0.1f, 1000.0f);
647         Matrix4d testUnit1 = Matrix4d();
648         Matrix4d result1 = t.invertAffine(testUnit1);
649         Matrix4d testUnit2 = Matrix4d();
650         Matrix4d result2 = t.invertAffine(testUnit2);
651         p.mul(result1, result1);
652         p.mul0(result2, result2);
653         assertMatrix4dEquals(result1, result2, STANDARD_AROUND_ZERO_PRECISION_DOUBLE);
654     }
655 
656     // testSetPerspectiveOffCenterFov
657     {
658         Matrix4d m1 = Matrix4d().setPerspective(
659                 Math.toRadians(45),
660                 1,
661                 0.01,
662                 10.0);
663         Matrix4d m2 = Matrix4d().setPerspectiveOffCenterFov(
664                 -Math.toRadians(45/2.),
665                 Math.toRadians(45/2.),
666                 -Math.toRadians(45/2.),
667                 Math.toRadians(45/2.),
668                 0.01,
669                 10.0);
670         assertMatrix4dEquals(m1, m2, 1E-6);
671     }
672 
673     // testPerspectiveOffCenterFov
674     {
675         Matrix4d m1 = Matrix4d().perspective(
676                 Math.toRadians(45),
677                 1,
678                 0.01,
679                 10.0);
680         Matrix4d m2 = Matrix4d().perspectiveOffCenterFov(
681                 -Math.toRadians(45/2.),
682                 Math.toRadians(45/2.),
683                 -Math.toRadians(45/2.),
684                 Math.toRadians(45/2.),
685                 0.01,
686                 10.0);
687         assertMatrix4dEquals(m1, m2, 1E-6);
688     }
689 
690     writeln("PASSED!");
691 
692 }