"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.createRealityMeshBuilder = exports.createRealityMeshHiliter = exports.createClassifierRealityMeshHiliter = exports.addColorOverrideMix = exports.finalizeNormal = void 0;
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 AttributeMap_1 = require("../AttributeMap");
const Matrix_1 = require("../Matrix");
const RenderFlags_1 = require("../RenderFlags");
const ShaderBuilder_1 = require("../ShaderBuilder");
const System_1 = require("../System");
const Atmosphere_1 = require("./Atmosphere");
const Color_1 = require("./Color");
const Common_1 = require("./Common");
const Decode_1 = require("./Decode");
const FeatureSymbology_1 = require("./FeatureSymbology");
const Fragment_1 = require("./Fragment");
const PlanarClassification_1 = require("./PlanarClassification");
const SolarShadowMapping_1 = require("./SolarShadowMapping");
const Surface_1 = require("./Surface");
const Thematic_1 = require("./Thematic");
const Vertex_1 = require("./Vertex");
const Wiremesh_1 = require("./Wiremesh");
const computePosition = "gl_PointSize = 1.0; return MAT_MVP * rawPos;";
const computeNormal = `
  vec3 normal = octDecodeNormal(a_norm); // normal coming in for is already in world space
  g_hillshadeIndex = normal.z;           // save off world Z for thematic hill shade mode index
  return normalize(u_worldToViewN * normal);
`;
exports.finalizeNormal = `
  return normalize(v_n) * (2.0 * float(gl_FrontFacing) - 1.0);
`;
const testInside = `
bool testInside(float x0, float y0, float x1, float y1, float x, float y) {
  vec2 perp = vec2(y0 - y1, x1 - x0), test = vec2(x - x0, y - y0);
  float dot = (test.x * perp.x + test.y * perp.y) / sqrt(perp.x * perp.x + perp.y * perp.y);
  return dot >= -0.001;
}
`;
/* 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 projected 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 "projected" 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.
*/
const applyTexture = `
bool applyTexture(inout vec4 col, sampler2D sampler, mat4 params, mat4 matrix) {
  vec2 uv;
  float layerAlpha;
  bool isProjected = params[0][0] != 0.0;
  float imageCount = params[0][1];
  vec2 classPos;

  if (isProjected) {
    vec4 eye4 = vec4(v_eyeSpace, 1.0);
    vec4 classPos4 = matrix * eye4;
    classPos = classPos4.xy / classPos4.w;

    if (!testInside(params[2].x, params[2].y, params[2].z, params[2].w, classPos.x, classPos.y) ||
        !testInside(params[2].z, params[2].w, params[3].x, params[3].y, classPos.x, classPos.y) ||
        !testInside(params[3].x, params[3].y, params[3].z, params[3].w, classPos.x, classPos.y) ||
        !testInside(params[3].z, params[3].w, params[2].x, params[2].y, classPos.x, classPos.y))
        return false;

    uv.x = classPos.x;
    uv.y = classPos.y / imageCount;
    layerAlpha = params[0][2];

    if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
      return false;

  } else {
    vec4 texTransform = matrix[0].xyzw;
    vec4 texClip = matrix[1].xyzw;
    layerAlpha = matrix[2].x;
    uv = vec2(texTransform[0] + texTransform[2] * v_texCoord.x, texTransform[1] + texTransform[3] * v_texCoord.y);

    if (uv.x < texClip[0] || uv.x > texClip[2] || uv.y < texClip[1] || uv.y > texClip[3])
      return false;

    uv.y = 1.0 - uv.y;
  }

  vec4 texCol = TEXTURE(sampler, uv);
  float alpha = layerAlpha * texCol.a;

  if (alpha > 0.05) {
    vec3 texRgb = isProjected ? (texCol.rgb / texCol.a) : texCol.rgb; // If projected, undo premultiplication
    // Texture color is premultiplied earlier by alpha only if projected (from classification).

    col.rgb = (1.0 - alpha) * col.rgb + alpha * texRgb;

    if (isProjected) {
      vec4 featureTexel = TEXTURE(sampler, vec2(uv.x, (1.0 + classPos.y) / imageCount));
      classifierId = addUInt32s(params[1], featureTexel * 255.0) / 255.0;
    } else {
      featureIncrement = matrix[2].y;
      classifierId = vec4(0);
    }

    if (alpha > col.a)
      col.a = alpha;

    return true;
  }

  // If texture color is transparent but base color is not, return true (don't discard)
  // Else return false (discard) if both the texture and base color are transparent
  return (col.a > 0.05);
}
`;
const computeTexCoord = "return unquantize2d(a_uvParam, u_qTexCoordParams);";
const scratchMatrix4d1 = core_geometry_1.Matrix4d.createIdentity();
const scratchMatrix4d2 = core_geometry_1.Matrix4d.createIdentity();
const scratchMatrix = new Matrix_1.Matrix4();
const overrideFeatureId = `return (classifierId == vec4(0)) ? (addUInt32s(feature_id * 255.0, vec4(featureIncrement, 0.0, 0.0, 0.0)) / 255.0) : classifierId;`;
function addTextures(builder, maxTexturesPerMesh) {
    builder.vert.addFunction(Decode_1.unquantize2d);
    builder.addFunctionComputedVarying("v_texCoord", 3 /* VariableType.Vec2 */, "computeTexCoord", computeTexCoord);
    builder.vert.addUniform("u_qTexCoordParams", 5 /* VariableType.Vec4 */, (prog) => {
        prog.addGraphicUniform("u_qTexCoordParams", (uniform, params) => {
            const realityMesh = params.geometry.asRealityMesh;
            if (undefined !== realityMesh.uvQParams) {
                uniform.setUniform4fv(realityMesh.uvQParams);
            }
        });
    });
    builder.frag.addUniform("u_texturesPresent", 0 /* VariableType.Boolean */, (program) => {
        program.addGraphicUniform("u_texturesPresent", (uniform, params) => {
            uniform.setUniform1i(params.geometry.asRealityMesh.hasTextures ? 1 : 0);
        });
    });
    for (let i = 0; i < maxTexturesPerMesh; i++) {
        const textureLabel = `s_texture${i}`;
        builder.frag.addUniform(textureLabel, 8 /* VariableType.Sampler2D */, (prog) => {
            prog.addGraphicUniform(textureLabel, (uniform, params) => {
                const textureUnits = [RenderFlags_1.TextureUnit.RealityMesh0, RenderFlags_1.TextureUnit.RealityMesh1, params.target.drawForReadPixels ? RenderFlags_1.TextureUnit.ShadowMap : RenderFlags_1.TextureUnit.PickDepthAndOrder, RenderFlags_1.TextureUnit.RealityMesh3, RenderFlags_1.TextureUnit.RealityMesh4, RenderFlags_1.TextureUnit.RealityMesh5];
                const realityMesh = params.geometry.asRealityMesh;
                const realityTexture = realityMesh.textureParams ? realityMesh.textureParams.params[i].texture : undefined;
                if (realityTexture !== undefined) {
                    const texture = realityTexture;
                    texture.texture.bindSampler(uniform, textureUnits[i]);
                }
                else {
                    // assert(false, "Terrain Mesh texture not defined when beginning texture.");
                    System_1.System.instance.ensureSamplerBound(uniform, textureUnits[i]);
                }
            });
        });
        const paramsLabel = `u_texParams${i}`, matrixLabel = `u_texMatrix${i}`;
        builder.frag.addUniform(matrixLabel, 7 /* VariableType.Mat4 */, (prog) => {
            prog.addGraphicUniform(matrixLabel, (uniform, params) => {
                const realityMesh = params.geometry.asRealityMesh;
                const textureParam = realityMesh.textureParams?.params[i];
                (0, core_bentley_1.assert)(undefined !== textureParam);
                if (undefined !== textureParam) {
                    const projectionMatrix = textureParam.getProjectionMatrix();
                    if (projectionMatrix) {
                        const eyeToModel = core_geometry_1.Matrix4d.createTransform(params.target.uniforms.frustum.viewMatrix.inverse(), scratchMatrix4d1);
                        const eyeToTexture = projectionMatrix.multiplyMatrixMatrix(eyeToModel, scratchMatrix4d2);
                        uniform.setMatrix4(Matrix_1.Matrix4.fromMatrix4d(eyeToTexture, scratchMatrix));
                    }
                    else
                        uniform.setMatrix4(textureParam.getTerrainMatrix());
                }
            });
        });
        builder.frag.addUniform(paramsLabel, 7 /* VariableType.Mat4 */, (prog) => {
            prog.addGraphicUniform(paramsLabel, (uniform, params) => {
                const realityMesh = params.geometry.asRealityMesh;
                const textureParam = realityMesh.textureParams?.params[i];
                (0, core_bentley_1.assert)(undefined !== textureParam);
                if (undefined !== textureParam) {
                    uniform.setMatrix4(textureParam.getParams(scratchMatrix));
                }
            });
        });
    }
}
function baseColorFromTextures(textureCount, applyFeatureColor) {
    const applyTextureStrings = [];
    for (let i = 0; i < textureCount; i++)
        applyTextureStrings.push(`if (applyTexture(col, s_texture${i}, u_texParams${i}, u_texMatrix${i})) doDiscard = false; `);
    return `
  if (!u_texturesPresent) {
    vec4 col = u_baseColor;
    ${applyFeatureColor}
    return col;
  }

  bool doDiscard = true;
  vec4 col = u_baseColor;
  ${applyTextureStrings.join("\n  ")}
  if (doDiscard)
    discard;

  ${applyFeatureColor}

  return col;
`;
}
// feature_rgb.r = -1.0 if rgb color not overridden for feature.
// feature_alpha = -1.0 if alpha not overridden for feature.
const mixFeatureColor = `
  col.rgb = mix(col.rgb, mix(col.rgb, v_color.rgb, u_overrideColorMix), step(0.0, v_color.r));
  col.a = mix(col.a, v_color.a, step(0.0, v_color.a));
  `;
function addThematicToRealityMesh(builder, gradientTextureUnit) {
    (0, Vertex_1.addNormalMatrix)(builder.vert);
    builder.vert.addFunction(Surface_1.octDecodeNormal);
    builder.vert.addGlobal("g_hillshadeIndex", 2 /* VariableType.Float */);
    builder.addFunctionComputedVarying("v_n", 4 /* VariableType.Vec3 */, "computeLightingNormal", computeNormal);
    builder.frag.addGlobal("g_normal", 4 /* VariableType.Vec3 */);
    builder.frag.set(23 /* FragmentShaderComponent.FinalizeNormal */, exports.finalizeNormal);
    (0, Thematic_1.addThematicDisplay)(builder, false, true);
    builder.addInlineComputedVarying("v_thematicIndex", 2 /* VariableType.Float */, (0, Thematic_1.getComputeThematicIndex)(builder.vert.usesInstancedGeometry, false, false));
    builder.vert.addUniform("u_worldToViewN", 6 /* VariableType.Mat3 */, (prog) => {
        prog.addGraphicUniform("u_worldToViewN", (uniform, params) => {
            params.target.uniforms.branch.bindWorldToViewNTransform(uniform, params.geometry, false);
        });
    });
    builder.frag.addUniform("s_texture", 8 /* VariableType.Sampler2D */, (prog) => {
        prog.addGraphicUniform("s_texture", (uniform, params) => {
            params.target.uniforms.thematic.bindTexture(uniform, gradientTextureUnit >= 0 ? gradientTextureUnit : (params.target.drawForReadPixels ? RenderFlags_1.TextureUnit.ShadowMap : RenderFlags_1.TextureUnit.PickDepthAndOrder));
        });
    });
}
/** @internal */
function addColorOverrideMix(frag) {
    frag.addUniform("u_overrideColorMix", 2 /* VariableType.Float */, (prog) => {
        prog.addGraphicUniform("u_overrideColorMix", (uniform, params) => {
            params.target.uniforms.realityModel.bindOverrideColorMix(uniform);
        });
    });
}
exports.addColorOverrideMix = addColorOverrideMix;
function createRealityMeshHiliterBuilder() {
    const builder = new ShaderBuilder_1.ProgramBuilder(AttributeMap_1.AttributeMap.findAttributeMap(7 /* TechniqueId.RealityMesh */, false));
    const vert = builder.vert;
    vert.set(10 /* VertexShaderComponent.ComputePosition */, computePosition);
    (0, Vertex_1.addModelViewProjectionMatrix)(vert);
    builder.frag.set(17 /* FragmentShaderComponent.AssignFragData */, Fragment_1.assignFragColor);
    return builder;
}
/** @internal */
function createClassifierRealityMeshHiliter() {
    const builder = createRealityMeshHiliterBuilder();
    (0, PlanarClassification_1.addHilitePlanarClassifier)(builder, false);
    return builder;
}
exports.createClassifierRealityMeshHiliter = createClassifierRealityMeshHiliter;
/** @internal */
function createRealityMeshHiliter() {
    const builder = createRealityMeshHiliterBuilder();
    (0, FeatureSymbology_1.addHiliter)(builder, false);
    return builder;
}
exports.createRealityMeshHiliter = createRealityMeshHiliter;
/** @internal */
function createRealityMeshBuilder(flags) {
    const builder = new ShaderBuilder_1.ProgramBuilder(AttributeMap_1.AttributeMap.findAttributeMap(7 /* TechniqueId.RealityMesh */, false));
    const vert = builder.vert;
    vert.set(10 /* VertexShaderComponent.ComputePosition */, computePosition);
    (0, Vertex_1.addModelViewProjectionMatrix)(vert);
    if (flags.isShadowable === 1 /* IsShadowable.Yes */)
        (0, SolarShadowMapping_1.addSolarShadowMap)(builder, true);
    const frag = builder.frag;
    frag.addGlobal("featureIncrement", 2 /* VariableType.Float */, "0.0");
    frag.addGlobal("classifierId", 5 /* VariableType.Vec4 */);
    frag.set(18 /* FragmentShaderComponent.OverrideFeatureId */, overrideFeatureId);
    const textureCount = System_1.System.instance.maxRealityImageryLayers;
    const gradientTextureUnit = RenderFlags_1.TextureUnit.RealityMeshThematicGradient;
    const feat = flags.featureMode;
    let opts = 2 /* FeatureMode.Overrides */ === feat ? 28 /* FeatureSymbologyOptions.Surface */ : 0 /* FeatureSymbologyOptions.None */;
    let applyFragmentFeatureColor = "";
    if (flags.isClassified) {
        opts &= ~16 /* FeatureSymbologyOptions.Alpha */;
        (0, PlanarClassification_1.addColorPlanarClassifier)(builder, flags.isTranslucent, flags.isThematic);
        (0, Surface_1.addClassificationTranslucencyDiscard)(builder);
    }
    (0, FeatureSymbology_1.addFeatureSymbology)(builder, feat, opts);
    if (feat === 2 /* FeatureMode.Overrides */) {
        (0, Common_1.addShaderFlags)(builder);
        (0, Color_1.addVaryingColor)(builder, "return vec4(-1.0, -1.0, -1.0, -1.0);");
        applyFragmentFeatureColor = mixFeatureColor;
        addColorOverrideMix(builder.frag);
    }
    const computeFragmentBaseColor = baseColorFromTextures(textureCount, applyFragmentFeatureColor);
    frag.addFunction(Common_1.addUInt32s);
    frag.addFunction(testInside);
    (0, Common_1.addEyeSpace)(builder);
    frag.addFunction(applyTexture);
    frag.set(1 /* FragmentShaderComponent.ComputeBaseColor */, computeFragmentBaseColor);
    builder.frag.addUniform("u_baseColor", 5 /* VariableType.Vec4 */, (prog) => {
        prog.addGraphicUniform("u_baseColor", (uniform, params) => {
            const realityMesh = params.geometry.asRealityMesh;
            const baseColor = (realityMesh.baseColor ? realityMesh.baseColor : core_common_1.ColorDef.create(0xff000000)).colors;
            uniform.setUniform4fv([baseColor.r / 255, baseColor.g / 255, baseColor.b / 255, 1 - baseColor.t / 255]);
        });
    });
    builder.frag.set(1 /* FragmentShaderComponent.ComputeBaseColor */, computeFragmentBaseColor);
    if (!flags.isTranslucent) {
        if (0 /* FeatureMode.None */ !== feat) {
            if (flags.isClassified)
                (0, PlanarClassification_1.addFeaturePlanarClassifier)(builder);
            builder.frag.addFunction(Decode_1.decodeDepthRgb);
            if (flags.isClassified)
                (0, Fragment_1.addPickBufferOutputs)(builder.frag);
            else
                (0, Fragment_1.addAltPickBufferOutputs)(builder.frag);
        }
    }
    addTextures(builder, textureCount);
    if (1 /* IsThematic.Yes */ === flags.isThematic)
        addThematicToRealityMesh(builder, gradientTextureUnit);
    if (flags.isWiremesh)
        (0, Wiremesh_1.addWiremesh)(builder);
    if (flags.enableAtmosphere)
        (0, Atmosphere_1.addAtmosphericScatteringEffect)(builder, false, false);
    return builder;
}
exports.createRealityMeshBuilder = createRealityMeshBuilder;
//# sourceMappingURL=RealityMesh.js.map