"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.adjustWidth = void 0;
exports.addAdjustWidth = addAdjustWidth;
exports.addLineCodeTexture = addLineCodeTexture;
exports.addLineCode = addLineCode;
exports.createPolylineBuilder = createPolylineBuilder;
exports.createPolylineHiliter = createPolylineHiliter;
const core_bentley_1 = require("@itwin/core-bentley");
const AttributeMap_1 = require("../AttributeMap");
const RenderFlags_1 = require("../RenderFlags");
const ShaderBuilder_1 = require("../ShaderBuilder");
const System_1 = require("../System");
const LineCode_1 = require("../LineCode");
const Color_1 = require("./Color");
const Edge_1 = require("./Edge");
const Common_1 = require("./Common");
const Decode_1 = require("./Decode");
const FeatureSymbology_1 = require("./FeatureSymbology");
const Fragment_1 = require("./Fragment");
const Vertex_1 = require("./Vertex");
const Viewport_1 = require("./Viewport");
const checkForDiscard = "return discardByLineCode;";
const applyLineCode = `
  if (v_texc.x >= 0.0) { // v_texc = (-1,-1) for solid lines - don't bother with any of this
    vec4 texColor = TEXTURE(u_lineCodeTexture, v_texc);
    discardByLineCode = (0.0 == texColor.r);
  }

  if (v_lnInfo.w > 0.5) { // line needs pixel trimming
    // calculate pixel distance from pixel center to expected line center, opposite dir from major
    vec2 dxy = gl_FragCoord.xy - v_lnInfo.xy;
    if (v_lnInfo.w < 1.5)  // not x-major
      dxy = dxy.yx;

    float dist = v_lnInfo.z * dxy.x - dxy.y;
    float distA = abs(dist);
    if (distA > 0.5 || (distA == 0.5 && dist < 0.0))
      discardByLineCode = true;  // borrow this flag to force discard
  }

  return baseColor;
`;
const computeTextureCoord = `
vec2 computeLineCodeTextureCoords(vec2 windowDir, vec4 projPos, float adjust, float patternDist) {
  vec2 texc;
  float lineCode = computeLineCode();
  if (0.0 == lineCode) {
    // Solid line - tell frag shader not to bother.
    texc = vec2(-1.0, -1.0);
  } else {
    const float imagesPerPixel = 1.0/32.0;
    const float textureCoordinateBase = 8192.0; // Temp workardound for clipping problem in perspective views (negative values don't seem to interpolate correctly).

    float patternDistPixels;
    if (u_useCumDist > 0.5) {
      patternDistPixels = patternDist * u_pixelsPerWorld;
    } else {
      if (abs(windowDir.x) > abs(windowDir.y))
        patternDistPixels = projPos.x + adjust * windowDir.x;
      else
        patternDistPixels = projPos.y + adjust * windowDir.y;
    }
    texc.x = textureCoordinateBase + imagesPerPixel * patternDistPixels;

    // Use uniform to support dynamic capacity based on System.maxTextureSize
    float numRows = u_numLineCodes;
    float centerY = 0.5 / numRows;
    float stepY = 1.0 / numRows;
    texc.y = stepY * lineCode + centerY;
  }

  return texc;
}
`;
function addPixelsPerWorldUniform(prog) {
    const addUniform = (shader) => {
        shader.addUniform("u_pixelsPerWorld", 2 /* VariableType.Float */, (prg) => {
            prg.addProgramUniform("u_pixelsPerWorld", (uniform, params) => {
                // Calculate pixels per world unit from viewport
                // For orthographic views this is constant and provides stable pattern scaling
                const vp = params.target.viewRect;
                // Default to 1.0 if we can't calculate (will use world units directly)
                let pixelsPerWorld = 1.0;
                // Get frustum planes: { top, bottom, left, right }
                const planes = params.target.uniforms.frustum.planes;
                const worldWidth = planes[3] - planes[2]; // right - left
                if (worldWidth > 0) {
                    // worldWidth is the view width in world units
                    pixelsPerWorld = vp.width / worldWidth;
                }
                uniform.setUniform1f(pixelsPerWorld);
            });
        });
    };
    addUniform(prog.vert);
    addUniform(prog.frag);
}
function addUseCumulativeDistanceUniform(prog) {
    prog.vert.addUniform("u_useCumDist", 2 /* VariableType.Float */, (prg) => {
        prg.addGraphicUniform("u_useCumDist", (uniform, params) => {
            uniform.setUniform1f(params.geometry.hasCumulativeDistances ? 1.0 : 0.0);
        });
    });
}
function addNumLineCodesUniform(prog) {
    prog.vert.addUniform("u_numLineCodes", 2 /* VariableType.Float */, (prg) => {
        prg.addProgramUniform("u_numLineCodes", (uniform, _params) => {
            // Pass the current LineCode capacity to the shader
            uniform.setUniform1f(LineCode_1.LineCode.capacity());
        });
    });
}
/** @internal */
exports.adjustWidth = `
void adjustWidth(inout float width, vec2 d2, vec2 org) {
  if (u_aaSamples > 1) {
    if (width < 5.0)
      width += (5.0 - width) * 0.125;
    return;
  }

  // calculate slope based width adjustment for non-AA lines, widths 1 to 4
  vec2 d2A = abs(d2);
  const float s_myFltEpsilon = 0.0001;  // limit test resolution to 4 digits in case 24 bit (s16e7) is used in hardware
  if (d2A.y > s_myFltEpsilon && width < 4.5) {
    float len = length(d2A);
    float tan = d2A.x / d2A.y;

    if (width < 1.5) { // width 1
      if (tan <= 1.0)
        width = d2A.y;
      else
        width = d2A.x;
      // width 1 requires additional adjustment plus trimming in frag shader using v_lnInfo
      width *= 1.01;
      v_lnInfo.xy = org;
      v_lnInfo.w = 1.0; // set flag to do trimming
      // set slope in v_lnInfo.z
      if (d2A.x - d2A.y > s_myFltEpsilon) {
        v_lnInfo.z = d2.y / d2.x;
        v_lnInfo.w += 2.0; // add in x-major flag
      } else
        v_lnInfo.z = d2.x / d2.y;

    } else if (width < 2.5) { // width 2
      if (tan <= 0.5)
        width = 2.0 * d2A.y;
      else
        width = (d2A.y + 2.0 * d2A.x);

    } else if (width < 3.5) { // width 3
        if (tan <= 1.0)
            width = (3.0 * d2A.y + d2A.x);
        else
            width = (d2A.y + 3.0 * d2A.x);

    } else { // if (width < 4.5) // width 4
      if (tan <= 0.5)
        width = (4.0 * d2A.y + d2A.x);
      else if (tan <= 2.0)
        width = (3.0 * d2A.y + 3.0 * d2A.x);
      else
        width = (d2A.y + 4.0 * d2A.x);
    }
    width /= len;
  }
}
`;
/** @internal */
function addAdjustWidth(vert) {
    vert.addUniform("u_aaSamples", 1 /* VariableType.Int */, (prog) => {
        prog.addGraphicUniform("u_aaSamples", (attr, params) => {
            const numSamples = System_1.System.instance.frameBufferStack.currentFbMultisampled ? params.target.compositor.antialiasSamples : 1;
            attr.setUniform1i(numSamples);
        });
    });
    vert.addFunction(exports.adjustWidth);
}
/** @internal */
function addLineCodeTexture(frag) {
    frag.addUniform("u_lineCodeTexture", 8 /* VariableType.Sampler2D */, (prog) => {
        prog.addProgramUniform("u_lineCodeTexture", (uniform) => {
            const lct = System_1.System.instance.lineCodeTexture;
            (0, core_bentley_1.assert)(undefined !== lct);
            if (undefined !== lct)
                lct.bindSampler(uniform, RenderFlags_1.TextureUnit.LineCode);
        });
    });
}
/** @internal */
function addLineCode(prog, args) {
    const vert = prog.vert;
    const frag = prog.frag;
    (0, Vertex_1.addLineCode)(vert);
    addPixelsPerWorldUniform(prog);
    addUseCumulativeDistanceUniform(prog);
    addNumLineCodesUniform(prog); // Add uniform for dynamic capacity
    const funcCall = `computeLineCodeTextureCoords(${args})`;
    prog.addFunctionComputedVaryingWithArgs("v_texc", 3 /* VariableType.Vec2 */, funcCall, computeTextureCoord);
    (0, Common_1.addFrustum)(prog);
    addLineCodeTexture(prog.frag);
    frag.set(3 /* FragmentShaderComponent.FinalizeBaseColor */, applyLineCode);
    frag.set(4 /* FragmentShaderComponent.CheckForDiscard */, checkForDiscard);
    frag.addGlobal("discardByLineCode", 0 /* VariableType.Boolean */, "false");
}
function polylineAddLineCode(prog) {
    addLineCode(prog, lineCodeArgs);
    (0, Vertex_1.addModelViewMatrix)(prog.vert);
}
function addCommon(prog) {
    const vert = prog.vert;
    (0, Viewport_1.addModelToWindowCoordinates)(vert); // adds u_mvp, u_viewportTransformation
    (0, Vertex_1.addProjectionMatrix)(vert);
    (0, Vertex_1.addModelViewMatrix)(vert);
    (0, Viewport_1.addViewport)(vert);
    vert.addGlobal("g_windowPos", 5 /* VariableType.Vec4 */);
    vert.addGlobal("g_prevPos", 5 /* VariableType.Vec4 */);
    vert.addGlobal("g_nextPos", 5 /* VariableType.Vec4 */);
    vert.addGlobal("g_windowDir", 3 /* VariableType.Vec2 */);
    vert.addInitializer(decodeAdjacentPositions);
    vert.addFunction(Decode_1.unquantize2d);
    (0, Vertex_1.addLineWeight)(vert);
    vert.addGlobal("miterAdjust", 2 /* VariableType.Float */, "0.0");
    prog.addVarying("v_patternDistance", 2 /* VariableType.Float */);
    prog.addVarying("v_eyeSpace", 4 /* VariableType.Vec3 */);
    vert.set(10 /* VertexShaderComponent.ComputePosition */, buildComputePosition(vert.positionType));
    prog.addVarying("v_lnInfo", 5 /* VariableType.Vec4 */);
    addAdjustWidth(vert);
    (0, Vertex_1.addSamplePosition)(vert);
    vert.addFunction(decodePosition);
    vert.addFunction(decodeFloatFromBytes);
}
const decodePosition = `
vec4 decodePosition(vec3 baseIndex) {
  float index = decodeUInt24(baseIndex);
  return samplePosition(index);
}
`;
const decodeAdjacentPositions = `
  g_prevPos = decodePosition(a_prevIndex);
  g_nextPos = decodePosition(a_nextIndex);
`;
const decodeFloatFromBytes = `
float decodeFloatFromBytes(vec4 bytes) {
  uvec4 b = uvec4(bytes);
  uint u = b.x | (b.y << 8) | (b.z << 16) | (b.w << 24);
  return uintBitsToFloat(u);
}
`;
function buildComputePosition(positionType) {
    const cumDistExpr = positionType === "unquantized"
        ? "((u_vertParams.z > 5.0) ? decodeFloatFromBytes(g_vertLutData5) : 0.0)"
        : "((u_vertParams.z > 3.0) ? decodeFloatFromBytes(g_vertLutData3) : 0.0)";
    return `
  const float kNone = 0.0,
              kSquare = 1.0*3.0,
              kMiter = 2.0*3.0,
              kMiterInsideOnly = 3.0*3.0,
              kJointBase = 4.0*3.0,
              kNegatePerp = 8.0*3.0,
              kNegateAlong = 16.0*3.0,
              kNoneAdjWt = 32.0*3.0;

  v_lnInfo = vec4(0.0, 0.0, 0.0, 0.0);  // init and set flag to false

  vec4 next = g_nextPos;
  vec4 pos;
  g_windowPos = modelToWindowCoordinates(rawPos, next, pos, v_eyeSpace);
  if (g_windowPos.w == 0.0)
    return g_windowPos;

  float param = a_param;
  float weight = computeLineWeight();
  float scale = 1.0, directionScale = 1.0;

  if (param >= kNoneAdjWt)
    param -= kNoneAdjWt;

  bool isSegmentStart = true;
  if (param >= kNegateAlong) {
    directionScale = -directionScale;
    param -= kNegateAlong;
    isSegmentStart = false;
  }

  if (param >= kNegatePerp) {
    scale = -1.0;
    param -= kNegatePerp;
  }

  vec4 otherPos;
  vec3 otherMvPos;
  vec4 projNext = modelToWindowCoordinates(next, rawPos, otherPos, otherMvPos);
  g_windowDir = projNext.xy - g_windowPos.xy;

  if (u_useCumDist > 0.5)
    v_patternDistance = ${cumDistExpr};
  else
    v_patternDistance = 0.0;

  if (param < kJointBase) {
    vec2 dir = (directionScale > 0.0) ? g_windowDir : -g_windowDir;
    vec2 pos = (directionScale > 0.0) ? g_windowPos.xy : projNext.xy;
    adjustWidth(weight, dir, pos);
  }

  if (kNone != param) {
    vec2 delta = vec2(0.0);
    vec4 prev   = g_prevPos;
    vec4 projPrev = modelToWindowCoordinates(prev, rawPos, otherPos, otherMvPos);
    vec2 prevDir   = g_windowPos.xy - projPrev.xy;
    float thisLength = sqrt(g_windowDir.x * g_windowDir.x + g_windowDir.y * g_windowDir.y);
    const float s_minNormalizeLength = 1.0E-5;  // avoid normalizing zero length vectors.
    float dist = weight / 2.0;

    if (thisLength > s_minNormalizeLength) {
      g_windowDir /= thisLength;

      float prevLength = sqrt(prevDir.x * prevDir.x + prevDir.y * prevDir.y);

      if (prevLength > s_minNormalizeLength) {
        prevDir /= prevLength;
        const float     s_minParallelDot= -.9999, s_maxParallelDot = .9999;
        float           prevNextDot  = dot(prevDir, g_windowDir);

        if (prevNextDot < s_minParallelDot || prevNextDot > s_maxParallelDot)    // No miter if parallel or antiparallel.
          param = kSquare;
      } else
        param = kSquare;
    } else {
      g_windowDir = -normalize(prevDir);
      param = kSquare;
    }

    vec2 perp = scale * vec2(-g_windowDir.y, g_windowDir.x);

    if (param == kSquare) {
      delta = perp;
    } else {
      vec2 bisector = normalize(prevDir - g_windowDir);
      float dotP = dot (bisector, perp);

      if (dotP != 0.0) { // Should never occur - but avoid divide by zero.
        const float maxMiter = 3.0;
        float miterDistance = 1.0/dotP;

        if (param == kMiter) { // Straight miter.
          delta = (abs(miterDistance) > maxMiter) ? perp : bisector * miterDistance;

        } else if (param == kMiterInsideOnly) { // Miter at inside, square at outside (to make room for joint).
          delta = (dotP  > 0.0 || abs(miterDistance) > maxMiter) ? perp : bisector * miterDistance;

        } else {
          const float jointTriangleCount = 3.0;
          float ratio = (param - kJointBase) / jointTriangleCount; // 3 triangles per half-joint as defined in Graphics.cpp
          delta = normalize((1.0 - ratio) * bisector + (dotP < 0.0 ? -ratio : ratio) * perp); // Miter/Straight combination.
        }
      }
    }

    miterAdjust = dot(g_windowDir, delta) * dist; // Not actually used for hilite shader but meh.
    pos.x += dist * delta.x * 2.0 * pos.w / u_viewport.x;
    pos.y += dist * delta.y * 2.0 * pos.w / u_viewport.y;
  }

  return pos;
`;
}
const lineCodeArgs = "g_windowDir, g_windowPos, miterAdjust, v_patternDistance";
/** @internal */
function createPolylineBuilder(isInstanced, positionType) {
    const instanced = 1 /* IsInstanced.Yes */ === isInstanced;
    const attrMap = AttributeMap_1.AttributeMap.findAttributeMap(1 /* TechniqueId.Polyline */, instanced);
    const builder = new ShaderBuilder_1.ProgramBuilder(attrMap, { positionType, instanced });
    (0, Common_1.addShaderFlags)(builder);
    addCommon(builder);
    polylineAddLineCode(builder);
    (0, Color_1.addColor)(builder);
    (0, Edge_1.addEdgeContrast)(builder.vert);
    (0, Fragment_1.addWhiteOnWhiteReversal)(builder.frag);
    return builder;
}
/** @internal */
function createPolylineHiliter(isInstanced, positionType) {
    const instanced = 1 /* IsInstanced.Yes */ === isInstanced;
    const attrMap = AttributeMap_1.AttributeMap.findAttributeMap(1 /* TechniqueId.Polyline */, instanced);
    const builder = new ShaderBuilder_1.ProgramBuilder(attrMap, { positionType, instanced });
    addCommon(builder);
    addUseCumulativeDistanceUniform(builder);
    (0, Common_1.addFrustum)(builder);
    (0, FeatureSymbology_1.addHiliter)(builder, true);
    return builder;
}
//# sourceMappingURL=Polyline.js.map