1 /**
2 2D Transformation. 3x2 matrix.
3 
4 Copyright:
5 Copyright (c) 2007 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017 Godot-D contributors
8 Copyright (c) 2022 Godot-DLang contributors
9 
10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License)
11 
12 
13 */
14 module godot.projection;
15 
16 import godot.abi.types;
17 import godot.vector4;
18 import godot.vector2;
19 import godot.vector3;
20 import godot.rect2;
21 import godot.plane;
22 import godot.aabb;
23 import godot.array;
24 import godot.transform;
25 import godot.abi.core;
26 
27 import std.math;
28 import std.algorithm.comparison;
29 import std.algorithm.mutation : swap;
30 
31 double deg2rad(double p_y) @nogc nothrow {
32     return p_y * PI / 180.0;
33 }
34 
35 double rad2deg(double p_y) @nogc nothrow {
36     return p_y * 180.0 / PI;
37 }
38 
39 /**
40 Represents projection transformation. It is similar to a 4x4 matrix.
41 */
42 struct Projection {
43     // Array prohibits this, comment for now
44     //@nogc nothrow:
45 
46     enum Planes {
47         near,
48         far,
49         left,
50         top,
51         right,
52         bottom
53     }
54     // Convenience aliases
55     alias PLANE_NEAR = Planes.near;
56     alias PLANE_FAR = Planes.far;
57     alias PLANE_LEFT = Planes.left;
58     alias PLANE_TOP = Planes.top;
59     alias PLANE_RIGHT = Planes.right;
60     alias PLANE_BOTTOM = Planes.bottom;
61 
62     union {
63         Vector4[4] matrix = [
64             Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0),
65             Vector4(0, 0, 0, 1)
66         ];
67         struct {
68             Vector4 x_axis; /// 
69             Vector4 y_axis; /// 
70             Vector4 z_axis; /// 
71             Vector4 w_axis; /// 
72         }
73 
74         real_t[16] elements;
75     }
76 
77     private alias m = elements;
78 
79     this(Vector4 x, Vector4 y, Vector4 z, Vector4 w) {
80         matrix[0] = x;
81         matrix[1] = y;
82         matrix[2] = z;
83         matrix[3] = w;
84     }
85 
86     this(real_t[16] mat) {
87         elements = mat;
88     }
89 
90     this(in Transform3D p_transform) {
91         alias tr = p_transform;
92 
93         m[0] = tr.basis.rows[0][0];
94         m[1] = tr.basis.rows[1][0];
95         m[2] = tr.basis.rows[2][0];
96         m[3] = 0.0;
97         m[4] = tr.basis.rows[0][1];
98         m[5] = tr.basis.rows[1][1];
99         m[6] = tr.basis.rows[2][1];
100         m[7] = 0.0;
101         m[8] = tr.basis.rows[0][2];
102         m[9] = tr.basis.rows[1][2];
103         m[10] = tr.basis.rows[2][2];
104         m[11] = 0.0;
105         m[12] = tr.origin.x;
106         m[13] = tr.origin.y;
107         m[14] = tr.origin.z;
108         m[15] = 1.0;
109     }
110 
111     const(Vector4) opIndex(int axis) const {
112         return matrix[axis];
113     }
114 
115     ref Vector4 opIndex(int axis) return {
116         return matrix[axis];
117     }
118 
119     float determinant() const {
120         return matrix[0][3] * matrix[1][2] * matrix[2][1] * matrix[3][0] - matrix[0][2] * matrix[1][3] * matrix[2][1] * matrix[3][0] -
121             matrix[0][3] * matrix[1][1] * matrix[2][2] * matrix[3][0] + matrix[0][1] * matrix[1][3] * matrix[2][2] * matrix[3][0] +
122             matrix[0][2] * matrix[1][1] * matrix[2][3] * matrix[3][0] - matrix[0][1] * matrix[1][2] * matrix[2][3] * matrix[3][0] -
123             matrix[0][3] * matrix[1][2] * matrix[2][0] * matrix[3][1] + matrix[0][2] * matrix[1][3] * matrix[2][0] * matrix[3][1] +
124             matrix[0][3] * matrix[1][0] * matrix[2][2] * matrix[3][1] - matrix[0][0] * matrix[1][3] * matrix[2][2] * matrix[3][1] -
125             matrix[0][2] * matrix[1][0] * matrix[2][3] * matrix[3][1] + matrix[0][0] * matrix[1][2] * matrix[2][3] * matrix[3][1] +
126             matrix[0][3] * matrix[1][1] * matrix[2][0] * matrix[3][2] - matrix[0][1] * matrix[1][3] * matrix[2][0] * matrix[3][2] -
127             matrix[0][3] * matrix[1][0] * matrix[2][1] * matrix[3][2] + matrix[0][0] * matrix[1][3] * matrix[2][1] * matrix[3][2] +
128             matrix[0][1] * matrix[1][0] * matrix[2][3] * matrix[3][2] - matrix[0][0] * matrix[1][1] * matrix[2][3] * matrix[3][2] -
129             matrix[0][2] * matrix[1][1] * matrix[2][0] * matrix[3][3] + matrix[0][1] * matrix[1][2] * matrix[2][0] * matrix[3][3] +
130             matrix[0][2] * matrix[1][0] * matrix[2][1] * matrix[3][3] - matrix[0][0] * matrix[1][2] * matrix[2][1] * matrix[3][3] -
131             matrix[0][1] * matrix[1][0] * matrix[2][2] * matrix[3][3] + matrix[0][0] * matrix[1][1] * matrix[2][2] * matrix[3][3];
132     }
133 
134     void setIdentity() {
135         matrix = [
136             Vector4(1, 0, 0, 0), Vector4(0, 1, 0, 0), Vector4(0, 0, 1, 0),
137             Vector4(0, 0, 0, 1)
138         ];
139     }
140 
141     void setZero() {
142         elements[] = 0;
143     }
144 
145     void setLightBias() {
146         m[0] = 0.5;
147         m[1] = 0.0;
148         m[2] = 0.0;
149         m[3] = 0.0;
150         m[4] = 0.0;
151         m[5] = 0.5;
152         m[6] = 0.0;
153         m[7] = 0.0;
154         m[8] = 0.0;
155         m[9] = 0.0;
156         m[10] = 0.5;
157         m[11] = 0.0;
158         m[12] = 0.5;
159         m[13] = 0.5;
160         m[14] = 0.5;
161         m[15] = 1.0;
162     }
163 
164     void setDepthCorrection(bool p_flip_y = true) {
165         m[0] = 1;
166         m[1] = 0.0;
167         m[2] = 0.0;
168         m[3] = 0.0;
169         m[4] = 0.0;
170         m[5] = p_flip_y ? -1 : 1;
171         m[6] = 0.0;
172         m[7] = 0.0;
173         m[8] = 0.0;
174         m[9] = 0.0;
175         m[10] = 0.5;
176         m[11] = 0.0;
177         m[12] = 0.0;
178         m[13] = 0.0;
179         m[14] = 0.5;
180         m[15] = 1.0;
181     }
182 
183     void setLightAtlasRect(in Rect2 p_rect) {
184         m[0] = p_rect.size.width;
185         m[1] = 0.0;
186         m[2] = 0.0;
187         m[3] = 0.0;
188         m[4] = 0.0;
189         m[5] = p_rect.size.height;
190         m[6] = 0.0;
191         m[7] = 0.0;
192         m[8] = 0.0;
193         m[9] = 0.0;
194         m[10] = 1.0;
195         m[11] = 0.0;
196         m[12] = p_rect.position.x;
197         m[13] = p_rect.position.y;
198         m[14] = 0.0;
199         m[15] = 1.0;
200     }
201 
202     void setPerspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov = false) {
203         if (p_flip_fov) {
204             p_fovy_degrees = getFovY(p_fovy_degrees, 1.0 / p_aspect);
205         }
206 
207         real_t sine, cotangent, deltaZ;
208         real_t radians = deg2rad(p_fovy_degrees / 2.0);
209 
210         deltaZ = p_z_far - p_z_near;
211         sine = sin(radians);
212 
213         if ((deltaZ == 0) || (sine == 0) || (p_aspect == 0)) {
214             return;
215         }
216         cotangent = cos(radians) / sine;
217 
218         setIdentity();
219 
220         matrix[0][0] = cotangent / p_aspect;
221         matrix[1][1] = cotangent;
222         matrix[2][2] = -(p_z_far + p_z_near) / deltaZ;
223         matrix[2][3] = -1;
224         matrix[3][2] = -2 * p_z_near * p_z_far / deltaZ;
225         matrix[3][3] = 0;
226     }
227 
228     void setPerspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov, int p_eye, real_t p_intraocular_dist, real_t p_convergence_dist) {
229         if (p_flip_fov) {
230             p_fovy_degrees = getFovY(p_fovy_degrees, 1.0 / p_aspect);
231         }
232 
233         real_t left, right, modeltranslation, ymax, xmax, frustumshift;
234 
235         ymax = p_z_near * tan(deg2rad(p_fovy_degrees / 2.0));
236         xmax = ymax * p_aspect;
237         frustumshift = (p_intraocular_dist / 2.0) * p_z_near / p_convergence_dist;
238 
239         switch (p_eye) {
240         case 1: { // left eye
241                 left = -xmax + frustumshift;
242                 right = xmax + frustumshift;
243                 modeltranslation = p_intraocular_dist / 2.0;
244             }
245             break;
246         case 2: { // right eye
247                 left = -xmax - frustumshift;
248                 right = xmax - frustumshift;
249                 modeltranslation = -p_intraocular_dist / 2.0;
250             }
251             break;
252         default: { // mono, should give the same result as set_perspective(p_fovy_degrees,p_aspect,p_z_near,p_z_far,p_flip_fov)
253                 left = -xmax;
254                 right = xmax;
255                 modeltranslation = 0.0;
256             }
257             break;
258         }
259 
260         setFrustum(left, right, -ymax, ymax, p_z_near, p_z_far);
261 
262         // translate matrix by (modeltranslation, 0.0, 0.0)
263         Projection cm;
264         cm.setIdentity();
265         cm.matrix[3][0] = modeltranslation;
266         this = (this * cm);
267     }
268 
269     void setForHMD(int p_eye, real_t p_aspect, real_t p_intraocular_dist, real_t p_display_width, real_t p_display_to_lens, real_t p_oversample, real_t p_z_near, real_t p_z_far) {
270         // we first calculate our base frustum on our values without taking our lens magnification into account.
271         real_t f1 = (p_intraocular_dist * 0.5) / p_display_to_lens;
272         real_t f2 = ((p_display_width - p_intraocular_dist) * 0.5) / p_display_to_lens;
273         real_t f3 = (p_display_width / 4.0) / p_display_to_lens;
274 
275         // now we apply our oversample factor to increase our FOV. how much we oversample is always a balance we strike between performance and how much
276         // we're willing to sacrifice in FOV.
277         real_t add = ((f1 + f2) * (p_oversample - 1.0)) / 2.0;
278         f1 += add;
279         f2 += add;
280         f3 *= p_oversample;
281 
282         // always apply KEEP_WIDTH aspect ratio
283         f3 /= p_aspect;
284 
285         switch (p_eye) {
286         case 1: { // left eye
287                 setFrustum(-f2 * p_z_near, f1 * p_z_near, -f3 * p_z_near, f3 * p_z_near, p_z_near, p_z_far);
288             }
289             break;
290         case 2: { // right eye
291                 setFrustum(-f1 * p_z_near, f2 * p_z_near, -f3 * p_z_near, f3 * p_z_near, p_z_near, p_z_far);
292             }
293             break;
294         default: { // mono, does not apply here!
295             }
296             break;
297         }
298     }
299 
300     void setOrthogonal(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_znear, real_t p_zfar) {
301         setIdentity();
302 
303         matrix[0][0] = 2.0 / (p_right - p_left);
304         matrix[3][0] = -((p_right + p_left) / (p_right - p_left));
305         matrix[1][1] = 2.0 / (p_top - p_bottom);
306         matrix[3][1] = -((p_top + p_bottom) / (p_top - p_bottom));
307         matrix[2][2] = -2.0 / (p_zfar - p_znear);
308         matrix[3][2] = -((p_zfar + p_znear) / (p_zfar - p_znear));
309         matrix[3][3] = 1.0;
310     }
311 
312     void setOrthogonal(real_t p_size, real_t p_aspect, real_t p_znear, real_t p_zfar, bool p_flip_fov = false) {
313         if (!p_flip_fov) {
314             p_size *= p_aspect;
315         }
316 
317         setOrthogonal(-p_size / 2, +p_size / 2, -p_size / p_aspect / 2, +p_size / p_aspect / 2, p_znear, p_zfar);
318     }
319 
320     void setFrustum(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_near, real_t p_far) {
321         assert(p_right <= p_left);
322         assert(p_top <= p_bottom);
323         assert(p_far <= p_near);
324 
325         real_t x = 2 * p_near / (p_right - p_left);
326         real_t y = 2 * p_near / (p_top - p_bottom);
327 
328         real_t a = (p_right + p_left) / (p_right - p_left);
329         real_t b = (p_top + p_bottom) / (p_top - p_bottom);
330         real_t c = -(p_far + p_near) / (p_far - p_near);
331         real_t d = -2 * p_far * p_near / (p_far - p_near);
332 
333         m[0] = x;
334         m[1] = 0;
335         m[2] = 0;
336         m[3] = 0;
337         m[4] = 0;
338         m[5] = y;
339         m[6] = 0;
340         m[7] = 0;
341         m[8] = a;
342         m[9] = b;
343         m[10] = c;
344         m[11] = -1;
345         m[12] = 0;
346         m[13] = 0;
347         m[14] = d;
348         m[15] = 0;
349     }
350 
351     void setFrustum(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false) {
352         if (!p_flip_fov) {
353             p_size *= p_aspect;
354         }
355 
356         setFrustum(-p_size / 2 + p_offset.x, +p_size / 2 + p_offset.x, -p_size / p_aspect / 2 + p_offset.y, +p_size / p_aspect / 2 + p_offset
357                 .y, p_near, p_far);
358     }
359 
360     void adjustPerspectiveZNear(real_t p_new_znear) {
361         real_t zfar = getZFar();
362         real_t znear = p_new_znear;
363 
364         real_t deltaZ = zfar - znear;
365         matrix[2][2] = -(zfar + znear) / deltaZ;
366         matrix[3][2] = -2 * znear * zfar / deltaZ;
367     }
368 
369     static Projection createDepthCorrection(bool p_flip_y) {
370         Projection proj;
371         proj.setDepthCorrection(p_flip_y);
372         return proj;
373     }
374 
375     static Projection createLightAtlasRect(in Rect2 p_rect) {
376         Projection proj;
377         proj.setLightAtlasRect(p_rect);
378         return proj;
379     }
380 
381     static Projection createPerspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov = false) {
382         Projection proj;
383         proj.setPerspective(p_fovy_degrees, p_aspect, p_z_near, p_z_far, p_flip_fov);
384         return proj;
385     }
386 
387     static Projection createPerspectiveHMD(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov, int p_eye, real_t p_intraocular_dist, real_t p_convergence_dist) {
388         Projection proj;
389         proj.setPerspective(p_fovy_degrees, p_aspect, p_z_near, p_z_far, p_flip_fov, p_eye, p_intraocular_dist, p_convergence_dist);
390         return proj;
391     }
392 
393     static Projection createForHMD(int p_eye, real_t p_aspect, real_t p_intraocular_dist, real_t p_display_width, real_t p_display_to_lens, real_t p_oversample, real_t p_z_near, real_t p_z_far) {
394         Projection proj;
395         proj.setForHMD(p_eye, p_aspect, p_intraocular_dist, p_display_width, p_display_to_lens, p_oversample, p_z_near, p_z_far);
396         return proj;
397     }
398 
399     static Projection createOrthogonal(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_znear, real_t p_zfar) {
400         Projection proj;
401         proj.setOrthogonal(p_left, p_right, p_bottom, p_top, p_zfar, p_zfar);
402         return proj;
403     }
404 
405     static Projection createOrthogonalAspect(real_t p_size, real_t p_aspect, real_t p_znear, real_t p_zfar, bool p_flip_fov = false) {
406         Projection proj;
407         proj.setOrthogonal(p_size, p_aspect, p_znear, p_zfar, p_flip_fov);
408         return proj;
409     }
410 
411     static Projection createFrustum(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_near, real_t p_far) {
412         Projection proj;
413         proj.setFrustum(p_left, p_right, p_bottom, p_top, p_near, p_far);
414         return proj;
415     }
416 
417     static Projection createFrustumAspect(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false) {
418         Projection proj;
419         proj.setFrustum(p_size, p_aspect, p_offset, p_near, p_far, p_flip_fov);
420         return proj;
421     }
422 
423     static Projection createFitAABB(in AABB p_aabb) {
424         Projection proj;
425         proj.scaleTranslateToFit(p_aabb);
426         return proj;
427     }
428 
429     Projection perspective_znear_adjusted(real_t p_new_znear) const {
430         Projection proj = this;
431         proj.adjustPerspectiveZNear(p_new_znear);
432         return proj;
433     }
434 
435     Plane getProjectionPlane(Planes p_plane) const {
436         const real_t* matrix = m.ptr;
437 
438         switch (p_plane) {
439         case Planes.near: {
440                 Plane new_plane = Plane(matrix[3] + matrix[2],
441                     matrix[7] + matrix[6],
442                     matrix[11] + matrix[10],
443                     matrix[15] + matrix[14]);
444 
445                 new_plane.normal = -new_plane.normal;
446                 new_plane.normalize();
447                 return new_plane;
448             }
449         case Planes.far: {
450                 Plane new_plane = Plane(matrix[3] - matrix[2],
451                     matrix[7] - matrix[6],
452                     matrix[11] - matrix[10],
453                     matrix[15] - matrix[14]);
454 
455                 new_plane.normal = -new_plane.normal;
456                 new_plane.normalize();
457                 return new_plane;
458             }
459         case Planes.left: {
460                 Plane new_plane = Plane(matrix[3] + matrix[0],
461                     matrix[7] + matrix[4],
462                     matrix[11] + matrix[8],
463                     matrix[15] + matrix[12]);
464 
465                 new_plane.normal = -new_plane.normal;
466                 new_plane.normalize();
467                 return new_plane;
468             }
469         case Planes.top: {
470                 Plane new_plane = Plane(matrix[3] - matrix[1],
471                     matrix[7] - matrix[5],
472                     matrix[11] - matrix[9],
473                     matrix[15] - matrix[13]);
474 
475                 new_plane.normal = -new_plane.normal;
476                 new_plane.normalize();
477                 return new_plane;
478             }
479         case Planes.right: {
480                 Plane new_plane = Plane(matrix[3] - matrix[0],
481                     matrix[7] - matrix[4],
482                     matrix[11] - matrix[8],
483                     matrix[15] - matrix[12]);
484 
485                 new_plane.normal = -new_plane.normal;
486                 new_plane.normalize();
487                 return new_plane;
488             }
489         case Planes.bottom: {
490                 Plane new_plane = Plane(matrix[3] + matrix[1],
491                     matrix[7] + matrix[5],
492                     matrix[11] + matrix[9],
493                     matrix[15] + matrix[13]);
494 
495                 new_plane.normal = -new_plane.normal;
496                 new_plane.normalize();
497                 return new_plane;
498             }
499         default:
500             break;
501         }
502 
503         return Plane();
504     }
505 
506     Projection flippedY() const {
507         Projection proj = this;
508         proj.flipY();
509         return proj;
510     }
511 
512     Projection jitter_offseted(in Vector2 p_offset) const {
513         Projection proj = this;
514         proj.addJitterOffset(p_offset);
515         return proj;
516     }
517 
518     static real_t getFovY(real_t p_fovx, real_t p_aspect) {
519         return rad2deg(atan(p_aspect * tan(deg2rad(p_fovx) * 0.5)) * 2.0);
520     }
521 
522     real_t getZFar() const {
523         const real_t* matrix = m.ptr;
524         Plane new_plane = Plane(matrix[3] - matrix[2],
525             matrix[7] - matrix[6],
526             matrix[11] - matrix[10],
527             matrix[15] - matrix[14]);
528 
529         new_plane.normal = -new_plane.normal;
530         new_plane.normalize();
531 
532         return new_plane.d;
533     }
534 
535     real_t getZNear() const {
536         const real_t* matrix = m.ptr;
537         Plane new_plane = Plane(matrix[3] + matrix[2],
538             matrix[7] + matrix[6],
539             matrix[11] + matrix[10],
540             -matrix[15] - matrix[14]);
541 
542         new_plane.normalize();
543         return new_plane.d;
544     }
545 
546     real_t getAspect() const {
547         Vector2 vp_he = getViewportHalfExtents();
548         return vp_he.x / vp_he.y;
549     }
550 
551     real_t getFov() const {
552         const real_t* matrix = m.ptr;
553 
554         Plane right_plane = Plane(matrix[3] - matrix[0],
555             matrix[7] - matrix[4],
556             matrix[11] - matrix[8],
557             -matrix[15] + matrix[12]);
558         right_plane.normalize();
559 
560         if ((matrix[8] == 0) && (matrix[9] == 0)) {
561             return rad2deg(acos(abs(right_plane.normal.x))) * 2.0;
562         } else {
563             // our frustum is asymmetrical need to calculate the left planes angle separately..
564             Plane left_plane = Plane(matrix[3] + matrix[0],
565                 matrix[7] + matrix[4],
566                 matrix[11] + matrix[8],
567                 matrix[15] + matrix[12]);
568             left_plane.normalize();
569 
570             return rad2deg(acos(abs(left_plane.normal.x))) + rad2deg(
571                 acos(abs(right_plane.normal.x)));
572         }
573     }
574 
575     bool isOrthogonal() const {
576         return matrix[3][3] == 1.0;
577     }
578 
579     Array getProjectionPlanes(in Transform3D p_transform) const {
580         /** Fast Plane Extraction from combined modelview/projection matrices.
581 		* References:
582 		* https://web.archive.org/web/20011221205252/https://www.markmorley.com/opengl/frustumculling.html
583 		* https://web.archive.org/web/20061020020112/https://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf
584 		*/
585 
586         Array planes;
587 
588         const real_t* matrix = m.ptr;
589 
590         Plane new_plane;
591 
592         ///////--- Near Plane ---///////
593         new_plane = Plane(matrix[3] + matrix[2],
594             matrix[7] + matrix[6],
595             matrix[11] + matrix[10],
596             matrix[15] + matrix[14]);
597 
598         new_plane.normal = -new_plane.normal;
599         new_plane.normalize();
600 
601         planes.pushBack(p_transform.xform(new_plane));
602 
603         ///////--- Far Plane ---///////
604         new_plane = Plane(matrix[3] - matrix[2],
605             matrix[7] - matrix[6],
606             matrix[11] - matrix[10],
607             matrix[15] - matrix[14]);
608 
609         new_plane.normal = -new_plane.normal;
610         new_plane.normalize();
611 
612         planes.pushBack(p_transform.xform(new_plane));
613 
614         ///////--- Left Plane ---///////
615         new_plane = Plane(matrix[3] + matrix[0],
616             matrix[7] + matrix[4],
617             matrix[11] + matrix[8],
618             matrix[15] + matrix[12]);
619 
620         new_plane.normal = -new_plane.normal;
621         new_plane.normalize();
622 
623         planes.pushBack(p_transform.xform(new_plane));
624 
625         ///////--- Top Plane ---///////
626         new_plane = Plane(matrix[3] - matrix[1],
627             matrix[7] - matrix[5],
628             matrix[11] - matrix[9],
629             matrix[15] - matrix[13]);
630 
631         new_plane.normal = -new_plane.normal;
632         new_plane.normalize();
633 
634         planes.pushBack(p_transform.xform(new_plane));
635 
636         ///////--- Right Plane ---///////
637         new_plane = Plane(matrix[3] - matrix[0],
638             matrix[7] - matrix[4],
639             matrix[11] - matrix[8],
640             matrix[15] - matrix[12]);
641 
642         new_plane.normal = -new_plane.normal;
643         new_plane.normalize();
644 
645         planes.pushBack(p_transform.xform(new_plane));
646 
647         ///////--- Bottom Plane ---///////
648         new_plane = Plane(matrix[3] + matrix[1],
649             matrix[7] + matrix[5],
650             matrix[11] + matrix[9],
651             matrix[15] + matrix[13]);
652 
653         new_plane.normal = -new_plane.normal;
654         new_plane.normalize();
655 
656         planes.pushBack(p_transform.xform(new_plane));
657 
658         return planes;
659     }
660 
661     bool getEndpoints(in Transform3D p_transform, Vector3* p_8points) const {
662         import std.array : staticArray;
663 
664         Array planes = getProjectionPlanes(Transform3D());
665         const Planes[3][8] intersections = [
666             [PLANE_FAR, PLANE_LEFT, PLANE_TOP],
667             [PLANE_FAR, PLANE_LEFT, PLANE_BOTTOM],
668             [PLANE_FAR, PLANE_RIGHT, PLANE_TOP],
669             [PLANE_FAR, PLANE_RIGHT, PLANE_BOTTOM],
670             [PLANE_NEAR, PLANE_LEFT, PLANE_TOP],
671             [PLANE_NEAR, PLANE_LEFT, PLANE_BOTTOM],
672             [PLANE_NEAR, PLANE_RIGHT, PLANE_TOP],
673             [PLANE_NEAR, PLANE_RIGHT, PLANE_BOTTOM],
674         ];
675 
676         for (int i = 0; i < 8; i++) {
677             Vector3 point;
678             bool res = (planes[intersections[i][0]].as!Plane).intersect3(
679                 planes[intersections[i][1]].as!Plane,
680                 planes[intersections[i][2]].as!Plane,
681                 &point);
682             //ERR_FAIL_COND_V(!res, false);
683             p_8points[i] = p_transform.xform(point);
684         }
685 
686         return true;
687     }
688 
689     Vector2 getViewportHalfExtents() const {
690         const real_t* matrix = m.ptr;
691         ///////--- Near Plane ---///////
692         Plane near_plane = Plane(matrix[3] + matrix[2],
693             matrix[7] + matrix[6],
694             matrix[11] + matrix[10],
695             -matrix[15] - matrix[14]);
696         near_plane.normalize();
697 
698         ///////--- Right Plane ---///////
699         Plane right_plane = Plane(matrix[3] - matrix[0],
700             matrix[7] - matrix[4],
701             matrix[11] - matrix[8],
702             -matrix[15] + matrix[12]);
703         right_plane.normalize();
704 
705         Plane top_plane = Plane(matrix[3] - matrix[1],
706             matrix[7] - matrix[5],
707             matrix[11] - matrix[9],
708             -matrix[15] + matrix[13]);
709         top_plane.normalize();
710 
711         Vector3 res;
712         near_plane.intersect3(right_plane, top_plane, &res);
713 
714         return Vector2(res.x, res.y);
715     }
716 
717     Vector2 getFarPlaneHalfExtents() const {
718         const real_t* matrix = m.ptr;
719         ///////--- Far Plane ---///////
720         Plane far_plane = Plane(matrix[3] - matrix[2],
721             matrix[7] - matrix[6],
722             matrix[11] - matrix[10],
723             -matrix[15] + matrix[14]);
724         far_plane.normalize();
725 
726         ///////--- Right Plane ---///////
727         Plane right_plane = Plane(matrix[3] - matrix[0],
728             matrix[7] - matrix[4],
729             matrix[11] - matrix[8],
730             -matrix[15] + matrix[12]);
731         right_plane.normalize();
732 
733         Plane top_plane = Plane(matrix[3] - matrix[1],
734             matrix[7] - matrix[5],
735             matrix[11] - matrix[9],
736             -matrix[15] + matrix[13]);
737         top_plane.normalize();
738 
739         Vector3 res;
740         far_plane.intersect3(right_plane, top_plane, &res);
741 
742         return Vector2(res.x, res.y);
743     }
744 
745     void invert() {
746         int i, j, k;
747         int[4] pvt_i, pvt_j; /* Locations of pivot matrix */
748         real_t pvt_val; /* Value of current pivot element */
749         real_t hold; /* Temporary storage */
750         real_t determinant = 1.0f;
751         for (k = 0; k < 4; k++) {
752             /** Locate k'th pivot element **/
753             pvt_val = matrix[k][k]; /** Initialize for search **/
754             pvt_i[k] = k;
755             pvt_j[k] = k;
756             for (i = k; i < 4; i++) {
757                 for (j = k; j < 4; j++) {
758                     if (abs(matrix[i][j]) > abs(pvt_val)) {
759                         pvt_i[k] = i;
760                         pvt_j[k] = j;
761                         pvt_val = matrix[i][j];
762                     }
763                 }
764             }
765 
766             /** Product of pivots, gives determinant when finished **/
767             determinant *= pvt_val;
768             if (isClose(determinant, 0)) {
769                 return; /** Matrix is singular (zero determinant). **/
770             }
771 
772             /** "Interchange" elements (with sign change stuff) **/
773             i = pvt_i[k];
774             if (i != k) { /** If elements are different **/
775                 for (j = 0; j < 4; j++) {
776                     hold = -matrix[k][j];
777                     matrix[k][j] = matrix[i][j];
778                     matrix[i][j] = hold;
779                 }
780             }
781 
782             /** "Interchange" columns **/
783             j = pvt_j[k];
784             if (j != k) { /** If columns are different **/
785                 for (i = 0; i < 4; i++) {
786                     hold = -matrix[i][k];
787                     matrix[i][k] = matrix[i][j];
788                     matrix[i][j] = hold;
789                 }
790             }
791 
792             /** Divide column by minus pivot value **/
793             for (i = 0; i < 4; i++) {
794                 if (i != k) {
795                     matrix[i][k] /= (-pvt_val);
796                 }
797             }
798 
799             /** Reduce the matrix **/
800             for (i = 0; i < 4; i++) {
801                 hold = matrix[i][k];
802                 for (j = 0; j < 4; j++) {
803                     if (i != k && j != k) {
804                         matrix[i][j] += hold * matrix[k][j];
805                     }
806                 }
807             }
808 
809             /** Divide row by pivot **/
810             for (j = 0; j < 4; j++) {
811                 if (j != k) {
812                     matrix[k][j] /= pvt_val;
813                 }
814             }
815 
816             /** Replace pivot by reciprocal (at last we can touch it). **/
817             matrix[k][k] = 1.0 / pvt_val;
818         }
819 
820         /* That was most of the work, one final pass of row/column interchange */
821         /* to finish */
822         for (k = 4 - 2; k >= 0; k--) { /* Don't need to work with 1 by 1 corner*/
823             i = pvt_j[k]; /* Rows to swap correspond to pivot COLUMN */
824             if (i != k) { /* If elements are different */
825                 for (j = 0; j < 4; j++) {
826                     hold = matrix[k][j];
827                     matrix[k][j] = -matrix[i][j];
828                     matrix[i][j] = hold;
829                 }
830             }
831 
832             j = pvt_i[k]; /* Columns to swap correspond to pivot ROW */
833             if (j != k) { /* If columns are different */
834                 for (i = 0; i < 4; i++) {
835                     hold = matrix[i][k];
836                     matrix[i][k] = -matrix[i][j];
837                     matrix[i][j] = hold;
838                 }
839             }
840         }
841     }
842 
843     Projection inverse() const {
844         Projection cm = this;
845         cm.invert();
846         return cm;
847     }
848 
849     Projection opBinary(string op : "*")(in Projection p_matrix) const {
850         Projection new_matrix;
851 
852         for (int j = 0; j < 4; j++) {
853             for (int i = 0; i < 4; i++) {
854                 real_t ab = 0;
855                 for (int k = 0; k < 4; k++) {
856                     ab += matrix[k][i] * p_matrix.matrix[j][k];
857                 }
858                 new_matrix.matrix[j][i] = ab;
859             }
860         }
861 
862         return new_matrix;
863     }
864 
865     Plane xform4(in Plane p_vec4) const {
866         Plane ret;
867 
868         ret.normal.x = matrix[0][0] * p_vec4.normal.x + matrix[1][0] * p_vec4.normal.y + matrix[2][0] * p_vec4
869             .normal.z + matrix[3][0] * p_vec4.d;
870         ret.normal.y = matrix[0][1] * p_vec4.normal.x + matrix[1][1] * p_vec4.normal.y + matrix[2][1] * p_vec4
871             .normal.z + matrix[3][1] * p_vec4.d;
872         ret.normal.z = matrix[0][2] * p_vec4.normal.x + matrix[1][2] * p_vec4.normal.y + matrix[2][2] * p_vec4
873             .normal.z + matrix[3][2] * p_vec4.d;
874         ret.d = matrix[0][3] * p_vec4.normal.x + matrix[1][3] * p_vec4.normal.y + matrix[2][3] * p_vec4.normal.z + matrix[3][3] * p_vec4
875             .d;
876         return ret;
877     }
878 
879     Vector3 xform(in Vector3 p_vec3) const {
880         Vector3 ret;
881         ret.x = matrix[0][0] * p_vec3.x + matrix[1][0] * p_vec3.y + matrix[2][0] * p_vec3.z + matrix[3][0];
882         ret.y = matrix[0][1] * p_vec3.x + matrix[1][1] * p_vec3.y + matrix[2][1] * p_vec3.z + matrix[3][1];
883         ret.z = matrix[0][2] * p_vec3.x + matrix[1][2] * p_vec3.y + matrix[2][2] * p_vec3.z + matrix[3][2];
884         real_t w = matrix[0][3] * p_vec3.x + matrix[1][3] * p_vec3.y + matrix[2][3] * p_vec3.z + matrix[3][3];
885         return ret / w;
886     }
887 
888     Vector4 xform(in Vector4 p_vec4) const {
889         return Vector4(
890             matrix[0][0] * p_vec4.x + matrix[1][0] * p_vec4.y + matrix[2][0] * p_vec4.z + matrix[3][0] * p_vec4.w,
891             matrix[0][1] * p_vec4.x + matrix[1][1] * p_vec4.y + matrix[2][1] * p_vec4.z + matrix[3][1] * p_vec4.w,
892             matrix[0][2] * p_vec4.x + matrix[1][2] * p_vec4.y + matrix[2][2] * p_vec4.z + matrix[3][2] * p_vec4.w,
893             matrix[0][3] * p_vec4.x + matrix[1][3] * p_vec4.y + matrix[2][3] * p_vec4.z + matrix[3][3] * p_vec4
894                 .w);
895     }
896 
897     Vector4 xform_inv(in Vector4 p_vec4) const {
898         return Vector4(
899             matrix[0][0] * p_vec4.x + matrix[0][1] * p_vec4.y + matrix[0][2] * p_vec4.z + matrix[0][3] * p_vec4.w,
900             matrix[1][0] * p_vec4.x + matrix[1][1] * p_vec4.y + matrix[1][2] * p_vec4.z + matrix[1][3] * p_vec4.w,
901             matrix[2][0] * p_vec4.x + matrix[2][1] * p_vec4.y + matrix[2][2] * p_vec4.z + matrix[2][3] * p_vec4.w,
902             matrix[3][0] * p_vec4.x + matrix[3][1] * p_vec4.y + matrix[3][2] * p_vec4.z + matrix[3][3] * p_vec4
903                 .w);
904     }
905 
906     void scaleTranslateToFit(in AABB p_aabb) {
907         Vector3 min = p_aabb.position;
908         Vector3 max = p_aabb.position + p_aabb.size;
909 
910         matrix[0][0] = 2 / (max.x - min.x);
911         matrix[1][0] = 0;
912         matrix[2][0] = 0;
913         matrix[3][0] = -(max.x + min.x) / (max.x - min.x);
914 
915         matrix[0][1] = 0;
916         matrix[1][1] = 2 / (max.y - min.y);
917         matrix[2][1] = 0;
918         matrix[3][1] = -(max.y + min.y) / (max.y - min.y);
919 
920         matrix[0][2] = 0;
921         matrix[1][2] = 0;
922         matrix[2][2] = 2 / (max.z - min.z);
923         matrix[3][2] = -(max.z + min.z) / (max.z - min.z);
924 
925         matrix[0][3] = 0;
926         matrix[1][3] = 0;
927         matrix[2][3] = 0;
928         matrix[3][3] = 1;
929     }
930 
931     void addJitterOffset(in Vector2 p_offset) {
932         matrix[3][0] += p_offset.x;
933         matrix[3][1] += p_offset.y;
934     }
935 
936     void makeScale(in Vector3 p_scale) {
937         setIdentity();
938         matrix[0][0] = p_scale.x;
939         matrix[1][1] = p_scale.y;
940         matrix[2][2] = p_scale.z;
941     }
942 
943     int get_pixels_per_meter(int p_for_pixel_width) const {
944         Vector3 result = xform(Vector3(1, 0, -1));
945 
946         return cast(int)((result.x * 0.5 + 0.5) * p_for_pixel_width);
947     }
948 
949     Transform3D opCast(Transform3D)() const {
950         Transform3D tr;
951         const real_t* matrix = m.ptr;
952 
953         tr.basis.rows[0][0] = m[0];
954         tr.basis.rows[1][0] = m[1];
955         tr.basis.rows[2][0] = m[2];
956 
957         tr.basis.rows[0][1] = m[4];
958         tr.basis.rows[1][1] = m[5];
959         tr.basis.rows[2][1] = m[6];
960 
961         tr.basis.rows[0][2] = m[8];
962         tr.basis.rows[1][2] = m[9];
963         tr.basis.rows[2][2] = m[10];
964 
965         tr.origin.x = m[12];
966         tr.origin.y = m[13];
967         tr.origin.z = m[14];
968 
969         return tr;
970     }
971 
972     void flipY() {
973         for (int i = 0; i < 4; i++) {
974             matrix[1][i] = -matrix[1][i];
975         }
976     }
977 
978     bool opEquals(const Projection p_cam) const {
979         for (uint32_t i = 0; i < 4; i++) {
980             for (uint32_t j = 0; j < 4; j++) {
981                 if (matrix[i][j] != p_cam.matrix[i][j]) {
982                     return false;
983                 }
984             }
985         }
986         return true;
987     }
988 
989     float getLODMultiplier() const {
990         if (isOrthogonal()) {
991             return getViewportHalfExtents().x;
992         } else {
993             float zn = getZNear();
994             float width = getViewportHalfExtents().x * 2.0;
995             return 1.0 / (zn / width);
996         }
997 
998         // usage is lod_size / (lod_distance * multiplier) < threshold
999     }
1000 }