"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module WebGL
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.FrustumUniforms = void 0;
exports.fromSumOf = fromSumOf;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const IModelFrameLifecycle_1 = require("./IModelFrameLifecycle");
const Matrix_1 = require("./Matrix");
const Sync_1 = require("./Sync");
/** Represents a Target's frustum for use in glsl as a pair of uniforms.
 * Do not modify fields of exposed objects directly. e.g., do not directly manipulate the projection or view matrices - use the appropriate APIs.
 * @internal
 */
class FrustumUniforms {
    // CPU state. Do not modify - use APIs.
    planFrustum = new core_common_1.Frustum();
    _planFraction = 0;
    _nearPlaneCenter = new core_geometry_1.Point3d();
    viewMatrix = core_geometry_1.Transform.createIdentity();
    projectionMatrix = core_geometry_1.Matrix4d.createIdentity();
    _worldUpVector = core_geometry_1.Vector3d.unitZ();
    _viewUpVector = core_geometry_1.Vector3d.unitZ();
    // GPU state
    _planeData = new Float32Array(4);
    _frustumData = new Float32Array(3);
    projectionMatrix32 = new Matrix_1.Matrix4();
    _logZData = new Float32Array(2);
    _viewUpVector32 = new Float32Array(3);
    // SyncTarget
    syncKey = 0;
    // Scratch variables
    _scratch = {
        point3d: new core_geometry_1.Point3d(),
        vec3d: new core_geometry_1.Vector3d(),
        viewX: new core_geometry_1.Vector3d(),
        viewY: new core_geometry_1.Vector3d(),
        viewZ: new core_geometry_1.Vector3d(),
    };
    constructor() {
    }
    bindProjectionMatrix(uniform) {
        if (!(0, Sync_1.sync)(this, uniform))
            uniform.setMatrix4(this.projectionMatrix32);
    }
    bindUpVector(uniform) {
        if (!(0, Sync_1.sync)(this, uniform))
            uniform.setUniform3fv(this._viewUpVector32);
    }
    // uniform vec4 u_frustumPlanes; // { top, bottom, left, right }
    get planes() { return this._planeData; }
    // uniform vec3 u_frustum; // { near, far, type }
    get frustum() { return this._frustumData; }
    get nearPlane() { return this._frustumData[0 /* FrustumData.kNear */]; }
    get farPlane() { return this._frustumData[1 /* FrustumData.kFar */]; }
    get type() { return this.frustum[2 /* FrustumData.kType */]; }
    get is2d() { return 0 /* FrustumUniformType.TwoDee */ === this.type; }
    get planFraction() { return this._planFraction; }
    // uniform vec2 u_logZ where x = 1/near and y = log(far/near)
    get logZ() { return this._logZData; }
    changeFrustum(newFrustum, newFraction, is3d) {
        if (newFraction === this._planFraction && is3d !== this.is2d && newFrustum.equals(this.planFrustum))
            return;
        (0, Sync_1.desync)(this);
        newFrustum.clone(this.planFrustum);
        const farLowerLeft = newFrustum.getCorner(core_common_1.Npc.LeftBottomRear);
        const farLowerRight = newFrustum.getCorner(core_common_1.Npc.RightBottomRear);
        const farUpperLeft = newFrustum.getCorner(core_common_1.Npc.LeftTopRear);
        const farUpperRight = newFrustum.getCorner(core_common_1.Npc.RightTopRear);
        const nearLowerLeft = newFrustum.getCorner(core_common_1.Npc.LeftBottomFront);
        const nearLowerRight = newFrustum.getCorner(core_common_1.Npc.RightBottomFront);
        const nearUpperLeft = newFrustum.getCorner(core_common_1.Npc.LeftTopFront);
        const nearUpperRight = newFrustum.getCorner(core_common_1.Npc.RightTopFront);
        const nearCenter = nearLowerLeft.interpolate(0.5, nearUpperRight, this._scratch.point3d);
        const viewX = normalizedDifference(nearLowerRight, nearLowerLeft, this._scratch.viewX);
        const viewY = normalizedDifference(nearUpperLeft, nearLowerLeft, this._scratch.viewY);
        const viewZ = (0, core_bentley_1.expectDefined)(viewX.crossProduct(viewY, this._scratch.viewZ).normalize());
        this._planFraction = newFraction;
        if (!is3d || newFraction > 0.999) { // ortho or 2d
            const halfWidth = core_geometry_1.Vector3d.createStartEnd(farLowerRight, farLowerLeft, this._scratch.vec3d).magnitude() * 0.5;
            const halfHeight = core_geometry_1.Vector3d.createStartEnd(farLowerRight, farUpperRight).magnitude() * 0.5;
            const depth = core_geometry_1.Vector3d.createStartEnd(farLowerLeft, nearLowerLeft, this._scratch.vec3d).magnitude();
            lookIn(nearCenter, viewX, viewY, viewZ, this.viewMatrix);
            ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, 0, depth, this.projectionMatrix);
            this._nearPlaneCenter.setFrom(nearLowerLeft);
            this._nearPlaneCenter.interpolate(0.5, nearUpperRight, this._nearPlaneCenter);
            this.setPlanes(halfHeight, -halfHeight, -halfWidth, halfWidth);
            this.setFrustum(0, depth, is3d ? 1 /* FrustumUniformType.Orthographic */ : 0 /* FrustumUniformType.TwoDee */);
        }
        else { // perspective
            const scale = 1.0 / (1.0 - newFraction);
            const zVec = core_geometry_1.Vector3d.createStartEnd(farLowerLeft, nearLowerLeft, this._scratch.vec3d);
            const cameraPosition = fromSumOf(farLowerLeft, zVec, scale, this._scratch.point3d);
            const frustumLeft = dotDifference(farLowerLeft, cameraPosition, viewX) * newFraction;
            const frustumRight = dotDifference(farLowerRight, cameraPosition, viewX) * newFraction;
            const frustumBottom = dotDifference(farLowerLeft, cameraPosition, viewY) * newFraction;
            const frustumTop = dotDifference(farUpperLeft, cameraPosition, viewY) * newFraction;
            const frustumFront = -dotDifference(nearLowerLeft, cameraPosition, viewZ);
            const frustumBack = -dotDifference(farLowerLeft, cameraPosition, viewZ);
            lookIn(cameraPosition, viewX, viewY, viewZ, this.viewMatrix);
            frustum(frustumLeft, frustumRight, frustumBottom, frustumTop, frustumFront, frustumBack, this.projectionMatrix);
            IModelFrameLifecycle_1.IModelFrameLifecycle.onChangeCameraView.raiseEvent({
                cameraPosition,
                viewX,
                viewY,
                viewZ,
            });
            IModelFrameLifecycle_1.IModelFrameLifecycle.onChangeCameraFrustum.raiseEvent({
                type: 2 /* FrustumUniformType.Perspective */,
                left: frustumLeft,
                right: frustumRight,
                bottom: frustumBottom,
                top: frustumTop,
                front: frustumFront,
                back: frustumBack,
            });
            this._nearPlaneCenter.setFrom(nearLowerLeft);
            this._nearPlaneCenter.interpolate(0.5, nearUpperRight, this._nearPlaneCenter);
            this.setPlanes(frustumTop, frustumBottom, frustumLeft, frustumRight);
            this.setFrustum(frustumFront, frustumBack, 2 /* FrustumUniformType.Perspective */);
        }
        this.viewMatrix.matrix.inverseState = core_geometry_1.InverseMatrixState.unknown;
        this.viewMatrix.matrix.multiplyVector(this._worldUpVector, this._viewUpVector);
        this._viewUpVector.normalizeInPlace();
        this._viewUpVector32[0] = this._viewUpVector.x;
        this._viewUpVector32[1] = this._viewUpVector.y;
        this._viewUpVector32[2] = this._viewUpVector.z;
        this.projectionMatrix32.initFromMatrix4d(this.projectionMatrix);
    }
    changeProjectionMatrix(newMatrix) {
        (0, Sync_1.desync)(this);
        this.projectionMatrix.setFrom(newMatrix);
        this.projectionMatrix32.initFromMatrix4d(this.projectionMatrix);
    }
    setPlanes(top, bottom, left, right) {
        this._planeData[0 /* Plane.kTop */] = top;
        this._planeData[1 /* Plane.kBottom */] = bottom;
        this._planeData[2 /* Plane.kLeft */] = left;
        this._planeData[3 /* Plane.kRight */] = right;
    }
    setFrustum(nearPlane, farPlane, type) {
        this._frustumData[0 /* FrustumData.kNear */] = nearPlane;
        this._frustumData[1 /* FrustumData.kFar */] = farPlane;
        this._frustumData[2 /* FrustumData.kType */] = type;
        // If nearPlane is zero, we don't have a camera (or got very unlucky); in that case shader will compute linear depth.
        this._logZData[0] = 0 !== nearPlane ? 1 / nearPlane : 0;
        this._logZData[1] = 0 !== nearPlane ? Math.log(farPlane / nearPlane) : farPlane;
    }
}
exports.FrustumUniforms = FrustumUniforms;
function normalizedDifference(p0, p1, out) {
    const result = undefined !== out ? out : new core_geometry_1.Vector3d();
    result.x = p0.x - p1.x;
    result.y = p0.y - p1.y;
    result.z = p0.z - p1.z;
    result.normalizeInPlace();
    return result;
}
/** @internal */
function fromSumOf(p, v, scale, out) {
    const result = undefined !== out ? out : new core_geometry_1.Point3d();
    result.x = p.x + v.x * scale;
    result.y = p.y + v.y * scale;
    result.z = p.z + v.z * scale;
    return result;
}
function dotDifference(pt, origin, vec) {
    return (pt.x - origin.x) * vec.x + (pt.y - origin.y) * vec.y + (pt.z - origin.z) * vec.z;
}
function lookIn(eye, viewX, viewY, viewZ, result) {
    const rot = result.matrix.coffs;
    rot[0] = viewX.x;
    rot[1] = viewX.y;
    rot[2] = viewX.z;
    rot[3] = viewY.x;
    rot[4] = viewY.y;
    rot[5] = viewY.z;
    rot[6] = viewZ.x;
    rot[7] = viewZ.y;
    rot[8] = viewZ.z;
    result.origin.x = -viewX.dotProduct(eye);
    result.origin.y = -viewY.dotProduct(eye);
    result.origin.z = -viewZ.dotProduct(eye);
}
function ortho(left, right, bottom, top, near, far, result) {
    core_geometry_1.Matrix4d.createRowValues(2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left), 0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom), 0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near), 0.0, 0.0, 0.0, 1.0, result);
}
function frustum(left, right, bottom, top, near, far, result) {
    core_geometry_1.Matrix4d.createRowValues((2.0 * near) / (right - left), 0.0, (right + left) / (right - left), 0.0, 0.0, (2.0 * near) / (top - bottom), (top + bottom) / (top - bottom), 0.0, 0.0, 0.0, -(far + near) / (far - near), -(2.0 * far * near) / (far - near), 0.0, 0.0, -1.0, 0.0, result);
}
//# sourceMappingURL=FrustumUniforms.js.map