/*---------------------------------------------------------------------------------------------
* 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
 */
import { assert } from "@itwin/core-bentley";
import { Matrix3d, Matrix4d, Point3d, Transform } from "@itwin/core-geometry";
import { BranchStack } from "./BranchStack";
import { BatchState } from "./BatchState";
import { Matrix3, Matrix4 } from "./Matrix";
import { RenderCommands } from "./RenderCommands";
import { desync, sync } from "./Sync";
import { ClipStack } from "./ClipStack";
function equalXYZs(a, b) {
    if (a === b)
        return true;
    if ((undefined === a) !== (undefined === b))
        return false;
    if (undefined !== a && undefined !== b)
        return a.isExactEqual(b);
    assert(undefined === a && undefined === b);
    return true;
}
/** Maintains uniform variable state associated with the Branch currently being drawn by a Target.
 * @internal
 */
export class BranchUniforms {
    get stack() { return this._stack; }
    constructor(target) {
        this._viewClipEnabled = false;
        this.syncKey = 0;
        this._stack = new BranchStack();
        // Parameters that affect synchronization.
        this._isInstanced = false;
        // CPU state
        this._mv = Matrix4d.createIdentity();
        this._mvp = Matrix4d.createIdentity();
        // GPU state
        this._mv32 = new Matrix4();
        this._mvp32 = new Matrix4();
        this._m32 = new Matrix4();
        this._v32 = new Matrix3();
        // Working state
        this._scratchTransform = Transform.createIdentity();
        this._scratchTransform2 = Transform.createIdentity();
        this._scratchViewToWorld = Matrix3d.createIdentity();
        this._scratchVIModelMatrix = Transform.createIdentity();
        this._zeroPoint = new Point3d(0, 0, 0);
        this._target = target;
        this.clipStack = new ClipStack(() => target.uniforms.frustum.viewMatrix, () => this._viewClipEnabled && this.top.viewFlags.clipVolume);
    }
    createBatchState() {
        return new BatchState(this._stack);
    }
    createRenderCommands(batchState) {
        return new RenderCommands(this._target, this._stack, batchState);
    }
    get modelViewMatrix() {
        return this._mv;
    }
    get top() {
        return this._stack.top;
    }
    get length() {
        return this._stack.length;
    }
    pushBranch(branch) {
        desync(this);
        this._stack.pushBranch(branch);
        if (this.top.clipVolume)
            this.clipStack.push(this.top.clipVolume);
        if (branch.branch.realityModelDisplaySettings)
            this._target.uniforms.realityModel.update(branch.branch.realityModelDisplaySettings);
    }
    pushState(state) {
        desync(this);
        this._stack.pushState(state);
        if (this.top.clipVolume)
            this.clipStack.push(this.top.clipVolume);
        if (state.realityModelDisplaySettings)
            this._target.uniforms.realityModel.update(state.realityModelDisplaySettings);
    }
    pop() {
        desync(this);
        if (this.top.clipVolume)
            this.clipStack.pop();
        this._stack.pop();
    }
    pushViewClip() {
        assert(!this._viewClipEnabled);
        this._viewClipEnabled = true;
        // Target.readPixels() pushes another BranchState before pushing view clip...
        assert((this._target.isReadPixelsInProgress ? 2 : 1) === this._stack.length);
    }
    popViewClip() {
        assert(this._viewClipEnabled);
        this._viewClipEnabled = false;
        assert((this._target.isReadPixelsInProgress ? 2 : 1) === this._stack.length);
    }
    changeRenderPlan(vf, is3d, hline) {
        this._stack.changeRenderPlan(vf, is3d, hline);
    }
    updateViewClip(clip, style) {
        this.clipStack.setViewClip(clip, style);
    }
    overrideFeatureSymbology(ovr) {
        this._stack.setSymbologyOverrides(ovr);
    }
    bindModelViewMatrix(uniform, geom, isViewCoords) {
        if (this.update(uniform, geom, isViewCoords))
            uniform.setMatrix4(this._mv32);
    }
    bindModelViewProjectionMatrix(uniform, geom, isViewCoords) {
        if (this.update(uniform, geom, isViewCoords))
            uniform.setMatrix4(this._mvp32);
    }
    bindModelToWorldTransform(uniform, geom, isViewCoords) {
        if (this.update(uniform, geom, isViewCoords))
            uniform.setMatrix4(this._m32);
    }
    bindWorldToViewNTransform(uniform, geom, isViewCoords) {
        if (this.update(uniform, geom, isViewCoords))
            uniform.setMatrix3(this._v32);
    }
    update(uniform, geometry, isViewCoords) {
        const uniforms = this._target.uniforms[isViewCoords ? "viewRect" : "frustum"];
        if (!sync(uniforms, this))
            desync(this);
        const instancedGeom = geometry.asInstanced;
        if (undefined !== instancedGeom || this._isInstanced) {
            this._isInstanced = undefined !== instancedGeom;
            desync(this);
        }
        const vio = geometry.viewIndependentOrigin;
        if (!equalXYZs(vio, this._viewIndependentOrigin)) {
            this._viewIndependentOrigin = vio;
            desync(this);
        }
        if (sync(this, uniform))
            return false;
        let mv;
        const modelMatrix = this._target.currentTransform;
        if (isViewCoords) {
            // Zero out Z for silly clipping tools...
            mv = modelMatrix.clone(this._scratchTransform);
            mv.matrix.coffs[2] = mv.matrix.coffs[5] = mv.matrix.coffs[8] = 0.0;
            if (instancedGeom)
                mv = instancedGeom.getRtcModelTransform(mv);
            // Scale based on device-pixel ratio.
            const scale = this._target.devicePixelRatio;
            const viewMatrix = Transform.createScaleAboutPoint(this._zeroPoint, scale, this._scratchTransform2);
            viewMatrix.multiplyTransformTransform(mv, mv);
        }
        else {
            const viewMatrix = this._target.uniforms.frustum.viewMatrix;
            if (undefined !== instancedGeom) {
                // For instanced geometry, the "model view" matrix is really a transform from center of instanced geometry range to view.
                // Shader will compute final model-view matrix based on this and the per-instance transform.
                if (vio) {
                    const viewToWorldRot = viewMatrix.matrix.inverse(this._scratchViewToWorld);
                    const rotateAboutOrigin = Transform.createFixedPointAndMatrix(vio, viewToWorldRot, this._scratchTransform2);
                    const viModelMatrix = rotateAboutOrigin.multiplyTransformTransform(instancedGeom.getRtcModelTransform(modelMatrix), this._scratchVIModelMatrix);
                    mv = viewMatrix.multiplyTransformTransform(viModelMatrix, this._scratchTransform);
                }
                else {
                    mv = viewMatrix.multiplyTransformTransform(instancedGeom.getRtcModelTransform(modelMatrix), this._scratchTransform);
                }
            }
            else {
                if (undefined !== vio) {
                    const viewToWorldRot = viewMatrix.matrix.inverse(this._scratchViewToWorld);
                    const rotateAboutOrigin = Transform.createFixedPointAndMatrix(vio, viewToWorldRot, this._scratchTransform2);
                    const viModelMatrix = rotateAboutOrigin.multiplyTransformTransform(modelMatrix, this._scratchVIModelMatrix);
                    mv = viewMatrix.multiplyTransformTransform(viModelMatrix, this._scratchTransform);
                }
                else {
                    mv = viewMatrix.multiplyTransformTransform(modelMatrix, this._scratchTransform);
                }
            }
        }
        if (this._target.wantThematicDisplay) {
            this._m32.initFromTransform(modelMatrix);
            this._v32.initFromMatrix3d(this._target.uniforms.frustum.viewMatrix.matrix);
        }
        else if (undefined !== geometry.asSurface?.mesh.constantLodVParams) {
            this._m32.initFromTransform(modelMatrix);
        }
        Matrix4d.createTransform(mv, this._mv);
        this._mv32.initFromTransform(mv);
        // Don't bother computing mvp for instanced geometry - it's not used.
        if (!this._isInstanced) {
            uniforms.projectionMatrix.multiplyMatrixMatrix(this._mv, this._mvp);
            this._mvp32.initFromMatrix4d(this._mvp);
        }
        return true;
    }
}
//# sourceMappingURL=BranchUniforms.js.map