"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RealityMeshGeometry = exports.RealityMeshGeometryParams = void 0;
/** @packageDocumentation
 * @module WebGL
 */
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const core_geometry_1 = require("@itwin/core-geometry");
const GraphicBranch_1 = require("../../../render/GraphicBranch");
const AttributeBuffers_1 = require("./AttributeBuffers");
const AttributeMap_1 = require("./AttributeMap");
const CachedGeometry_1 = require("./CachedGeometry");
const GL_1 = require("./GL");
const Primitive_1 = require("./Primitive");
const System_1 = require("./System");
const RenderTerrain_1 = require("../RenderTerrain");
const internal_1 = require("../../../tile/internal");
const MapLayerParams_1 = require("./MapLayerParams");
const scratchOverlapRange = core_geometry_1.Range2d.createNull();
/** @internal */
class RealityMeshGeometryParams extends CachedGeometry_1.IndexedGeometryParams {
    uvParams;
    featureID;
    normals;
    numBytesPerIndex;
    constructor(positions, normals, uvParams, indices, numIndices, numBytesPerIndex, featureID) {
        super(positions, indices, numIndices);
        this.numBytesPerIndex = numBytesPerIndex;
        let attrParams = AttributeMap_1.AttributeMap.findAttribute("a_uvParam", 7 /* TechniqueId.RealityMesh */, false);
        (0, core_bentley_1.assert)(attrParams !== undefined);
        this.buffers.addBuffer(uvParams, [AttributeBuffers_1.BufferParameters.create(attrParams.location, 2, GL_1.GL.DataType.UnsignedShort, false, 0, 0, false)]);
        this.uvParams = uvParams;
        if (undefined !== normals) {
            attrParams = AttributeMap_1.AttributeMap.findAttribute("a_norm", 7 /* TechniqueId.RealityMesh */, false);
            (0, core_bentley_1.assert)(attrParams !== undefined);
            if (normals.bytesUsed > 0)
                this.buffers.addBuffer(normals, [AttributeBuffers_1.BufferParameters.create(attrParams.location, 2, GL_1.GL.DataType.UnsignedByte, false, 0, 0, false)]);
            this.normals = normals;
        }
        this.featureID = featureID;
    }
    static createFromBuffers(posBuf, uvParamBuf, indices, normBuf, featureID) {
        const indBuf = AttributeBuffers_1.BufferHandle.createBuffer(GL_1.GL.Buffer.Target.ElementArrayBuffer, indices);
        if (undefined === indBuf)
            return undefined;
        const bytesPerIndex = indices.BYTES_PER_ELEMENT;
        (0, core_bentley_1.assert)(1 === bytesPerIndex || 2 === bytesPerIndex || 4 === bytesPerIndex);
        return new RealityMeshGeometryParams(posBuf, normBuf, uvParamBuf, indBuf, indices.length, bytesPerIndex, featureID);
    }
    static fromRealityMesh(params) {
        const posBuf = AttributeBuffers_1.QBufferHandle3d.create(params.positions.params, params.positions.points);
        const uvParamBuf = AttributeBuffers_1.QBufferHandle2d.create(params.uvs.params, params.uvs.points);
        const normalBuf = params.normals ? AttributeBuffers_1.BufferHandle.createArrayBuffer(params.normals) : undefined;
        return (undefined === posBuf || undefined === uvParamBuf) ? undefined : this.createFromBuffers(posBuf, uvParamBuf, params.indices, normalBuf, params.featureID ?? 0);
    }
    get isDisposed() {
        return super.isDisposed && this.uvParams.isDisposed;
    }
    get bytesUsed() { return this.positions.bytesUsed + (undefined === this.normals ? 0 : this.normals.bytesUsed) + this.uvParams.bytesUsed + this.indices.bytesUsed; }
    [Symbol.dispose]() {
        super[Symbol.dispose]();
        (0, core_bentley_1.dispose)(this.uvParams);
    }
}
exports.RealityMeshGeometryParams = RealityMeshGeometryParams;
/** @internal */
class RealityMeshGeometry extends CachedGeometry_1.IndexedGeometry {
    renderGeometryType = "reality-mesh";
    isInstanceable = false;
    noDispose = false;
    hasTextures;
    get asRealityMesh() { return this; }
    get isDisposed() { return this._realityMeshParams.isDisposed; }
    get uvQParams() { return this._realityMeshParams.uvParams.params; }
    get hasFeatures() { return this._realityMeshParams.featureID !== undefined; }
    get supportsThematicDisplay() { return true; }
    get overrideColorMix() { return .5; } // This could be a setting from either the mesh or the override if required.
    get transform() { return this._transform; }
    _realityMeshParams;
    _indexType;
    textureParams;
    _transform;
    baseColor;
    _baseIsTransparent;
    _isTerrain;
    _disableTextureDisposal;
    constructor(props) {
        super(props.realityMeshParams);
        this._realityMeshParams = props.realityMeshParams;
        this.textureParams = props.textureParams;
        this._transform = props.transform;
        this.baseColor = props.baseColor;
        this._baseIsTransparent = props.baseIsTransparent;
        this._isTerrain = props.isTerrain;
        this._disableTextureDisposal = props.disableTextureDisposal;
        this.hasTextures = undefined !== this.textureParams && this.textureParams.params.some((x) => undefined !== x.texture);
        const bytesPerIndex = props.realityMeshParams.numBytesPerIndex;
        this._indexType = 1 === bytesPerIndex ? GL_1.GL.DataType.UnsignedByte : (2 === bytesPerIndex ? GL_1.GL.DataType.UnsignedShort : GL_1.GL.DataType.UnsignedInt);
    }
    [Symbol.dispose]() {
        if (this.noDispose) {
            return;
        }
        super[Symbol.dispose]();
        (0, core_bentley_1.dispose)(this._realityMeshParams);
        if (true !== this._disableTextureDisposal)
            (0, core_bentley_1.dispose)(this.textureParams);
    }
    static createForTerrain(mesh, transform, disableTextureDisposal = false) {
        const params = RealityMeshGeometryParams.fromRealityMesh(mesh);
        if (!params)
            return undefined;
        return new RealityMeshGeometry({
            realityMeshParams: params,
            transform,
            baseIsTransparent: false,
            isTerrain: true,
            disableTextureDisposal,
        });
    }
    static createFromRealityMesh(realityMesh, disableTextureDisposal = false) {
        const params = RealityMeshGeometryParams.fromRealityMesh(realityMesh);
        if (!params)
            return undefined;
        const { texture: meshTexture, featureID } = realityMesh;
        const tile = realityMesh.tileData;
        const layerClassifiers = tile?.layerClassifiers;
        const texture = meshTexture ? new RenderTerrain_1.TerrainTexture(meshTexture, featureID ?? 0, core_geometry_1.Vector2d.create(1.0, -1.0), core_geometry_1.Vector2d.create(0.0, 1.0), core_geometry_1.Range2d.createXYXY(0, 0, 1, 1), 0, 0) : undefined;
        if (!layerClassifiers?.size || !tile)
            return new RealityMeshGeometry({ realityMeshParams: params, textureParams: texture ? MapLayerParams_1.LayerTextureParams.create([texture]) : undefined, baseIsTransparent: false, isTerrain: false, disableTextureDisposal });
        const transformECEF = tile.ecefTransform;
        const tileEcefRange = transformECEF.multiplyRange(tile.range);
        const cartographicRange = new core_common_1.CartographicRange(tileEcefRange, transformECEF);
        const boundingBox = cartographicRange.getLongitudeLatitudeBoundingBox();
        const mapCartoRectangle = internal_1.MapCartoRectangle.fromRadians(boundingBox.low.x, boundingBox.low.y, boundingBox.high.x, boundingBox.high.y);
        const corners = tile.range.corners();
        const normal = core_geometry_1.Vector3d.createCrossProductToPoints(corners[0], corners[1], corners[2])?.normalize();
        if (!normal) {
            return new RealityMeshGeometry({ realityMeshParams: params, textureParams: texture ? MapLayerParams_1.LayerTextureParams.create([texture]) : undefined, baseIsTransparent: false, isTerrain: false, disableTextureDisposal });
        }
        const chordHeight = corners[0].distance(corners[3]) / 2;
        const realityPlanarTilePatch = new internal_1.PlanarTilePatch(corners, normal, chordHeight);
        const realityProjection = new internal_1.PlanarProjection(realityPlanarTilePatch);
        const realityMeshParams = {
            projection: realityProjection,
            tileRectangle: mapCartoRectangle,
            tileId: undefined,
            baseColor: undefined,
            baseTransparent: false,
            layerClassifiers
        };
        const layerTextures = texture ? [texture] : [];
        layerClassifiers?.forEach((layerClassifier, layerIndex) => layerTextures[layerIndex] = new MapLayerParams_1.ProjectedTexture(layerClassifier, realityMeshParams, realityMeshParams.tileRectangle));
        return new RealityMeshGeometry({ realityMeshParams: params, textureParams: layerTextures.length > 0 ? MapLayerParams_1.LayerTextureParams.create(layerTextures) : undefined, baseIsTransparent: false, isTerrain: false, disableTextureDisposal });
    }
    getRange() {
        return core_geometry_1.Range3d.createXYZXYZ(this.qOrigin[0], this.qOrigin[1], this.qOrigin[2], this.qOrigin[0] + core_common_1.Quantization.rangeScale16 * this.qScale[0], this.qOrigin[1] + core_common_1.Quantization.rangeScale16 * this.qScale[1], this.qOrigin[2] + core_common_1.Quantization.rangeScale16 * this.qScale[2]);
    }
    static createGraphic(system, params, disableTextureDisposal = false) {
        const meshes = [];
        const textures = params.textures ?? [];
        const realityMesh = params.realityMesh;
        const { baseColor, baseTransparent, featureTable, tileId, layerClassifiers } = params;
        const texturesPerMesh = System_1.System.instance.maxRealityImageryLayers;
        const layers = new Array();
        // Collate the textures and classifiers layers into a single array.
        for (const texture of textures) {
            const layer = layers[texture.layerIndex];
            if (layer) {
                layer.push(texture);
            }
            else {
                layers[texture.layerIndex] = [texture];
            }
        }
        params.layerClassifiers?.forEach((layerClassifier, layerIndex) => layers[layerIndex] = [new MapLayerParams_1.ProjectedTexture(layerClassifier, params, params.tileRectangle)]);
        if (layers.length < 2 && !layerClassifiers?.size && textures.length < texturesPerMesh) {
            // If only there is not more than one layer then we can group all of the textures into a single draw call.
            meshes.push(new RealityMeshGeometry({ realityMeshParams: realityMesh._realityMeshParams, textureParams: MapLayerParams_1.LayerTextureParams.create(textures), transform: realityMesh._transform, baseColor, baseIsTransparent: baseTransparent, isTerrain: realityMesh._isTerrain, disableTextureDisposal }));
        }
        else {
            let primaryLayer;
            while (primaryLayer === undefined)
                primaryLayer = layers.shift();
            if (!primaryLayer)
                return undefined;
            for (const primaryTexture of primaryLayer) {
                const targetRectangle = primaryTexture.targetRectangle;
                const overlapMinimum = 1.0E-5 * (targetRectangle.high.x - targetRectangle.low.x) * (targetRectangle.high.y - targetRectangle.low.y);
                let layerTextures = [primaryTexture];
                for (const secondaryLayer of layers) {
                    if (!secondaryLayer)
                        continue;
                    for (const secondaryTexture of secondaryLayer) {
                        if (secondaryTexture instanceof MapLayerParams_1.ProjectedTexture) {
                            layerTextures.push(secondaryTexture.clone(targetRectangle));
                        }
                        else {
                            const secondaryRectangle = secondaryTexture.targetRectangle;
                            const overlap = targetRectangle.intersect(secondaryRectangle, scratchOverlapRange);
                            if (!overlap.isNull && (overlap.high.x - overlap.low.x) * (overlap.high.y - overlap.low.y) > overlapMinimum) {
                                const textureRange = core_geometry_1.Range2d.createXYXY(overlap.low.x, overlap.low.y, overlap.high.x, overlap.high.y);
                                secondaryRectangle.worldToLocal(textureRange.low, textureRange.low);
                                secondaryRectangle.worldToLocal(textureRange.high, textureRange.high);
                                if (secondaryTexture.clipRectangle)
                                    textureRange.intersect(secondaryTexture.clipRectangle, textureRange);
                                if (!textureRange.isNull && textureRange) {
                                    layerTextures.push(secondaryTexture.cloneWithClip(textureRange));
                                }
                            }
                        }
                    }
                }
                while (layerTextures.length > texturesPerMesh) {
                    meshes.push(new RealityMeshGeometry({ realityMeshParams: realityMesh._realityMeshParams, textureParams: MapLayerParams_1.LayerTextureParams.create(layerTextures.slice(0, texturesPerMesh)), transform: realityMesh._transform, baseColor, baseIsTransparent: baseTransparent, isTerrain: realityMesh._isTerrain, disableTextureDisposal }));
                    layerTextures = layerTextures.slice(texturesPerMesh);
                }
                meshes.push(new RealityMeshGeometry({ realityMeshParams: realityMesh._realityMeshParams, textureParams: MapLayerParams_1.LayerTextureParams.create(layerTextures), transform: realityMesh._transform, baseColor, baseIsTransparent: baseTransparent, isTerrain: realityMesh._isTerrain, disableTextureDisposal }));
            }
        }
        if (meshes.length === 0)
            return undefined;
        const branch = new GraphicBranch_1.GraphicBranch(true);
        for (const mesh of meshes) {
            const primitive = Primitive_1.Primitive.create(mesh);
            if (featureTable) {
                branch.add(system.createBatch((0, core_bentley_1.expectDefined)(primitive), featureTable, mesh.getRange(), { tileId }));
            }
        }
        return system.createBranch(branch, realityMesh._transform ? realityMesh._transform : core_geometry_1.Transform.createIdentity(), { disableClipStyle: params.disableClipStyle });
    }
    collectStatistics(stats) {
        this._isTerrain ? stats.addTerrain(this._realityMeshParams.bytesUsed) : stats.addRealityMesh(this._realityMeshParams.bytesUsed);
        if (this.textureParams?.params) {
            for (const param of this.textureParams.params) {
                if (param.texture?.bytesUsed)
                    stats.addTexture(param.texture.bytesUsed);
            }
        }
    }
    get techniqueId() { return 7 /* TechniqueId.RealityMesh */; }
    getPass(target) {
        if (this._baseIsTransparent || (target.wantThematicDisplay && target.uniforms.thematic.wantIsoLines))
            return "translucent";
        return "opaque";
    }
    get renderOrder() { return 3 /* RenderOrder.UnlitSurface */; }
    draw() {
        this._params.buffers.bind();
        System_1.System.instance.context.drawElements(GL_1.GL.PrimitiveType.Triangles, this._params.numIndices, this._indexType, 0);
        this._params.buffers.unbind();
    }
}
exports.RealityMeshGeometry = RealityMeshGeometry;
//# sourceMappingURL=RealityMesh.js.map