"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.ClipStack = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const IModelApp_1 = require("../../../IModelApp");
const FloatRGBA_1 = require("./FloatRGBA");
const Texture_1 = require("./Texture");
const ClipVolume_1 = require("./ClipVolume");
const GL_1 = require("./GL");
const emptyClipData = new Uint8Array(0);
const emptyClip = {
    numRows: 0,
    getData: () => emptyClipData,
};
const scratchRangeCorners = [
    new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(),
    new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(),
];
function getRangeCorners(r) {
    return r.corners(scratchRangeCorners);
}
/** Maintains a stack of ClipVolumes. The volumes nest such that the stack represents the intersection of all the volumes.
 * The bottom of the stack represents the view's clip volume and is always present even if the view has no clip.
 * It also maintains the inside/outside clip colors, where the alpha component is 1 if the color should be applied and 0 if not.
 * @internal
 */
class ClipStack {
    /** Encoded data for all clips on the stack, update when the stack or the transform changes. */
    _cpuBuffer;
    /** A view of the encoded buffer in the format expected by the GPU. */
    _gpuBuffer;
    _texture;
    /** The maximum number of rows we have ever required. Determines the texture height. Grows as needed, reallocating a larger texture, but never shrinks. */
    _numTotalRows;
    /** The number of rows in the texture actually required to encode the current contents of the stack. */
    _numRowsInUse;
    /** The first entry always represents the view clip. The rest are pushed and popped with GraphicBranches. */
    _stack = [emptyClip];
    /** True if we need to recompute the texture. */
    _isStackDirty = false;
    /** Obtain the transform to be applied to the clips - i.e., the view matrix. */
    _getTransform;
    /** If this returns false, the clip at the bottom of the stack is ignored. */
    _wantViewClip;
    /** If alpha is 1 then geometry inside the clip will be drawn in this color. */
    _insideColor = FloatRGBA_1.FloatRgba.from(0, 0, 0, 0);
    /** If alpha is 1 then geometry outside the clip will be drawn in this color. */
    _outsideColor = FloatRGBA_1.FloatRgba.from(0, 0, 0, 0);
    /** For detecting whether the transform changed from one invocation of setViewClip to the next. */
    _prevTransform = core_geometry_1.Transform.createZero();
    /** True if we want to colorize geometry intersecting clip planes */
    _colorizeIntersection = false;
    /** The style to colorize the geometry intersecting clip planes */
    _intersectionStyle = FloatRGBA_1.FloatRgba.from(0, 0, 0, 0);
    constructor(getTransform, wantViewClip) {
        this._getTransform = getTransform;
        this._wantViewClip = wantViewClip;
        this._numTotalRows = this._numRowsInUse = 0;
        this._cpuBuffer = new Uint8Array(this._numTotalRows);
        this._gpuBuffer = this.allocateGpuBuffer();
    }
    get insideColor() {
        return this._insideColor;
    }
    get outsideColor() {
        return this._outsideColor;
    }
    get hasOutsideColor() {
        return this.outsideColor.alpha !== 0;
    }
    get colorizeIntersection() {
        return this._colorizeIntersection;
    }
    set colorizeIntersection(b) {
        this._colorizeIntersection = b;
    }
    get intersectionStyle() {
        return this._intersectionStyle;
    }
    get bytesUsed() {
        return this._texture ? this._texture.bytesUsed : 0;
    }
    setViewClip(clip, style) {
        (0, core_bentley_1.assert)(this._stack.length === 1);
        this.updateColor(style.insideColor, this._insideColor);
        this.updateColor(style.outsideColor, this._outsideColor);
        this.updateIntersectionStyle(style.colorizeIntersection, style.intersectionStyle, this._intersectionStyle);
        const transform = this._getTransform();
        if (!transform.isAlmostEqual(this._prevTransform)) {
            transform.clone(this._prevTransform);
            this._isStackDirty = true;
        }
        const cur = this._stack[0];
        if (cur === emptyClip) {
            if (!clip)
                return; // no change.
        }
        else if (!clip) {
            this._stack[0] = emptyClip;
            this._numRowsInUse = 0;
            this._isStackDirty = true;
            return;
        }
        else {
            (0, core_bentley_1.assert)(cur instanceof ClipVolume_1.ClipVolume);
            if (cur.clipVector === clip) {
                // We assume that the active view's ClipVector is never mutated in place, so if we are given the same ClipVector, we expect our RenderClipVolume to match it.
                return;
            }
        }
        // ClipVector has changed.
        const newClip = IModelApp_1.IModelApp.renderSystem.createClipVolume(clip);
        if (!newClip) {
            this._isStackDirty = this._stack[0] !== emptyClip;
            this._stack[0] = emptyClip;
            this._numRowsInUse = 0;
        }
        else {
            this.pop();
            this.push(newClip);
        }
    }
    push(clip) {
        (0, core_bentley_1.assert)(clip instanceof ClipVolume_1.ClipVolume);
        this._stack.push(clip);
        this._numRowsInUse += clip.numRows;
        this._numTotalRows = Math.max(this._numRowsInUse, this._numTotalRows);
        this._isStackDirty = true;
    }
    pop() {
        (0, core_bentley_1.assert)(this._stack.length > 0);
        const clip = this._stack.pop();
        this._numRowsInUse -= (clip ? clip.numRows : 0);
    }
    get hasClip() {
        return this.startIndex < this.endIndex;
    }
    get hasViewClip() {
        return emptyClip !== this._stack[0] && this._wantViewClip();
    }
    get startIndex() {
        (0, core_bentley_1.assert)(this._stack.length > 0);
        return this._wantViewClip() ? 0 : this._stack[0].numRows;
    }
    get endIndex() {
        return this._numRowsInUse;
    }
    get textureHeight() {
        return this._numTotalRows;
    }
    get texture() {
        this.updateTexture();
        return this._texture;
    }
    isRangeClipped(range, transform) {
        if (this.hasOutsideColor || !this.hasClip)
            return false;
        range = transform.multiplyRange(range, range);
        const corners = getRangeCorners(range);
        const startIndex = this._wantViewClip() && emptyClip !== this._stack[0] ? 0 : 1;
        for (let i = startIndex; i < this._stack.length; i++) {
            const clip = this._stack[i];
            (0, core_bentley_1.assert)(clip instanceof ClipVolume_1.ClipVolume);
            if (core_geometry_1.ClipPlaneContainment.StronglyOutside === clip.clipVector.classifyPointContainment(corners))
                return true;
        }
        return false;
    }
    /** Exposed strictly for tests. */
    get clips() {
        return this._stack;
    }
    /** Exposed strictly for tests. */
    static get emptyViewClip() {
        return emptyClip;
    }
    updateTexture() {
        if (this._numTotalRows > 0 && (!this._texture || this._texture.height < this._numTotalRows)) {
            // We need to resize the texture.
            (0, core_bentley_1.assert)(this._isStackDirty);
            this._isStackDirty = true;
            this._texture = (0, core_bentley_1.dispose)(this._texture);
            this._cpuBuffer = new Uint8Array(this._numTotalRows * 4 * 4);
            this._gpuBuffer = this.allocateGpuBuffer();
        }
        if (this._isStackDirty) {
            this._isStackDirty = false;
            this.recomputeTexture();
        }
    }
    recomputeTexture() {
        // Copy each clip's data to the buffer, recording whether the buffer's contents actually changed.
        let bufferDirty = false;
        const transform = this._getTransform();
        let bufferIndex = 0;
        for (const clip of this._stack) {
            const data = clip.getData(transform);
            for (let i = 0; i < data.byteLength; i++) {
                const byte = data[i];
                bufferDirty = bufferDirty || byte !== this._cpuBuffer[bufferIndex];
                this._cpuBuffer[bufferIndex++] = byte;
            }
        }
        // If the contents have changed, upload the new texture data to the GPU.
        if (bufferDirty) {
            this.uploadTexture();
        }
    }
    uploadTexture() {
        if (this._texture)
            this._texture.replaceTextureData(this._gpuBuffer);
        else
            this._texture = Texture_1.Texture2DHandle.createForData(1, this._numTotalRows, this._gpuBuffer, false, GL_1.GL.Texture.WrapMode.ClampToEdge, GL_1.GL.Texture.Format.Rgba);
        (0, core_bentley_1.assert)((this._texture?.height ?? -1) === this._numTotalRows);
    }
    allocateGpuBuffer() {
        return new Float32Array(this._cpuBuffer.buffer);
    }
    updateColor(rgb, rgba) {
        rgba.alpha = undefined !== rgb ? 1 : 0;
        if (rgb)
            rgba.setRgbColor(rgb);
    }
    updateIntersectionStyle(colorizeIntersection, style, _thisStyle) {
        this._colorizeIntersection = colorizeIntersection === true ? true : false;
        if (style !== undefined) {
            if (style.color !== undefined)
                _thisStyle.setRgbColor(style.color);
            if (style.width !== undefined)
                _thisStyle.alpha = style.width;
        }
    }
}
exports.ClipStack = ClipStack;
//# sourceMappingURL=ClipStack.js.map