/*---------------------------------------------------------------------------------------------
* 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, dispose } from "@itwin/core-bentley";
import { CartographicRange, FillFlags, RenderMode, TextureTransparency, ThematicGradientTransparencyMode } from "@itwin/core-common";
import { SurfaceType } from "../../../common/internal/render/SurfaceParams";
import { AttributeMap } from "./AttributeMap";
import { GL } from "./GL";
import { BufferHandle, BufferParameters, BuffersContainer } from "./AttributeBuffers";
import { System } from "./System";
import { MeshGeometry } from "./MeshGeometry";
import { LayerTextureParams, ProjectedTexture } from "./MapLayerParams";
import { MapCartoRectangle, PlanarProjection, PlanarTilePatch } from "../../../tile/internal";
import { Vector3d } from "@itwin/core-geometry";
/** @internal */
export function wantMaterials(vf) {
    return vf.materials && RenderMode.SmoothShade === vf.renderMode;
}
function wantLighting(vf) {
    return RenderMode.SmoothShade === vf.renderMode && vf.lighting;
}
/** @internal */
export class SurfaceGeometry extends MeshGeometry {
    _buffers;
    _indices;
    hasTextures;
    textureParams;
    get lutBuffers() { return this._buffers; }
    static create(mesh, params) {
        const indices = params.surface.indices;
        const indexBuffer = BufferHandle.createArrayBuffer(indices.data);
        const tile = params.tileData;
        const layerClassifiers = tile?.layerClassifiers;
        if (!layerClassifiers?.size || !tile || undefined === layerClassifiers) {
            return undefined !== indexBuffer ? new SurfaceGeometry(indexBuffer, indices.length, mesh, undefined) : undefined;
        }
        const transformECEF = tile.ecefTransform;
        const tileEcefRange = transformECEF.multiplyRange(tile.range);
        const cartographicRange = new CartographicRange(tileEcefRange, transformECEF);
        const boundingBox = cartographicRange.getLongitudeLatitudeBoundingBox();
        const mapCartoRectangle = MapCartoRectangle.fromRadians(boundingBox.low.x, boundingBox.low.y, boundingBox.high.x, boundingBox.high.y);
        const corners = tile.range.corners();
        const normal = Vector3d.createCrossProductToPoints(corners[0], corners[1], corners[2])?.normalize();
        if (!normal) {
            return undefined !== indexBuffer ? new SurfaceGeometry(indexBuffer, indices.length, mesh, undefined) : undefined;
        }
        const chordHeight = corners[0].distance(corners[3]) / 2;
        const surfacePlanarTilePatch = new PlanarTilePatch(corners, normal, chordHeight);
        const surfaceProjection = new PlanarProjection(surfacePlanarTilePatch);
        const meshParams = {
            projection: surfaceProjection,
            tileRectangle: mapCartoRectangle,
            tileId: undefined,
            baseColor: undefined,
            baseTransparent: false,
            layerClassifiers
        };
        const layerTextures = [];
        let sequentialIndex = 0;
        layerClassifiers?.forEach((layerClassifier) => {
            layerTextures[sequentialIndex++] = new ProjectedTexture(layerClassifier, meshParams, meshParams.tileRectangle);
        });
        let surfaceGeometry;
        if (undefined !== indexBuffer) {
            const indexCount = indices.length;
            const hasLayerTextures = layerTextures.length > 0;
            const layerTextureParams = hasLayerTextures ? LayerTextureParams.create(layerTextures) : undefined;
            surfaceGeometry = new SurfaceGeometry(indexBuffer, indexCount, mesh, layerTextureParams);
        }
        return surfaceGeometry;
    }
    get isDisposed() {
        return this._buffers.isDisposed
            && this._indices.isDisposed;
    }
    [Symbol.dispose]() {
        dispose(this._buffers);
        dispose(this._indices);
    }
    collectStatistics(stats) {
        stats.addSurface(this._indices.bytesUsed);
    }
    get isLit() { return SurfaceType.Lit === this.surfaceType || SurfaceType.TexturedLit === this.surfaceType; }
    get isTexturedType() { return SurfaceType.Textured === this.surfaceType || SurfaceType.TexturedLit === this.surfaceType; }
    get hasTexture() { return this.isTexturedType && undefined !== this.texture; }
    get hasNormalMap() { return this.isLit && this.isTexturedType && undefined !== this.normalMap; }
    get isGlyph() { return this.mesh.isGlyph; }
    get alwaysRenderTranslucent() { return this.isGlyph; }
    get isTileSection() { return undefined !== this.texture && this.texture.isTileSection; }
    get isClassifier() { return SurfaceType.VolumeClassifier === this.surfaceType; }
    get supportsThematicDisplay() {
        return !this.isGlyph;
    }
    get allowColorOverride() {
        // Text background color should not be overridden by feature symbology overrides - otherwise it becomes unreadable...
        // We don't actually know if we have text.
        // We do know that text background color uses blanking fill. So do ImageGraphics, so they're also going to forbid overriding their color.
        return FillFlags.Blanking !== (this.fillFlags & FillFlags.Blanking);
    }
    get asSurface() { return this; }
    get asEdge() { return undefined; }
    get asSilhouette() { return undefined; }
    _draw(numInstances, instanceBuffersContainer) {
        const system = System.instance;
        // If we can't write depth in the fragment shader, use polygonOffset to force blanking regions to draw behind.
        const offset = 2 /* RenderOrder.BlankingRegion */ === this.renderOrder && !system.supportsLogZBuffer;
        if (offset) {
            system.context.enable(GL.POLYGON_OFFSET_FILL);
            system.context.polygonOffset(1.0, 1.0);
        }
        const bufs = instanceBuffersContainer !== undefined ? instanceBuffersContainer : this._buffers;
        bufs.bind();
        system.drawArrays(GL.PrimitiveType.Triangles, 0, this._numIndices, numInstances);
        bufs.unbind();
        if (offset)
            system.context.disable(GL.POLYGON_OFFSET_FILL);
    }
    wantMixMonochromeColor(target) {
        // Text relies on white-on-white reversal.
        return !this.isGlyph && (this.isLitSurface || this.wantTextures(target, this.hasTexture));
    }
    get techniqueId() { return 0 /* TechniqueId.Surface */; }
    get isLitSurface() { return this.isLit; }
    get hasBakedLighting() { return this.mesh.hasBakedLighting; }
    get renderOrder() {
        if (FillFlags.Behind === (this.fillFlags & FillFlags.Behind))
            return 2 /* RenderOrder.BlankingRegion */;
        let order = this.isLit ? 4 /* RenderOrder.LitSurface */ : 3 /* RenderOrder.UnlitSurface */;
        if (this.isPlanar)
            order = order | 8 /* RenderOrder.PlanarBit */;
        return order;
    }
    getColor(target) {
        if (FillFlags.Background === (this.fillFlags & FillFlags.Background))
            return target.uniforms.style.backgroundColorInfo;
        else
            return this.colorInfo;
    }
    getPass(target) {
        // Classifiers have a dedicated pass
        if (this.isClassifier)
            return "classification";
        let opaquePass = this.isPlanar ? "opaque-planar" : "opaque";
        // When reading pixels, glyphs are always opaque. Otherwise always transparent (for anti-aliasing).
        if (this.isGlyph)
            return target.isReadPixelsInProgress ? opaquePass : "translucent";
        const vf = target.currentViewFlags;
        // When rendering thematic isolines, we need translucency because they have anti-aliasing.
        const thematic = target.wantThematicDisplay && this.supportsThematicDisplay ? target.uniforms.thematic.thematicDisplay : undefined;
        if (thematic && target.uniforms.thematic.wantIsoLines)
            return "translucent";
        // In wireframe, unless fill is explicitly enabled for planar region, surface does not draw
        if (RenderMode.Wireframe === vf.renderMode && !this.mesh.isTextureAlwaysDisplayed) {
            const fillFlags = this.fillFlags;
            const showFill = FillFlags.Always === (fillFlags & FillFlags.Always) || (vf.fill && FillFlags.ByView === (fillFlags & FillFlags.ByView));
            if (!showFill)
                return "none";
        }
        // If transparency disabled by render mode or view flag, always draw opaque.
        if (!vf.transparency || RenderMode.SolidFill === vf.renderMode || RenderMode.HiddenLine === vf.renderMode)
            return opaquePass;
        // A gradient texture applied by analysis style always fully determines the transparency of the surface.
        if (this.hasScalarAnimation && undefined !== target.analysisTexture) {
            assert(undefined !== target.analysisStyle?.thematic);
            switch (target.analysisStyle.thematic.thematicSettings.textureTransparency) {
                case TextureTransparency.Translucent:
                    return "translucent";
                case TextureTransparency.Opaque:
                    return opaquePass;
                case TextureTransparency.Mixed:
                    return `${opaquePass}-translucent`;
            }
        }
        // We have 3 sources of alpha: the material, the texture, and the color.
        // Base alpha comes from the material if it overrides it; otherwise from the color.
        // The texture's alpha is multiplied by the base alpha.
        // So we must draw in the translucent pass if the texture has transparency OR the base alpha is less than 1.
        let hasAlpha = false;
        const mat = wantMaterials(vf) ? this.mesh.materialInfo : undefined;
        if (undefined !== mat && mat.overridesAlpha)
            hasAlpha = mat.hasTranslucency;
        else
            hasAlpha = this.getColor(target).hasTranslucency;
        // Thematic gradient can optionally multiply gradient alpha with surface alpha.
        if (thematic && thematic.gradientSettings.transparencyMode === ThematicGradientTransparencyMode.MultiplySurfaceAndGradient) {
            switch (thematic.gradientSettings.textureTransparency) {
                case TextureTransparency.Opaque:
                    // This surface's alpha gets multiplied by 1 - gradient colors are all opaque.
                    return hasAlpha ? "translucent" : opaquePass;
                case TextureTransparency.Translucent:
                    // This surface's alpha gets multiplied by < 1 - gradient colors are all translucent.
                    return "translucent";
                case TextureTransparency.Mixed:
                    // The gradient contains a mix of translucent and opaque colors.
                    return hasAlpha ? "translucent" : `${opaquePass}-translucent`;
            }
        }
        if (!hasAlpha) {
            const tex = this.wantTextures(target, true) ? this.texture : undefined;
            switch (tex?.transparency) {
                case TextureTransparency.Translucent:
                    hasAlpha = true;
                    break;
                case TextureTransparency.Mixed:
                    opaquePass = `${opaquePass}-translucent`;
                    break;
            }
        }
        return hasAlpha ? "translucent" : opaquePass;
    }
    _wantWoWReversal(target) {
        if (this.isGlyph) {
            // Raster text is always subject to white-on-white reversal.
            return true;
        }
        const fillFlags = this.fillFlags;
        if (FillFlags.None !== (fillFlags & FillFlags.Background)) {
            return false; // fill color explicitly from background
        }
        if (this.wantTextures(target, this.hasTexture)) {
            // Don't invert white pixels of textures.
            return false;
        }
        const vf = target.currentViewFlags;
        if (RenderMode.Wireframe === vf.renderMode) {
            // Fill displayed even in wireframe?
            return FillFlags.None !== (fillFlags & FillFlags.Always);
        }
        if (vf.visibleEdges) {
            return false; // never invert surfaces when edges are displayed
        }
        if (this.isLit && wantLighting(vf)) {
            return false; // the lit color won't be pure white anyway.
        }
        return true;
    }
    get materialInfo() { return this.mesh.materialInfo; }
    useTexture(params) {
        return this.wantTextures(params.target, this.hasTexture);
    }
    useNormalMap(params) {
        return this.wantNormalMaps(params.target, this.hasNormalMap);
    }
    computeSurfaceFlags(params, flags) {
        const target = params.target;
        const vf = target.currentViewFlags;
        const useMaterial = wantMaterials(vf);
        flags[3 /* SurfaceBitIndex.IgnoreMaterial */] = useMaterial ? 0 : 1;
        flags[9 /* SurfaceBitIndex.HasMaterialAtlas */] = useMaterial && this.hasMaterialAtlas ? 1 : 0;
        flags[1 /* SurfaceBitIndex.ApplyLighting */] = 0;
        flags[6 /* SurfaceBitIndex.HasColorAndNormal */] = 0;
        if (this.isLit) {
            flags[2 /* SurfaceBitIndex.HasNormals */] = 1;
            if (wantLighting(vf))
                flags[1 /* SurfaceBitIndex.ApplyLighting */] = 1;
            // Textured meshes store normal in place of color index.
            // Untextured lit meshes store normal where textured meshes would store UV coords.
            // Tell shader where to find normal.
            if (!this.isTexturedType) {
                flags[6 /* SurfaceBitIndex.HasColorAndNormal */] = 1;
            }
        }
        else {
            flags[2 /* SurfaceBitIndex.HasNormals */] = 0;
        }
        flags[0 /* SurfaceBitIndex.HasTexture */] = this.useTexture(params) ? 1 : 0;
        flags[8 /* SurfaceBitIndex.HasNormalMap */] = this.useNormalMap(params) ? 1 : 0;
        flags[10 /* SurfaceBitIndex.UseConstantLodTextureMapping */] = this.mesh.textureUsesConstantLod ? 1 : 0;
        flags[11 /* SurfaceBitIndex.UseConstantLodNormalMapMapping */] = this.mesh.normalMapUsesConstantLod ? 1 : 0;
        // The transparency threshold controls how transparent a surface must be to allow light to pass through; more opaque surfaces cast shadows.
        flags[4 /* SurfaceBitIndex.TransparencyThreshold */] = params.target.isDrawingShadowMap ? 1 : 0;
        flags[5 /* SurfaceBitIndex.BackgroundFill */] = 0;
        switch (params.renderPass) {
            // NB: We need this for opaque pass due to SolidFill (must compute transparency, discard below threshold, render opaque at or above threshold)
            case 2 /* RenderPass.OpaqueLinear */:
            case 3 /* RenderPass.OpaquePlanar */:
            case 5 /* RenderPass.OpaqueGeneral */:
            case 8 /* RenderPass.Translucent */:
            case 12 /* RenderPass.WorldOverlay */:
            case 1 /* RenderPass.OpaqueLayers */:
            case 7 /* RenderPass.TranslucentLayers */:
            case 11 /* RenderPass.OverlayLayers */: {
                const mode = vf.renderMode;
                if (!this.isGlyph && (RenderMode.HiddenLine === mode || RenderMode.SolidFill === mode)) {
                    flags[4 /* SurfaceBitIndex.TransparencyThreshold */] = 1;
                    if (RenderMode.HiddenLine === mode && FillFlags.Always !== (this.fillFlags & FillFlags.Always)) {
                        // fill flags test for text - doesn't render with bg fill in hidden line mode.
                        flags[5 /* SurfaceBitIndex.BackgroundFill */] = 1;
                    }
                    break;
                }
            }
        }
    }
    constructor(indices, numIndices, mesh, textureParams) {
        super(mesh, numIndices);
        this.textureParams = textureParams;
        this._buffers = BuffersContainer.create();
        const attrPos = AttributeMap.findAttribute("a_pos", 0 /* TechniqueId.Surface */, false);
        assert(undefined !== attrPos);
        this._buffers.addBuffer(indices, [BufferParameters.create(attrPos.location, 3, GL.DataType.UnsignedByte, false, 0, 0, false)]);
        this._indices = indices;
        this.hasTextures = undefined !== this.textureParams && this.textureParams.params.some((x) => undefined !== x.texture);
    }
    wantTextures(target, surfaceTextureExists) {
        if (this.hasScalarAnimation && undefined !== target.analysisTexture)
            return true;
        if (!surfaceTextureExists)
            return false;
        if (this.mesh.isTextureAlwaysDisplayed)
            return true;
        if (this.supportsThematicDisplay && target.wantThematicDisplay)
            return false;
        const fill = this.fillFlags;
        const flags = target.currentViewFlags;
        // ###TODO need to distinguish between gradient fill and actual textures...
        switch (flags.renderMode) {
            case RenderMode.SmoothShade:
                return flags.textures;
            case RenderMode.Wireframe:
                return FillFlags.Always === (fill & FillFlags.Always) || (flags.fill && FillFlags.ByView === (fill & FillFlags.ByView));
            default:
                return FillFlags.Always === (fill & FillFlags.Always);
        }
    }
    wantNormalMaps(target, normalMapExists) {
        if (!normalMapExists || !target.displayNormalMaps)
            return false;
        const flags = target.currentViewFlags;
        switch (flags.renderMode) {
            case RenderMode.SmoothShade:
                return flags.textures;
            default:
                return false;
        }
    }
}
//# sourceMappingURL=SurfaceGeometry.js.map