/* COPYRIGHT Esri - https://js.arcgis.com/5.1.0/LICENSE.txt */
import{ScreenSpacePass as e}from"../views/3d/webgl-engine/core/shaderLibrary/ScreenSpacePass.glsl.js";import{ReadDepthLastFrame as a}from"../views/3d/webgl-engine/core/shaderLibrary/output/ReadDepthLastFrame.glsl.js";import{Gamma as t}from"../views/3d/webgl-engine/core/shaderLibrary/shading/Gamma.glsl.js";import{addMainLightDirection as r}from"../views/3d/webgl-engine/core/shaderLibrary/shading/MainLighting.glsl.js";import{ScreenSpaceRayMarching as o}from"../views/3d/webgl-engine/core/shaderLibrary/shading/ScreenSpaceRayMarching.glsl.js";import{CameraSpace as i}from"../views/3d/webgl-engine/core/shaderLibrary/util/CameraSpace.glsl.js";import{FloatPassUniform as l}from"../views/3d/webgl-engine/core/shaderModules/FloatPassUniform.js";import{glsl as n,If as s}from"../views/3d/webgl-engine/core/shaderModules/glsl.js";import{Matrix4BindUniform as c}from"../views/3d/webgl-engine/core/shaderModules/Matrix4BindUniform.js";import{Texture2DBindUniform as m}from"../views/3d/webgl-engine/core/shaderModules/Texture2DBindUniform.js";import{Texture2DPassUniform as h}from"../views/3d/webgl-engine/core/shaderModules/Texture2DPassUniform.js";import{GlobalIlluminationColorQuantization as u}from"../views/3d/webgl-engine/effects/globalIllumination/GlobalIlluminationColorQuantization.glsl.js";import{ColorAttachment1 as d}from"../views/webgl/enums.js";import{NoParameters as p}from"../views/webgl/NoParameters.js";import{ShaderBuilder as g}from"../views/webgl/ShaderBuilder.js";const f=.01,x=.008,v=.002,M=.5,y=.02,b=.1,w=.008,W=.012,D=.008,S=40,R=.095,F=.008,j=60,I=2,B=.0039,C=.25,z=.15,G=25,P=.15,T=.5,E=1,$=1,L=16;class O extends p{constructor(){super(...arguments),this.projScale=1,this.scaleGlobalIllumination=1,this.accumulatedFrames=0,this.temporalSampleFrame=0,this.rayMarchMinReach=P,this.rayMarchMaxReach=T,this.rayMarchWorldReach=G,this.rayMarchMinReachEmissionWeight=E,this.rayMarchMaxReachEmissionWeight=$,this.rayMarchMaxSteps=L,this.colorBleedWeight=z}}function V(p){const z=new g,G=z.fragment;return z.include(e),z.include(i),r(G),G.include(a),G.include(t),G.include(u),z.include(o,p),G.uniforms.add(new h("normalMap",e=>e.normalTexture),new h("depthMap",e=>e.depthTexture),new m("lastFrameColorTexture",e=>e.reprojection.lastFrameColor?.getTexture()),new m("lastFrameDepthTexture",e=>e.reprojection.lastFrameDepth?.attachment),new m("lastFrameGlobalIlluminationTexture",e=>e.globalIllumination?.getTexture()),new m("lastFrameGlobalIlluminationWeightTexture",e=>e.globalIllumination?.getTexture(d)),new c("reprojectionViewMatrix",e=>e.reprojection.viewMatrix),new c("view",e=>e.camera.viewMatrix),new l("accumulatedFrames",e=>e.accumulatedFrames),new l("temporalSampleFrame",e=>e.temporalSampleFrame),new l("scaleGlobalIllumination",e=>e.scaleGlobalIllumination)),G.uniforms.add(new l("rayMarchMinReach",e=>e.rayMarchMinReach),new l("rayMarchMaxReach",e=>e.rayMarchMaxReach),new l("rayMarchWorldReach",e=>e.rayMarchWorldReach),new l("rayMarchMinReachEmissionWeight",e=>e.rayMarchMinReachEmissionWeight),new l("rayMarchMaxReachEmissionWeight",e=>e.rayMarchMaxReachEmissionWeight),new l("rayMarchMaxSteps",e=>e.rayMarchMaxSteps),new l("colorBleedWeight",e=>e.colorBleedWeight)),p.hasEmission&&G.uniforms.add(new m("lastFrameEmissionTexture",e=>e.reprojection.lastFrameEmission?.attachment)),G.code.add(n`
    float computeIdleColorBlendWeight(float accumulatedFrames) {
      float idleColorBlendProgress = clamp(
        accumulatedFrames / ${n.float(S)},
        0.0,
        1.0
      );
      return mix(
        ${n.float(W)},
        ${n.float(D)},
        idleColorBlendProgress
      );
    }

    float computeIdleOcclusionBlendWeight(float accumulatedFrames) {
      float idleOcclusionBlendProgress = clamp(
        accumulatedFrames / ${n.float(j)},
        0.0,
        1.0
      );
      return mix(
        ${n.float(R)},
        ${n.float(F)},
        pow(idleOcclusionBlendProgress, ${n.float(I)})
      );
    }

    bool isEdgeDepth(float centerDepth, vec2 sampleUv) {
      vec2 texelSize = 1.0 / vec2(textureSize(depthMap, 0));
      float depthLeft = linearizeDepth(depthFromTexture(depthMap, sampleUv + vec2(-texelSize.x, 0.0)));
      float depthRight = linearizeDepth(depthFromTexture(depthMap, sampleUv + vec2(texelSize.x, 0.0)));
      float depthUp = linearizeDepth(depthFromTexture(depthMap, sampleUv + vec2(0.0, texelSize.y)));
      float depthDown = linearizeDepth(depthFromTexture(depthMap, sampleUv + vec2(0.0, -texelSize.y)));

      float maxDifference = max(max(abs(centerDepth - depthLeft), abs(centerDepth - depthRight)), max(abs(centerDepth - depthUp), abs(centerDepth - depthDown)));

      return abs(maxDifference / centerDepth) > 0.01;
    }

    vec3 sampleCosineHemisphere(vec2 u) {
      float phi = 6.28318530718 * u.x;
      float radius = sqrt(u.y);
      float x = radius * cos(phi);
      float y = radius * sin(phi);
      float z = sqrt(max(0.0, 1.0 - u.y));

      return vec3(x, y, z);
    }

    mat3 basisFromNormal(vec3 n) {
      vec3 up = abs(n.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
      vec3 tangent = normalize(cross(up, n));
      vec3 bitangent = cross(n, tangent);

      return mat3(tangent, bitangent, n);
    }

    float blueNoiseDitherValue(vec2 pixel, float frame, vec2 axis, float phase) {
      float scroll = 5.588238 * mod(frame, 512.0);
      vec2 p = pixel + vec2(scroll);
      vec2 rotated = vec2(
        axis.x * p.x + axis.y * p.y,
        -axis.y * p.x + axis.x * p.y
      );

      return fract(52.9829189 * fract(0.06711056 * rotated.x + 0.00583715 * rotated.y + phase));
    }

    vec4 blueNoiseDither(vec2 pixel, float frame) {
      vec4 value = vec4(
        blueNoiseDitherValue(pixel, frame, vec2(0.9659258, 0.25881904), 0.0),
        blueNoiseDitherValue(pixel, frame, vec2(0.70710677, 0.70710677), 0.17),
        blueNoiseDitherValue(pixel, frame, vec2(0.25881904, 0.9659258), 0.37),
        blueNoiseDitherValue(pixel, frame, vec2(1.0, 0.0), 0.61)
      );

      return value * 2.0 - 1.0;
    }
  `),z.outputs.add("fragGlobalIllumination","vec4",0),z.outputs.add("fragWeight","float",1),G.main.add(n`
    float depth = depthFromTexture(depthMap, uv);

    // Early out if depth is out of range, such as in the sky
    if (depth >= 1.0 || depth <= 0.0) {
      fragGlobalIllumination = vec4(0.0, 0.0, 0.0, 1.0);
      fragWeight = 0.0;
      return;
    }

    // Get the normal of current fragment
    ivec2 iuv = ivec2(uv * vec2(textureSize(normalMap, 0)));
    vec4 normal4 = texelFetch(normalMap, iuv, 0);
    if (normal4.a != 1.0) {
      fragGlobalIllumination = vec4(0.0, 0.0, 0.0, 1.0);
      fragWeight = 0.0;
      return;
    }
    vec3 normal = normalize(normal4.xyz * 2.0 - 1.0);

    // Reconstruct view space position of current fragment
    float currentPixelDepth = linearizeDepth(depth);
    vec3 currentPixelPos = reconstructPosition(uv * vec2(textureSize(normalMap, 0)), currentPixelDepth);
    vec4 viewPos = vec4(currentPixelPos, 1.0);

    // Reproject current view position to last frame
    vec4 reprojectedViewPos = reprojectionViewMatrix * viewPos;
    vec4 reprojectedCoordinate = applyProjectionMat(proj, reprojectedViewPos.xyz);

    // Read last frame reprojected depth and GI history
    float lastFrameDepthViewPos = -linearDepthFromTextureLastFrame(lastFrameDepthTexture, reprojectedCoordinate.xy);
    vec4 lastFrameGlobalIllumination = texture(lastFrameGlobalIlluminationTexture, reprojectedCoordinate.xy);
    float historyOcclusionBlendWeight = texture(lastFrameGlobalIlluminationWeightTexture, reprojectedCoordinate.xy).r;

    int steps;
    float occlusionBlendWeight = 1.0;
    float colorBlendWeight = 1.0;
    float idleColorBlendWeight = computeIdleColorBlendWeight(accumulatedFrames);
    float idleOcclusionBlendWeight = computeIdleOcclusionBlendWeight(accumulatedFrames);
    float reprojectionDepthMismatch = abs((lastFrameDepthViewPos + reprojectedViewPos.z) / max(lastFrameDepthViewPos, reprojectedViewPos.z));
    bool hasReprojectionMismatch = reprojectionDepthMismatch > ${n.float(f)};
    bool isScaledGlobalIllumination = scaleGlobalIllumination < 1.0;
    bool isLowQualityEdgePixel = isScaledGlobalIllumination && isEdgeDepth(currentPixelDepth, uv);
    bool resetColorHistory = false;

    // Heuristic to determine blending weights and number of steps for occlusion and color
    if (hasReprojectionMismatch) {
      if (isLowQualityEdgePixel) {
        steps = 1;
        occlusionBlendWeight = ${n.float(x)};
        resetColorHistory = true;
      } else {
        steps = 6;
        occlusionBlendWeight = 1.0;
        resetColorHistory = true;
      }
    } else {
      steps = 1;
      if (historyOcclusionBlendWeight > ${n.float(M)}) {
        occlusionBlendWeight = ${n.float(b)};
        colorBlendWeight = ${n.float(w)};
      } else if (historyOcclusionBlendWeight > ${n.float(y)}) {
        occlusionBlendWeight = historyOcclusionBlendWeight - 0.05;
        colorBlendWeight = ${n.float(w)};
      } else {
        occlusionBlendWeight = isScaledGlobalIllumination ? ${n.float(x)} : idleOcclusionBlendWeight;
        colorBlendWeight = isScaledGlobalIllumination ? ${n.float(v)} : idleColorBlendWeight;
      }
    }

    vec4 randomDirectionSample;
    mat3 normalBasis = basisFromNormal(normal);
    int temporalSampleStride = min(64 / steps, 6);
    float temporalFrameOffset = mod(temporalSampleFrame, float(64 / steps));

    // For each ray determine if it hits geometry and accumulate occlusion or color
    float stepSize = 1.0 / float(steps);
    for (int i = 0; i < steps; ++i) {
      float sampleIndex = float(i * temporalSampleStride + int(temporalFrameOffset));
      randomDirectionSample = blueNoiseDither(floor(gl_FragCoord.xy), sampleIndex);
      vec2 hemisphereSample = randomDirectionSample.rg * 0.5 + 0.5;
      float offsetSample = randomDirectionSample.a * 0.5 + 0.5;
      vec3 rayDirection = normalBasis * sampleCosineHemisphere(hemisphereSample);
      float rayMarchScreenReach = rayMarchScreenReachFromWorldReach(viewPos.xyz, rayDirection, rayMarchWorldReach);
      rayMarchScreenReach = clamp(rayMarchScreenReach, rayMarchMinReach, rayMarchMaxReach);
      vec3 hit = screenSpaceIntersectionWithLimits(
        rayDirection,
        viewPos.xyz,
        normalize(viewPos.xyz),
        normal,
        offsetSample,
        rayMarchScreenReach,
        rayMarchMaxSteps
      );

      if (hit.z > 0.0) {
        ${s(p.hasColor,n`
          // Emission and color bleed - Reproject the current receiver and sampled hit to estimate bounced color
          vec3 receiverColor = texture(lastFrameColorTexture, reprojectedCoordinate.xy).rgb;

          vec2 hitReprojectedCoordinate = reprojectionCoordinate(hit);
          vec3 sourceColor = texture(lastFrameColorTexture, hitReprojectedCoordinate).rgb;
          vec3 sourceColorLinear = linearizeGamma(sourceColor);
          vec3 sourceEmission = ${s(p.hasEmission,"texture(lastFrameEmissionTexture, hitReprojectedCoordinate).xyz","vec3(0.0)")};

          float emissionWeight = mix(
            rayMarchMinReachEmissionWeight,
            rayMarchMaxReachEmissionWeight,
            (rayMarchScreenReach - rayMarchMinReach) / max(rayMarchMaxReach - rayMarchMinReach, 0.00001)
          );
          fragGlobalIllumination.rgb += ((sourceColorLinear * colorBleedWeight) + sourceEmission * emissionWeight) * stepSize;
          `)}
      } else {
        // Occlusion - heuristic modulating sky intensity based on angle to main light
        vec4 viewMainLightDirection = view * vec4(mainLightDirection, 0.0);
        float skyModulation = pow(max(dot(rayDirection, viewMainLightDirection.xyz), 0.0), 3.0) * 5.5;
        float skyFacingWeight = clamp(3.5 * dot(viewMainLightDirection.xyz, normal), 0.0, 1.0);
        skyModulation = mix(1.0, skyModulation * 0.2 + 0.8, skyFacingWeight);
        fragGlobalIllumination.a += skyModulation * stepSize;
      }
    }

    // Rendering trick add noise to reduce accumulation artifacts
    float accumulationDither = occlusionBlendWeight < 1.0
      ? randomDirectionSample.b * ${n.float(B)}
      : 0.0;

    ${s(p.hasColor,n`
      // Accumulate color
      vec3 lastFrameColor = lastFrameGlobalIllumination.rgb;
      float colorDitherScale = isScaledGlobalIllumination ? ${n.float(C)} : 1.0;
      fragGlobalIllumination.rgb = resetColorHistory
        ? vec3(0.0)
        : mix(lastFrameColor + accumulationDither * colorDitherScale, fragGlobalIllumination.rgb, colorBlendWeight);
      `,n`
      fragGlobalIllumination.rgb = vec3(0.0);
      `)}
    fragGlobalIllumination.rgb = quantizeGlobalIlluminationColor(fragGlobalIllumination.rgb);

    // Accumulate occlusion
    fragGlobalIllumination.a = mix(lastFrameGlobalIllumination.a + accumulationDither, fragGlobalIllumination.a, occlusionBlendWeight);

    fragWeight = occlusionBlendWeight;
  `),z}const U=Object.freeze(Object.defineProperty({__proto__:null,GlobalIlluminationPassParameters:O,build:V,defaultColorBleedWeight:z,defaultRayMarchMaxReach:T,defaultRayMarchMaxReachEmissionWeight:$,defaultRayMarchMaxSteps:L,defaultRayMarchMinReach:P,defaultRayMarchMinReachEmissionWeight:E,defaultRayMarchWorldReach:G},Symbol.toStringTag,{value:"Module"}));export{O as G,U as a,L as b,V as c,T as d,z as e,$ as f,P as g,E as h,G as i};
