diff --git a/src/mat4-impl.ts b/src/mat4-impl.ts index 48aa379..a5c4a2e 100644 --- a/src/mat4-impl.ts +++ b/src/mat4-impl.ts @@ -789,7 +789,7 @@ export function perspective(fieldOfViewYInRadians: number, aspect: number, zNear } /** - * Computes a 4-by-4 perspective transformation matrix given the angular height + * Computes a 4-by-4 reverse-z 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 @@ -831,13 +831,13 @@ export function perspective(fieldOfViewYInRadians: number, aspect: number, zNear dst[13] = 0; dst[15] = 0; - if (Number.isFinite(zFar)) { + if (zFar === Infinity) { + dst[10] = 0; + dst[14] = zNear; + } else { const rangeInv = 1 / (zFar - zNear); dst[10] = zNear * rangeInv; dst[14] = zFar * zNear * rangeInv; - } else { - dst[10] = 0; - dst[14] = zNear; } return dst; @@ -929,6 +929,57 @@ export function frustum(left: number, right: number, bottom: number, top: number return dst; } +/** + * Computes a 4-by-4 reverse-z perspective transformation matrix given the left, right, + * top, bottom, near and far clipping planes. The arguments define a frustum + * extending in the negative z direction. 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 (-near) to 0 (-far) in the z + * dimension. + * @param left - The x coordinate of the left plane of the box. + * @param right - The x coordinate of the right plane of the box. + * @param bottom - The y coordinate of the bottom plane of the box. + * @param top - The y coordinate of the right plane of the box. + * @param near - The negative z coordinate of the near plane of the box. + * @param far - The negative z coordinate of the far plane of the box. + * @param dst - Output matrix. If not passed a new one is created. + * @returns The perspective projection matrix. + */ +export function frustumReverseZ(left: number, right: number, bottom: number, top: number, near: number, far = Infinity, dst?: Mat4): Mat4 { + dst = dst || new MatType(16); + + const dx = (right - left); + const dy = (top - bottom); + + dst[ 0] = 2 * near / dx; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = 2 * near / dy; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = (left + right) / dx; + dst[ 9] = (top + bottom) / dy; + dst[11] = -1; + dst[12] = 0; + dst[13] = 0; + dst[15] = 0; + + if (far === Infinity) { + dst[10] = 0; + dst[14] = near; + } else { + const rangeInv = 1 / (far - near); + dst[10] = near * rangeInv; + dst[14] = far * near * rangeInv; + } + + return dst; +} + let xAxis: Vec3; let yAxis: Vec3; let zAxis: Vec3; diff --git a/test/tests/mat4-test.js b/test/tests/mat4-test.js index 859d8aa..58f7050 100644 --- a/test/tests/mat4-test.js +++ b/test/tests/mat4-test.js @@ -659,6 +659,56 @@ function check(Type) { assertEqualApproximately(p[2], 1); }); + it('should compute frustumReverseZ', () => { + const left = 2; + const right = 4; + const top = 10; + const bottom = 30; + const near = 15; + const far = 25; + + const dx = (right - left); + const dy = (top - bottom); + const dz = (far - near); + + const expected = [ + 2 * near / dx, + 0, + 0, + 0, + 0, + 2 * near / dy, + 0, + 0, + (left + right) / dx, + (top + bottom) / dy, + near / dz, + -1, + 0, + 0, + near * far / dz, + 0, + ]; + testMat4WithAndWithoutDest((dst) => { + return mat4.frustumReverseZ(left, right, bottom, top, near, far, dst); + }, expected); + }); + + it('should compute correct frustumReverseZ', () => { + const left = -2; + const right = 4; + const top = 10; + const bottom = 30; + const near = 15; + const far = 25; + const m = mat4.frustumReverseZ(left, right, bottom, top, near, far); + shouldBeCloseArray(vec3.transformMat4([left, bottom, -near], m), [-1, -1, 1], 0.000001); + const centerX = (left + right) * 0.5; + const centerY = (top + bottom) * 0.5; + assertEqualApproximately(vec3.transformMat4([centerX, centerY, -near], m)[2], 1); + assertEqualApproximately(vec3.transformMat4([centerX, centerY, -far], m)[2], 0); + }); + it('should compute same frustum as perspective', () => { const lr = 4; const tb = 2;