"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.FeatureOverrides = void 0;
exports.isFeatureHilited = isFeatureHilited;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const LineCode_1 = require("./LineCode");
const GL_1 = require("./GL");
const RenderFlags_1 = require("./RenderFlags");
const Sync_1 = require("./Sync");
const System_1 = require("./System");
const Texture_1 = require("./Texture");
const DisplayParams_1 = require("../../../common/internal/render/DisplayParams");
const VertexTable_1 = require("../../../common/internal/render/VertexTable");
function isFeatureHilited(feature, hilites, isModelHilited) {
    if (hilites.isEmpty)
        return false;
    if ("union" === hilites.modelSubCategoryMode)
        return isModelHilited || hilites.elements.hasPair(feature.elementId) || hilites.subcategories.hasPair(feature.subCategoryId);
    return hilites.elements.hasPair(feature.elementId) || (isModelHilited && hilites.subcategories.hasPair(feature.subCategoryId));
}
const scratchPackedFeature = core_common_1.PackedFeature.createWithIndex();
/** @internal */
class FeatureOverrides {
    target;
    _options;
    _lut;
    _mostRecentSymbologyOverrides;
    _lastFlashId = core_bentley_1.Id64.invalid;
    _hiliteSyncObserver = {};
    _pickExclusionsSyncObserver = {};
    _anyOverridden = true;
    _allHidden = true;
    _anyTranslucent = true;
    _anyViewIndependentTranslucent = true;
    _anyOpaque = true;
    _anyHilited = true;
    _lutParams = new Float32Array(2);
    _uniformSymbologyFlags = 0 /* EmphasisFlags.None */;
    _cleanup;
    get anyOverridden() { return this._anyOverridden; }
    get allHidden() { return this._allHidden; }
    get anyTranslucent() { return this._anyTranslucent; }
    get anyViewIndependentTranslucent() { return this._anyViewIndependentTranslucent; }
    get anyOpaque() { return this._anyOpaque; }
    get anyHilited() { return this._anyHilited; }
    /** For tests. */
    get lutData() { return this._lut?.dataBytes; }
    get byteLength() { return undefined !== this._lut ? this._lut.bytesUsed : 0; }
    get isUniform() { return 3 === this._lutParams[0] && 1 === this._lutParams[1]; }
    updateUniformSymbologyFlags() {
        this._uniformSymbologyFlags = 0 /* EmphasisFlags.None */;
        if (!this.isUniform || !this._lut?.dataBytes)
            return;
        let flags = (0, core_bentley_1.expectDefined)(this._lut.dataBytes[0]);
        if (0 !== (flags & 16 /* OvrFlags.Flashed */))
            this._uniformSymbologyFlags |= 4 /* EmphasisFlags.Flashed */;
        if (0 !== (flags & 32 /* OvrFlags.NonLocatable */))
            this._uniformSymbologyFlags |= 8 /* EmphasisFlags.NonLocatable */;
        if (!this._anyHilited)
            return;
        flags = (0, core_bentley_1.expectDefined)(this._lut.dataBytes[1]) << 8;
        if (0 !== (flags & 256 /* OvrFlags.Hilited */))
            this._uniformSymbologyFlags |= 1 /* EmphasisFlags.Hilite */;
        if (0 !== (flags & 512 /* OvrFlags.Emphasized */))
            this._uniformSymbologyFlags |= 2 /* EmphasisFlags.Emphasized */;
    }
    getUniformOverrides() {
        (0, core_bentley_1.assert)(this.isUniform);
        (0, core_bentley_1.assert)(undefined !== this._lut);
        (0, core_bentley_1.assert)(undefined !== this._lut.dataBytes);
        return this._lut.dataBytes;
    }
    _initialize(provider, map, ovrs, pickExcludes, hilite, flashed) {
        const nFeatures = map.numFeatures;
        const dims = (0, VertexTable_1.computeDimensions)(nFeatures, 3, 0, System_1.System.instance.maxTextureSize);
        const width = dims.width;
        const height = dims.height;
        (0, core_bentley_1.assert)(width * height >= nFeatures);
        this._lutParams[0] = width;
        this._lutParams[1] = height;
        const data = new Uint8Array(width * height * 4);
        const creator = new Texture_1.Texture2DDataUpdater(data);
        this.buildLookupTable(provider, creator, map, ovrs, pickExcludes, flashed, hilite);
        return Texture_1.TextureHandle.createForData(width, height, data, true, GL_1.GL.Texture.WrapMode.ClampToEdge);
    }
    _update(provider, map, lut, pickExcludes, flashed, hilites, ovrs) {
        const updater = new Texture_1.Texture2DDataUpdater((0, core_bentley_1.expectDefined)(lut.dataBytes));
        if (undefined === ovrs) {
            this.updateFlashedAndHilited(updater, map, pickExcludes, flashed, hilites);
        }
        else {
            (0, core_bentley_1.assert)(undefined !== hilites);
            this.buildLookupTable(provider, updater, map, ovrs, pickExcludes, flashed, hilites);
        }
        lut.update(updater);
    }
    setTransparency(transparency, viewDependentTransparency, data, transparencyByteIndex, curFlags) {
        // transparency in range [0, 1]...convert to byte and invert so 0=transparent...
        let alpha = 1.0 - transparency;
        alpha = Math.floor(0xff * alpha + 0.5);
        if ((0xff - alpha) < DisplayParams_1.DisplayParams.minTransparency)
            alpha = 0xff;
        data.setByteAtIndex(transparencyByteIndex, alpha);
        if (0xff === alpha) {
            this._anyOpaque = true;
        }
        else {
            this._anyTranslucent = true;
            if (!viewDependentTransparency) {
                curFlags |= 1024 /* OvrFlags.ViewIndependentTransparency */;
                this._anyViewIndependentTranslucent = true;
            }
        }
        return curFlags;
    }
    buildLookupTable(provider, data, map, ovr, pickExclude, flashedIdParts, hilites) {
        const allowHilite = true !== this._options.noHilite;
        const allowFlash = true !== this._options.noFlash;
        const allowEmphasis = true !== this._options.noEmphasis;
        let isModelHilited = false;
        const prevModelId = { lower: -1, upper: -1 };
        this._anyOpaque = this._anyTranslucent = this._anyViewIndependentTranslucent = this._anyHilited = false;
        let nHidden = 0;
        let nOverridden = 0;
        // NB: We currently use 3 RGBA values per feature as follows:
        //  [0]
        //      RG = override flags (see OvrFlags enum)
        //      B = line code
        //      A = line weight (if we need an extra byte in future, could combine code+weight into a single byte).
        //  [1]
        //      RGB = rgb
        //      A = alpha
        //  [2]
        //      RGB = line rgb
        //      A = line alpha
        for (const feature of map.iterable(scratchPackedFeature)) {
            const i = feature.index;
            const dataIndex = i * 4 * 3;
            if (prevModelId.lower !== feature.modelId.lower || prevModelId.upper !== feature.modelId.upper) {
                prevModelId.lower = feature.modelId.lower;
                prevModelId.upper = feature.modelId.upper;
                isModelHilited = allowHilite && hilites.models.hasPair(feature.modelId);
            }
            const app = provider.getFeatureAppearance(ovr, feature.elementId.lower, feature.elementId.upper, feature.subCategoryId.lower, feature.subCategoryId.upper, feature.geometryClass, feature.modelId.lower, feature.modelId.upper, map.type, feature.animationNodeId);
            // NB: If the appearance is fully transparent, then:
            //  - For normal ("primary") models, getAppearance() returns undefined.
            //  - For classifier models, getAppearance() returns the appearance, and classification shader will discard fully-transparent classified pixels.
            // (The latter is how we clip the classified model using the classifiers).
            if (undefined === app) {
                // The feature is not visible. We don't care about any of the other overrides, because we're not going to render it.
                data.setOvrFlagsAtIndex(dataIndex, 4096 /* OvrFlags.Visibility */);
                nHidden++;
                nOverridden++;
                continue;
            }
            let flags = app.nonLocatable ? 32 /* OvrFlags.NonLocatable */ : 0 /* OvrFlags.None */;
            if (allowHilite && isFeatureHilited(feature, hilites, isModelHilited)) {
                flags |= 256 /* OvrFlags.Hilited */;
                this._anyHilited = true;
            }
            if (allowEmphasis && app.emphasized) {
                flags |= 512 /* OvrFlags.Emphasized */;
                this._anyHilited = true;
            }
            if (app.overridesRgb && app.rgb) {
                flags |= 2 /* OvrFlags.Rgb */;
                const rgb = app.rgb;
                data.setByteAtIndex(dataIndex + 4, rgb.r);
                data.setByteAtIndex(dataIndex + 5, rgb.g);
                data.setByteAtIndex(dataIndex + 6, rgb.b);
            }
            if (undefined !== app.transparency) {
                flags |= 4 /* OvrFlags.Alpha */;
                flags = this.setTransparency(app.transparency, app.viewDependentTransparency, data, dataIndex + 7, flags);
            }
            const lineRgb = app.getLineRgb();
            if (lineRgb) {
                flags |= 1 /* OvrFlags.LineRgb */;
                data.setByteAtIndex(dataIndex + 8, lineRgb.r);
                data.setByteAtIndex(dataIndex + 9, lineRgb.g);
                data.setByteAtIndex(dataIndex + 10, lineRgb.b);
            }
            const lineTransp = app.getLineTransparency();
            if (undefined !== lineTransp) {
                flags |= 8 /* OvrFlags.LineAlpha */;
                flags = this.setTransparency(lineTransp, app.viewDependentTransparency, data, dataIndex + 11, flags);
            }
            if (app.overridesWeight && app.weight) {
                flags |= 128 /* OvrFlags.Weight */;
                let weight = app.weight;
                weight = Math.min(31, weight);
                weight = Math.max(1, weight);
                data.setByteAtIndex(dataIndex + 3, weight);
            }
            if (app.overridesLinePixels && app.linePixels) {
                flags |= 64 /* OvrFlags.LineCode */;
                const lineCode = LineCode_1.LineCode.valueFromLinePixels(app.linePixels);
                data.setByteAtIndex(dataIndex + 2, lineCode);
            }
            if (app.ignoresMaterial)
                flags |= 8192 /* OvrFlags.IgnoreMaterial */;
            if (allowFlash && undefined !== flashedIdParts && feature.elementId.lower === flashedIdParts.lower && feature.elementId.upper === flashedIdParts.upper)
                flags |= 16 /* OvrFlags.Flashed */;
            if (pickExclude?.hasPair(feature.elementId)) {
                flags |= 2048 /* OvrFlags.InvisibleDuringPick */;
                ++nHidden;
            }
            data.setOvrFlagsAtIndex(dataIndex, flags);
            if (0 /* OvrFlags.None */ !== flags)
                nOverridden++;
        }
        this._allHidden = (nHidden === map.numFeatures);
        this._anyOverridden = (nOverridden > 0);
        this.updateUniformSymbologyFlags();
    }
    // NB: If hilites is undefined, it means that the hilited set has not changed.
    updateFlashedAndHilited(data, map, pickExcludes, flashed, hilites) {
        if (!hilites || true === this._options.noHilite) {
            this.updateFlashed(data, map, pickExcludes, flashed);
            return;
        }
        const allowFlash = true !== this._options.noFlash;
        const intersect = "intersection" === hilites.modelSubCategoryMode;
        this._anyOverridden = this._anyHilited = false;
        for (const feature of map.iterable(scratchPackedFeature)) {
            const dataIndex = feature.index * 4 * 3;
            const oldFlags = data.getOvrFlagsAtIndex(dataIndex);
            if (0 /* OvrFlags.None */ !== (oldFlags & 4096 /* OvrFlags.Visibility */)) {
                // If it's invisible, none of the other flags matter. We can't flash it and don't want to hilite it.
                this._anyOverridden = true;
                continue;
            }
            const isModelHilited = hilites.models.hasPair(feature.modelId);
            let isHilited = isModelHilited && !intersect;
            if (!isHilited)
                isHilited = hilites.elements.hasPair(feature.elementId);
            if (!isHilited)
                if (isModelHilited || !intersect)
                    isHilited = hilites.subcategories.hasPair(feature.subCategoryId);
            let isFlashed = false;
            if (flashed && allowFlash)
                isFlashed = feature.elementId.lower === flashed.lower && feature.elementId.upper === flashed.upper;
            let newFlags = isFlashed ? (oldFlags | 16 /* OvrFlags.Flashed */) : (oldFlags & ~16 /* OvrFlags.Flashed */);
            newFlags = isHilited ? (newFlags | 256 /* OvrFlags.Hilited */) : (newFlags & ~256 /* OvrFlags.Hilited */);
            if (pickExcludes) {
                newFlags = pickExcludes.hasPair(feature.elementId) ? (newFlags | 2048 /* OvrFlags.InvisibleDuringPick */) : (newFlags & ~2048 /* OvrFlags.InvisibleDuringPick */);
            }
            data.setOvrFlagsAtIndex(dataIndex, newFlags);
            if (0 /* OvrFlags.None */ !== newFlags) {
                this._anyOverridden = true;
                this._anyHilited = this._anyHilited || isHilited || 0 /* OvrFlags.None */ !== (newFlags & 512 /* OvrFlags.Emphasized */);
            }
        }
        this.updateUniformSymbologyFlags();
    }
    updateFlashed(data, map, pickExcludes, flashed) {
        if (true === this._options.noFlash && !pickExcludes)
            return;
        this._anyOverridden = false;
        const elemId = { lower: 0, upper: 0 };
        for (let i = 0; i < map.numFeatures; i++) {
            const dataIndex = i * 4 * 3;
            const oldFlags = data.getOvrFlagsAtIndex(dataIndex);
            if (0 /* OvrFlags.None */ !== (oldFlags & 4096 /* OvrFlags.Visibility */)) {
                // If it's invisible, none of the other flags matter and we can't flash it.
                this._anyOverridden = true;
                continue;
            }
            let isFlashed = false;
            let thisElemId;
            if (flashed && !this._options.noFlash) {
                thisElemId = map.getElementIdPair(i, elemId);
                isFlashed = thisElemId.lower === flashed.lower && thisElemId.upper === flashed.upper;
            }
            let newFlags = isFlashed ? (oldFlags | 16 /* OvrFlags.Flashed */) : (oldFlags & ~16 /* OvrFlags.Flashed */);
            if (pickExcludes) {
                if (!thisElemId) {
                    thisElemId = map.getElementIdPair(i, elemId);
                }
                newFlags = pickExcludes.hasPair(thisElemId) ? (newFlags | 2048 /* OvrFlags.InvisibleDuringPick */) : (newFlags & ~2048 /* OvrFlags.InvisibleDuringPick */);
            }
            data.setOvrFlagsAtIndex(dataIndex, newFlags);
            if (0 /* OvrFlags.None */ !== newFlags)
                this._anyOverridden = true;
        }
        this.updateUniformSymbologyFlags();
    }
    constructor(target, options, cleanup) {
        this.target = target;
        this._options = options;
        this._cleanup = cleanup;
    }
    static createFromTarget(target, options, cleanup) {
        return new FeatureOverrides(target, options, cleanup);
    }
    get isDisposed() { return undefined === this._lut; }
    [Symbol.dispose]() {
        this._lut = (0, core_bentley_1.dispose)(this._lut);
        if (this._cleanup) {
            this._cleanup();
            this._cleanup = undefined;
        }
    }
    initFromMap(map, provider) {
        const nFeatures = map.numFeatures;
        (0, core_bentley_1.assert)(0 < nFeatures);
        this._lut = (0, core_bentley_1.dispose)(this._lut);
        const ovrs = this.target.currentFeatureSymbologyOverrides;
        this._mostRecentSymbologyOverrides = ovrs;
        const hilite = this.target.hilites;
        this._lut = this._initialize(provider, map, ovrs, this.target.pickExclusions, hilite, this.target.flashed);
        this._lastFlashId = core_bentley_1.Id64.invalid;
        this._hiliteSyncObserver = {};
        this._pickExclusionsSyncObserver = {};
    }
    update(features, provider) {
        let ovrs = this.target.currentFeatureSymbologyOverrides;
        const ovrsUpdated = ovrs !== this._mostRecentSymbologyOverrides;
        if (ovrsUpdated)
            this._mostRecentSymbologyOverrides = ovrs;
        else
            ovrs = undefined;
        const flashedId = this.target.flashedId;
        const hilite = this.target.hilites;
        const hiliteUpdated = !(0, Sync_1.sync)(this.target.hiliteSyncTarget, this._hiliteSyncObserver);
        const pickExcludesUpdated = !(0, Sync_1.sync)(this.target.pickExclusionsSyncTarget, this._pickExclusionsSyncObserver);
        if (ovrsUpdated || hiliteUpdated || flashedId !== this._lastFlashId || pickExcludesUpdated) {
            // _lut can be undefined if context was lost, (gl.createTexture returns null)
            if (this._lut) {
                this._update(provider, features, this._lut, undefined !== ovrs || pickExcludesUpdated ? this.target.pickExclusions : undefined, this.target.flashed, undefined !== ovrs || hiliteUpdated ? hilite : undefined, ovrs);
            }
            this._lastFlashId = flashedId;
        }
    }
    bindLUTParams(uniform) {
        uniform.setUniform2fv(this._lutParams);
    }
    bindLUT(uniform) {
        if (this._lut)
            this._lut.bindSampler(uniform, RenderFlags_1.TextureUnit.FeatureSymbology);
    }
    bindUniformSymbologyFlags(uniform) {
        uniform.setUniform1f(this._uniformSymbologyFlags);
    }
}
exports.FeatureOverrides = FeatureOverrides;
//# sourceMappingURL=FeatureOverrides.js.map