/*---------------------------------------------------------------------------------------------
* 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 { Range2d } from "@itwin/core-geometry";
import { TerrainTexture } from "../RenderTerrain";
import { Matrix4 } from "./Matrix";
import { ModelMapLayerDrapeTarget, ModelMapLayerSettings } from "@itwin/core-common";
import { assert, dispose, disposeArray, expectDefined } from "@itwin/core-bentley";
import { System } from "./System";
export class ProjectedTexture {
    meshParams;
    targetRectangle;
    classifier;
    constructor(classifier, meshParams, targetRectangle) {
        this.meshParams = meshParams;
        this.targetRectangle = targetRectangle;
        this.classifier = classifier;
    }
    clone(targetRectangle) {
        return new ProjectedTexture(this.classifier, this.meshParams, targetRectangle.clone());
    }
}
const scratchBytes = new Uint8Array(4);
const scratchBatchBaseId = new Uint32Array(scratchBytes.buffer);
const scratchRange2d = Range2d.createNull();
class LayerTextureParam {
    texture;
    _projectedTextureOrMatrix;
    constructor(texture, _projectedTextureOrMatrix) {
        this.texture = texture;
        this._projectedTextureOrMatrix = _projectedTextureOrMatrix;
    }
    get isProjected() { return this._projectedTextureOrMatrix instanceof ProjectedTexture; }
    [Symbol.dispose]() {
        this.texture = dispose(this.texture);
    }
    /* There are two methods of applying a texture to a reality mesh.  the first member of "params" denotes which
    method is to be used.  A value of zero indicates a standard texture and one represents a classified texture.
  
    A standard (nonprojected) texture is generated by multiplying v_textCoord by the scaling and translation packed into the first row
    of "matrix". A clip rectangle is packed into second row of "matrix".
  
    A "classified" reality mesh texture is used for map layers.  It does not uses v_texCoord, the texture coordinates
    are instead generated by a projection of the model position onto the X-Y plane. We only have eye position, not model position
    so the matrix in this case is a real transform matrix that contains a mapping from eye to model position
    followed by the model to texture projection.
  */
    getProjectionMatrix() {
        return this._projectedTextureOrMatrix instanceof ProjectedTexture ? this._projectedTextureOrMatrix.classifier.projectionMatrix : undefined;
    }
    getTerrainMatrix() {
        return this._projectedTextureOrMatrix instanceof Matrix4 ? this._projectedTextureOrMatrix : undefined;
    }
    getParams(result) {
        /** Entry 0 is 0 for  */
        if (this._projectedTextureOrMatrix instanceof ProjectedTexture) {
            const projectedTexture = this._projectedTextureOrMatrix;
            result.data[0] = 1;
            result.data[1] = projectedTexture.classifier.textureImageCount;
            result.data[2] = projectedTexture.classifier.sourceTransparency === undefined ? 1.0 : (1.0 - projectedTexture.classifier.sourceTransparency);
            scratchBatchBaseId[0] = projectedTexture.classifier.baseBatchId;
            result.data[4] = scratchBytes[0];
            result.data[5] = scratchBytes[1];
            result.data[6] = scratchBytes[2];
            result.data[7] = scratchBytes[3];
            const points = [];
            const meshParams = projectedTexture.meshParams;
            // Calculate range in the tiles local coordinates.
            const low = expectDefined(meshParams.tileRectangle.worldToLocal(projectedTexture.targetRectangle.low, scratchRange2d.low));
            const high = expectDefined(meshParams.tileRectangle.worldToLocal(projectedTexture.targetRectangle.high, scratchRange2d.high));
            points.push(meshParams.projection.getGlobalPoint(low.x, low.y, 0));
            points.push(meshParams.projection.getGlobalPoint(high.x, low.y, 0));
            points.push(meshParams.projection.getGlobalPoint(high.x, high.y, 0));
            points.push(meshParams.projection.getGlobalPoint(low.x, high.y, 0));
            for (let i = 0, j = 8; i < 4; i++) {
                const projectedPoint = projectedTexture.classifier.projectionMatrix.multiplyPoint3dQuietNormalize(points[i]);
                result.data[j++] = projectedPoint.x;
                result.data[j++] = projectedPoint.y;
            }
            const x0 = result.data[10] - result.data[8], y0 = result.data[11] - result.data[9];
            const x1 = result.data[12] - result.data[8], y1 = result.data[13] - result.data[9];
            if (x0 * y1 - x1 * y0 < 0) {
                const swap = ((i, j) => {
                    const temp = result.data[i];
                    result.data[i] = result.data[j];
                    result.data[j] = temp;
                });
                for (let i = 8, j = 14; i <= 10; i += 2, j -= 2) {
                    swap(i, j);
                    swap(i + 1, j + 1);
                }
            }
        }
        else {
            result.data[0] = 0;
        }
        return result;
    }
}
/** @internal */
export class LayerTextureParams {
    params;
    constructor(params) {
        this.params = params;
    }
    static create(textures) {
        const maxTexturesPerMesh = System.instance.maxRealityImageryLayers;
        assert(textures.length <= maxTexturesPerMesh);
        const textureParams = new Array();
        for (const texture of textures) {
            if (texture instanceof TerrainTexture) {
                const terrainTexture = texture;
                const matrix = new Matrix4(); // Published as Mat4.
                assert(terrainTexture.texture !== undefined, "Texture not defined in TerrainTextureParams constructor");
                matrix.data[0] = terrainTexture.translate.x;
                matrix.data[1] = terrainTexture.translate.y;
                matrix.data[2] = terrainTexture.scale.x;
                matrix.data[3] = terrainTexture.scale.y;
                if (terrainTexture.clipRectangle) {
                    matrix.data[4] = terrainTexture.clipRectangle.low.x;
                    matrix.data[5] = terrainTexture.clipRectangle.low.y;
                    matrix.data[6] = terrainTexture.clipRectangle.high.x;
                    matrix.data[7] = terrainTexture.clipRectangle.high.y;
                }
                else {
                    matrix.data[4] = matrix.data[5] = 0;
                    matrix.data[6] = matrix.data[7] = 1;
                }
                matrix.data[8] = (1.0 - terrainTexture.transparency);
                matrix.data[9] = terrainTexture.featureId;
                textureParams.push(new LayerTextureParam(terrainTexture.texture, matrix));
            }
            else {
                const classifier = texture.classifier;
                textureParams.push(new LayerTextureParam(classifier.getOrCreateClassifierTexture(), texture));
            }
        }
        for (let i = textures.length; i < maxTexturesPerMesh; i++) {
            const matrix = new Matrix4();
            matrix.data[0] = matrix.data[1] = 0.0;
            matrix.data[2] = matrix.data[3] = 1.0;
            matrix.data[4] = matrix.data[5] = 1;
            matrix.data[6] = matrix.data[7] = -1;
            matrix.data[15] = 0; // Denotes a terrain texture.
            textureParams.push(new LayerTextureParam(undefined, matrix));
        }
        return new LayerTextureParams(textureParams);
    }
    [Symbol.dispose]() {
        disposeArray(this.params);
    }
}
/**
 * Compares the map layers of two view states, ensuring both the number of layers
 * and their order remain unchanged.
 * Returns true if the map layers differ in count, order, or model IDs; otherwise, returns false.
 *
 * @param prevView The previous view state.
 * @param newView The new view state.
 * @returns {boolean} True if there is any difference in the model layer configuration; false otherwise.
 */
export function compareMapLayer(prevView, newView) {
    const getDrapedModelIds = (view) => view.displayStyle
        .getMapLayers(false)
        .filter((layer) => layer instanceof ModelMapLayerSettings &&
        (layer.drapeTarget === ModelMapLayerDrapeTarget.RealityData ||
            layer.drapeTarget === ModelMapLayerDrapeTarget.IModel))
        .map(layer => layer.modelId);
    const prev = getDrapedModelIds(prevView);
    const next = getDrapedModelIds(newView);
    if (prev.length !== next.length)
        return true;
    for (let i = 0; i < prev.length; i++) {
        if (prev[i] !== next[i])
            return true;
    }
    return false;
}
//# sourceMappingURL=MapLayerParams.js.map