/*---------------------------------------------------------------------------------------------
* 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 } from "@itwin/core-bentley";
import { AttributeMap } from "../AttributeMap";
import { ProgramBuilder } from "../ShaderBuilder";
import { TextureUnit } from "../RenderFlags";
import { addAnimation } from "./Animation";
import { addColor } from "./Color";
import { addFrustum, addShaderFlags } from "./Common";
import { addWhiteOnWhiteReversal } from "./Fragment";
import { addAdjustWidth, addLineCode } from "./Polyline";
import { octDecodeNormal } from "./Surface";
import { addLineWeight, addModelViewMatrix, addNormalMatrix, addProjectionMatrix, addSamplePosition } from "./Vertex";
import { addModelToWindowCoordinates, addViewport } from "./Viewport";
import { addLookupTable } from "./LookupTable";
import { addRenderOrder, addRenderOrderConstants } from "./FeatureSymbology";
const computeOtherPos = `
  g_otherPos = samplePosition(g_otherIndex);
`;
const decodeEndPointAndQuadIndices = `
  g_otherIndex = decodeUInt24(a_endPointAndQuadIndices.xyz);
${computeOtherPos}
  g_quadIndex = a_endPointAndQuadIndices.w;
`;
const animateEndPoint = `g_otherPos.xyz += computeAnimationDisplacement(g_otherIndex, u_animDispParams.x, u_animDispParams.y, u_animDispParams.z, u_qAnimDispOrigin, u_qAnimDispScale);`;
// a_pos is a 24-bit index into edge lookup table.
// First six bytes of lookup table entry are the pair of 24-bit indices identifying the endpoints of the edge in the vertex table.
// Return the 24-bit index of "this" vertex in the vertex table encoded in a vec3.
const computeIndexedQuantizedPosition = `
  g_vertexId = gl_VertexID % 6;
  if (g_vertexId == 0)
    g_quadIndex = 0.0;
  else if (g_vertexId == 2 || g_vertexId == 3)
    g_quadIndex = 1.0;
  else if (g_vertexId == 1 || g_vertexId == 4)
    g_quadIndex = 2.0;
  else
    g_quadIndex = 3.0;

  // The following formula computes the texel index, but suffers from precision issues for large edge indices, so we must compute using integers instead.
  // float edgeBaseIndex = u_edgeParams.z * 1.5 + u_edgeParams.w * 0.25 + (edgeIndex - u_edgeParams.z) * 2.5);

  float fEdgeIndex = decodeUInt24(a_pos);
  g_isSilhouette = fEdgeIndex >= u_edgeParams.z;
  int edgeIndex = int(fEdgeIndex);
  bool isEven = 0 == (edgeIndex & 1);
  float edgeBaseIndex;
  if (!g_isSilhouette) {
    edgeBaseIndex = float(edgeIndex + (edgeIndex / 2));
  } else {
    // If both pad and edgeIndex produce a remainder (0.5 for each - pad is a multiple of 2), we must add one to the index to account for it.
    int shift = isEven ? 0 : 1;
    int pad = int(u_edgeParams.w);
    if (0 != (pad % 4)) {
      isEven = !isEven;
      shift = shift + 1;
    }

    // s = num segments p = num padding bytes i = edge index
    // texel index = 1.5s + .25p + 2.5(i - s) = 1.5s + .25p + 2.5i - 2.5s = 2.5i + .25p - s = i + i + i/2 + p/4 - s
    edgeBaseIndex = float(edgeIndex + edgeIndex + edgeIndex / 2 + pad / 4 - int(u_edgeParams.z) + shift / 2);
  }

  vec2 tc = compute_edge_coords(floor(edgeBaseIndex));
  vec4 s0 = floor(TEXTURE(u_edgeLUT, tc) * 255.0 + 0.5);
  tc.x += g_edge_stepX;
  vec4 s1 = floor(TEXTURE(u_edgeLUT, tc) * 255.0 + 0.5);
  tc.x += g_edge_stepX;
  vec4 s2 = floor(TEXTURE(u_edgeLUT, tc) * 255.0 + 0.5);

  vec3 i0 = isEven ? s0.xyz : vec3(s0.zw, s1.x);
  vec3 i1 = isEven ? vec3(s0.w, s1.xy) : s1.yzw;
  g_otherIndexIndex = g_quadIndex < 2.0 ? i1 : i0;

  g_normals = isEven ? vec4(s1.zw, s2.xy) : s2;

  return g_quadIndex < 2.0 ? i0 : i1;
`;
const initializeIndexed = `
  g_otherIndex = decodeUInt24(g_otherIndexIndex);
${computeOtherPos}
`;
// IndexedEdgeGeometry.renderOrder returns Edge or PlanarEdge. Adjust if silhouette for output to pick buffers.
const computeIndexedRenderOrder = `
  if (g_isSilhouette)
    v_renderOrder = kRenderOrder_Edge == u_renderOrder ? kRenderOrder_Silhouette : kRenderOrder_PlanarSilhouette;
  else
    v_renderOrder = u_renderOrder;
`;
const checkForSilhouetteDiscard = `
  if (kFrustumType_Perspective != u_frustum.z) {
    float perpTol = 4.75e-6;
    return (n0.z * n1.z > perpTol);      // orthographic.
  } else {
    float perpTol = 2.5e-4;
    vec4  viewPos = MAT_MV * rawPos;     // perspective
    vec3  toEye = normalize(viewPos.xyz);
    float dot0 = dot(n0, toEye);
    float dot1 = dot(n1, toEye);

    if (dot0 * dot1 > perpTol)
      return true;

    // Need to discard if either is non-silhouette.
    vec4 otherPosition = g_otherPos;
    viewPos = MAT_MV * otherPosition;
    toEye = normalize(viewPos.xyz);
    dot0 = dot(n0, toEye);
    dot1 = dot(n1, toEye);

    return dot0 * dot1 > perpTol;
  }
`;
const checkForSilhouetteDiscardNonIndexed = `
  vec3 n0 = MAT_NORM * octDecodeNormal(a_normals.xy);
  vec3 n1 = MAT_NORM * octDecodeNormal(a_normals.zw);
${checkForSilhouetteDiscard}
`;
const checkForSilhouetteDiscardIndexed = `
  if (!g_isSilhouette)
    return false;

  vec3 n0 = MAT_NORM * octDecodeNormal(g_normals.xy);
  vec3 n1 = MAT_NORM * octDecodeNormal(g_normals.zw);
${checkForSilhouetteDiscard}
`;
const computePosition = `
  v_lnInfo = vec4(0.0, 0.0, 0.0, 0.0);  // init and set flag to false
  vec4  other = g_otherPos;
  float miterAdjust = 0.0;
  float weight = computeLineWeight();

  vec4 pos;
  g_windowPos = modelToWindowCoordinates(rawPos, other, pos, v_eyeSpace);
  if (g_windowPos.w == 0.0) // Clipped out.
    return g_windowPos;

  vec4 otherPos;
  vec3 otherMvPos;
  vec4 projOther = modelToWindowCoordinates(other, rawPos, otherPos, otherMvPos);

  g_windowDir = projOther.xy - g_windowPos.xy;

  adjustWidth(weight, g_windowDir, g_windowPos.xy);
  g_windowDir = normalize(g_windowDir);

  vec2  perp = vec2(-g_windowDir.y, g_windowDir.x);
  float perpDist = weight / 2.0;
  float alongDist = 0.0;

  perpDist *= sign(0.5 - float(g_quadIndex == 0.0 || g_quadIndex == 3.0)); // negate for index 0 and 3
  alongDist += distance(rawPos, other) * float(g_quadIndex >= 2.0); // index 2 and 3 correspond to 'far' endpoint of segment

  pos.x += perp.x * perpDist * 2.0 * pos.w / u_viewport.x;
  pos.y += perp.y * perpDist * 2.0 * pos.w / u_viewport.y;

  lineCodeEyePos = .5 * (rawPos + other);
  lineCodeDist = alongDist;

  return pos;
`;
const lineCodeArgs = "g_windowDir, g_windowPos, 0.0";
const adjustContrast = `
  float bgi = u_bgIntensity;
  if (bgi < 0.0)
    return baseColor;

  float s;
  float rgbi = baseColor.r * 0.3 + baseColor.g * 0.59 + baseColor.b * 0.11;
  if (rgbi > 0.81)
    s = bgi > 0.57 ? 0.0 : 0.699;
  else if (rgbi > 0.57)
    s = bgi > 0.57 ? 0.0 : 1.0;
  else
    s = bgi < 0.81 ? 1.0 : 0.699;

  return vec4(vec3(s), baseColor.a);
`;
/** @internal */
export function addEdgeContrast(vert) {
    vert.addUniform("u_bgIntensity", 2 /* VariableType.Float */, (prog) => {
        prog.addGraphicUniform("u_bgIntensity", (uniform, params) => {
            let bgi = -1;
            if (params.geometry.isEdge && params.target.currentEdgeSettings.wantContrastingColor(params.target.currentViewFlags.renderMode))
                bgi = params.target.uniforms.style.backgroundIntensity;
            uniform.setUniform1f(bgi);
        });
    });
    vert.set(8 /* VertexShaderComponent.AdjustContrast */, adjustContrast);
}
const edgeLutParams = new Float32Array(4);
function createBase(type, instanced, isAnimated, positionType) {
    const isInstanced = 1 /* IsInstanced.Yes */ === instanced;
    const isSilhouette = "Silhouette" === type;
    const isIndexed = "IndexedEdge" === type;
    const techId = isSilhouette ? 5 /* TechniqueId.SilhouetteEdge */ : (isIndexed ? 6 /* TechniqueId.IndexedEdge */ : 4 /* TechniqueId.Edge */);
    const attrMap = AttributeMap.findAttributeMap(techId, isInstanced);
    const builder = new ProgramBuilder(attrMap, { positionType, instanced: isInstanced });
    const vert = builder.vert;
    vert.addGlobal("g_otherPos", 5 /* VariableType.Vec4 */);
    vert.addGlobal("g_quadIndex", 2 /* VariableType.Float */);
    vert.addGlobal("g_windowPos", 5 /* VariableType.Vec4 */);
    vert.addGlobal("g_windowDir", 3 /* VariableType.Vec2 */);
    vert.addGlobal("g_otherIndex", 2 /* VariableType.Float */);
    addSamplePosition(vert);
    if (isIndexed) {
        vert.addGlobal("g_vertexId", 1 /* VariableType.Int */);
        vert.addGlobal("g_otherIndexIndex", 4 /* VariableType.Vec3 */);
        vert.addGlobal("g_isSilhouette", 0 /* VariableType.Boolean */, "false");
        vert.addGlobal("g_normals", 5 /* VariableType.Vec4 */);
        const initLut = addLookupTable(vert, "edge", "1.0");
        vert.addUniform("u_edgeLUT", 8 /* VariableType.Sampler2D */, (prog) => {
            prog.addGraphicUniform("u_edgeLUT", (uniform, params) => {
                const edge = params.geometry.asIndexedEdge;
                assert(undefined !== edge);
                edge.edgeLut.texture.bindSampler(uniform, TextureUnit.EdgeLUT);
            });
        });
        vert.addUniform("u_edgeParams", 5 /* VariableType.Vec4 */, (prog) => {
            prog.addGraphicUniform("u_edgeParams", (uniform, params) => {
                const edge = params.geometry.asIndexedEdge;
                assert(undefined !== edge);
                edgeLutParams[0] = edge.edgeLut.texture.width;
                edgeLutParams[1] = edge.edgeLut.texture.height;
                edgeLutParams[2] = edge.edgeLut.numSegments;
                edgeLutParams[3] = edge.edgeLut.silhouettePadding;
                uniform.setUniform4fv(edgeLutParams);
            });
        });
        vert.set(0 /* VertexShaderComponent.ComputeQuantizedPosition */, `${initLut}\n\n${computeIndexedQuantizedPosition}`);
        vert.addInitializer(initializeIndexed);
        addRenderOrder(vert);
        addRenderOrderConstants(vert);
        builder.addInlineComputedVarying("v_renderOrder", 2 /* VariableType.Float */, computeIndexedRenderOrder);
        builder.frag.set(21 /* FragmentShaderComponent.OverrideRenderOrder */, "return v_renderOrder;");
    }
    else {
        vert.addInitializer(decodeEndPointAndQuadIndices);
    }
    if (isAnimated) {
        addAnimation(vert, false);
        vert.addInitializer(animateEndPoint);
    }
    vert.addGlobal("lineCodeEyePos", 5 /* VariableType.Vec4 */);
    vert.addGlobal("lineCodeDist", 2 /* VariableType.Float */, "0.0");
    addModelToWindowCoordinates(vert); // adds u_mvp, u_viewportTransformation, and sets g_eyeSpace
    addProjectionMatrix(vert);
    addLineCode(builder, lineCodeArgs);
    builder.addVarying("v_eyeSpace", 4 /* VariableType.Vec3 */);
    vert.set(10 /* VertexShaderComponent.ComputePosition */, computePosition);
    builder.addVarying("v_lnInfo", 5 /* VariableType.Vec4 */);
    addAdjustWidth(vert);
    addViewport(vert);
    addModelViewMatrix(vert);
    addLineWeight(vert);
    if (isSilhouette || isIndexed) {
        addNormalMatrix(vert);
        addFrustum(builder);
        vert.addFunction(octDecodeNormal);
        vert.set(2 /* VertexShaderComponent.CheckForEarlyDiscard */, isSilhouette ? checkForSilhouetteDiscardNonIndexed : checkForSilhouetteDiscardIndexed);
    }
    return builder;
}
/** @internal */
export function createEdgeBuilder(type, instanced, isAnimated, posType) {
    const builder = createBase(type, instanced, isAnimated, posType);
    addShaderFlags(builder);
    addColor(builder);
    addEdgeContrast(builder.vert);
    addWhiteOnWhiteReversal(builder.frag);
    return builder;
}
//# sourceMappingURL=Edge.js.map