Skip to content

Commit 3f8c132

Browse files
committed
Add mat4.perspectiveReverseZ
1 parent db4afec commit 3f8c132

File tree

2 files changed

+148
-4
lines changed

2 files changed

+148
-4
lines changed

src/mat4-impl.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -776,13 +776,68 @@ export function perspective(fieldOfViewYInRadians: number, aspect: number, zNear
776776
dst[13] = 0;
777777
dst[15] = 0;
778778

779-
if (zFar === Infinity) {
780-
dst[10] = -1;
781-
dst[14] = -zNear;
782-
} else {
779+
if (Number.isFinite(zFar)) {
783780
const rangeInv = 1 / (zNear - zFar);
784781
dst[10] = zFar * rangeInv;
785782
dst[14] = zFar * zNear * rangeInv;
783+
} else {
784+
dst[10] = -1;
785+
dst[14] = -zNear;
786+
}
787+
788+
return dst;
789+
}
790+
791+
/**
792+
* Computes a 4-by-4 perspective transformation matrix given the angular height
793+
* of the frustum, the aspect ratio, and the near and far clipping planes. The
794+
* arguments define a frustum extending in the negative z direction. The given
795+
* angle is the vertical angle of the frustum, and the horizontal angle is
796+
* determined to produce the given aspect ratio. The arguments near and far are
797+
* the distances to the near and far clipping planes. Note that near and far
798+
* are not z coordinates, but rather they are distances along the negative
799+
* z-axis. The matrix generated sends the viewing frustum to the unit box.
800+
* We assume a unit box extending from -1 to 1 in the x and y dimensions and
801+
* from 1 (at -zNear) to 0 (at -zFar) in the z dimension.
802+
*
803+
* @param fieldOfViewYInRadians - The camera angle from top to bottom (in radians).
804+
* @param aspect - The aspect ratio width / height.
805+
* @param zNear - The depth (negative z coordinate)
806+
* of the near clipping plane.
807+
* @param zFar - The depth (negative z coordinate)
808+
* of the far clipping plane. (default = Infinity)
809+
* @param dst - matrix to hold result. If not passed a new one is created.
810+
* @returns The perspective matrix.
811+
*/export function perspectiveReverseZ(fieldOfViewYInRadians: number, aspect: number, zNear: number, zFar = Infinity, dst?: Mat4) {
812+
dst = dst || new MatType(16);
813+
814+
const f = 1 / Math.tan(fieldOfViewYInRadians * 0.5);
815+
816+
dst[ 0] = f / aspect;
817+
dst[ 1] = 0;
818+
dst[ 2] = 0;
819+
dst[ 3] = 0;
820+
821+
dst[ 4] = 0;
822+
dst[ 5] = f;
823+
dst[ 6] = 0;
824+
dst[ 7] = 0;
825+
826+
dst[ 8] = 0;
827+
dst[ 9] = 0;
828+
dst[11] = -1;
829+
830+
dst[12] = 0;
831+
dst[13] = 0;
832+
dst[15] = 0;
833+
834+
if (Number.isFinite(zFar)) {
835+
const rangeInv = 1 / (zFar - zNear);
836+
dst[10] = zNear * rangeInv;
837+
dst[14] = zFar * zNear * rangeInv;
838+
} else {
839+
dst[10] = 0;
840+
dst[14] = zNear;
786841
}
787842

788843
return dst;

test/tests/mat4-test.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,95 @@ function check(Type) {
474474
shouldBeCloseArray(vec3.transformMat4([0, 0, zFar], m), [0, 0, Infinity], 0.000001);
475475
});
476476

477+
it('should compute perspective reverseZ with zFar', () => {
478+
const fov = 2;
479+
const aspect = 4;
480+
const zNear = 10;
481+
const zFar = 20;
482+
const f = Math.tan(Math.PI * 0.5 - 0.5 * fov);
483+
const rangeInv = 1 / (zFar - zNear);
484+
const expected = [
485+
f / aspect,
486+
0,
487+
0,
488+
0,
489+
490+
0,
491+
f,
492+
0,
493+
0,
494+
495+
0,
496+
0,
497+
zNear * rangeInv,
498+
-1,
499+
500+
0,
501+
0,
502+
zFar * zNear * rangeInv,
503+
0,
504+
];
505+
testMat4WithAndWithoutDest((dst) => {
506+
return mat4.perspectiveReverseZ(fov, aspect, zNear, zFar, dst);
507+
}, expected);
508+
});
509+
510+
it('should compute correct perspective reverseZ', () => {
511+
const fov = Math.PI / 4;
512+
const aspect = 2;
513+
const zNear = 10;
514+
const zFar = 20;
515+
const m = mat4.perspectiveReverseZ(fov, aspect, zNear, zFar);
516+
shouldBeCloseArray(vec3.transformMat4([0, 0, -zNear], m), [0, 0, 1], 0.000001);
517+
shouldBeCloseArray(vec3.transformMat4([0, 0, -15], m), [0, 0, 0.3333333432674408], 0.000001);
518+
shouldBeCloseArray(vec3.transformMat4([0, 0, -zFar], m), [0, 0, 0], 0.000001);
519+
});
520+
521+
it('should compute perspective reverseZ with zFar at infinity', () => {
522+
const fov = 2;
523+
const aspect = 4;
524+
const zNear = 10;
525+
const zFar = Infinity;
526+
const f = Math.tan(Math.PI * 0.5 - 0.5 * fov);
527+
const expected = [
528+
f / aspect,
529+
0,
530+
0,
531+
0,
532+
533+
0,
534+
f,
535+
0,
536+
0,
537+
538+
0,
539+
0,
540+
0,
541+
-1,
542+
543+
0,
544+
0,
545+
zNear,
546+
0,
547+
];
548+
testMat4WithAndWithoutDest((dst) => {
549+
return mat4.perspectiveReverseZ(fov, aspect, zNear, zFar, dst);
550+
}, expected);
551+
});
552+
553+
it('should compute correct perspective reverseZ with zFar at Infinity', () => {
554+
const fov = Math.PI / 4;
555+
const aspect = 2;
556+
const zNear = 10;
557+
const zFar = Infinity;
558+
const m = mat4.perspectiveReverseZ(fov, aspect, zNear, zFar);
559+
shouldBeCloseArray(vec3.transformMat4([0, 0, -zNear], m), [0, 0, 1], 0.000001);
560+
shouldBeCloseArray(vec3.transformMat4([0, 0, -1000], m), [0, 0, 0.009999999776482582], 0.000001);
561+
shouldBeCloseArray(vec3.transformMat4([0, 0, -1000000], m), [0, 0, 0.000009999999747378752], 0.000001);
562+
shouldBeCloseArray(vec3.transformMat4([0, 0, -1000000000], m), [0, 0, 9.99999993922529e-9], 0.000001);
563+
shouldBeCloseArray(vec3.transformMat4([0, 0, -zFar], m), [0, 0, 0], 0.000001);
564+
});
565+
477566
it('should compute ortho', () => {
478567
const left = 2;
479568
const right = 4;

0 commit comments

Comments
 (0)