diff --git a/src/mat4-impl.ts b/src/mat4-impl.ts index b26db8a..48aa379 100644 --- a/src/mat4-impl.ts +++ b/src/mat4-impl.ts @@ -776,13 +776,68 @@ export function perspective(fieldOfViewYInRadians: number, aspect: number, zNear dst[13] = 0; dst[15] = 0; - if (zFar === Infinity) { - dst[10] = -1; - dst[14] = -zNear; - } else { + if (Number.isFinite(zFar)) { const rangeInv = 1 / (zNear - zFar); dst[10] = zFar * rangeInv; dst[14] = zFar * zNear * rangeInv; + } else { + dst[10] = -1; + dst[14] = -zNear; + } + + return dst; +} + +/** + * Computes a 4-by-4 perspective transformation matrix given the angular height + * of the frustum, the aspect ratio, and the near and far clipping planes. The + * arguments define a frustum extending in the negative z direction. The given + * angle is the vertical angle of the frustum, and the horizontal angle is + * determined to produce the given aspect ratio. The arguments near and far are + * the distances to the near and far clipping planes. Note that near and far + * are not z coordinates, but rather they are distances along the negative + * z-axis. The matrix generated sends the viewing frustum to the unit box. + * We assume a unit box extending from -1 to 1 in the x and y dimensions and + * from 1 (at -zNear) to 0 (at -zFar) in the z dimension. + * + * @param fieldOfViewYInRadians - The camera angle from top to bottom (in radians). + * @param aspect - The aspect ratio width / height. + * @param zNear - The depth (negative z coordinate) + * of the near clipping plane. + * @param zFar - The depth (negative z coordinate) + * of the far clipping plane. (default = Infinity) + * @param dst - matrix to hold result. If not passed a new one is created. + * @returns The perspective matrix. + */export function perspectiveReverseZ(fieldOfViewYInRadians: number, aspect: number, zNear: number, zFar = Infinity, dst?: Mat4) { + dst = dst || new MatType(16); + + const f = 1 / Math.tan(fieldOfViewYInRadians * 0.5); + + dst[ 0] = f / aspect; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + + dst[ 4] = 0; + dst[ 5] = f; + dst[ 6] = 0; + dst[ 7] = 0; + + dst[ 8] = 0; + dst[ 9] = 0; + dst[11] = -1; + + dst[12] = 0; + dst[13] = 0; + dst[15] = 0; + + if (Number.isFinite(zFar)) { + const rangeInv = 1 / (zFar - zNear); + dst[10] = zNear * rangeInv; + dst[14] = zFar * zNear * rangeInv; + } else { + dst[10] = 0; + dst[14] = zNear; } return dst; diff --git a/test/tests/mat4-test.js b/test/tests/mat4-test.js index d4494ad..859d8aa 100644 --- a/test/tests/mat4-test.js +++ b/test/tests/mat4-test.js @@ -474,6 +474,95 @@ function check(Type) { shouldBeCloseArray(vec3.transformMat4([0, 0, zFar], m), [0, 0, Infinity], 0.000001); }); + it('should compute perspective reverseZ with zFar', () => { + const fov = 2; + const aspect = 4; + const zNear = 10; + const zFar = 20; + const f = Math.tan(Math.PI * 0.5 - 0.5 * fov); + const rangeInv = 1 / (zFar - zNear); + const expected = [ + f / aspect, + 0, + 0, + 0, + + 0, + f, + 0, + 0, + + 0, + 0, + zNear * rangeInv, + -1, + + 0, + 0, + zFar * zNear * rangeInv, + 0, + ]; + testMat4WithAndWithoutDest((dst) => { + return mat4.perspectiveReverseZ(fov, aspect, zNear, zFar, dst); + }, expected); + }); + + it('should compute correct perspective reverseZ', () => { + const fov = Math.PI / 4; + const aspect = 2; + const zNear = 10; + const zFar = 20; + const m = mat4.perspectiveReverseZ(fov, aspect, zNear, zFar); + shouldBeCloseArray(vec3.transformMat4([0, 0, -zNear], m), [0, 0, 1], 0.000001); + shouldBeCloseArray(vec3.transformMat4([0, 0, -15], m), [0, 0, 0.3333333432674408], 0.000001); + shouldBeCloseArray(vec3.transformMat4([0, 0, -zFar], m), [0, 0, 0], 0.000001); + }); + + it('should compute perspective reverseZ with zFar at infinity', () => { + const fov = 2; + const aspect = 4; + const zNear = 10; + const zFar = Infinity; + const f = Math.tan(Math.PI * 0.5 - 0.5 * fov); + const expected = [ + f / aspect, + 0, + 0, + 0, + + 0, + f, + 0, + 0, + + 0, + 0, + 0, + -1, + + 0, + 0, + zNear, + 0, + ]; + testMat4WithAndWithoutDest((dst) => { + return mat4.perspectiveReverseZ(fov, aspect, zNear, zFar, dst); + }, expected); + }); + + it('should compute correct perspective reverseZ with zFar at Infinity', () => { + const fov = Math.PI / 4; + const aspect = 2; + const zNear = 10; + const zFar = Infinity; + const m = mat4.perspectiveReverseZ(fov, aspect, zNear, zFar); + shouldBeCloseArray(vec3.transformMat4([0, 0, -zNear], m), [0, 0, 1], 0.000001); + shouldBeCloseArray(vec3.transformMat4([0, 0, -1000], m), [0, 0, 0.009999999776482582], 0.000001); + shouldBeCloseArray(vec3.transformMat4([0, 0, -1000000], m), [0, 0, 0.000009999999747378752], 0.000001); + shouldBeCloseArray(vec3.transformMat4([0, 0, -1000000000], m), [0, 0, 9.99999993922529e-9], 0.000001); + shouldBeCloseArray(vec3.transformMat4([0, 0, -zFar], m), [0, 0, 0], 0.000001); + }); + it('should compute ortho', () => { const left = 2; const right = 4;