/**
 * Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 */
export declare const trace_frag = "\nprecision highp int;\nprecision highp float;\nprecision highp sampler2D;\n\nuniform sampler2D tColor;\nuniform sampler2D tNormal;\nuniform sampler2D tShaded;\nuniform sampler2D tThickness;\nuniform sampler2D tAccumulate;\nuniform sampler2D tDepth;\nuniform vec2 uTexSize;\nuniform vec4 uBounds;\n\nuniform float uNear;\nuniform float uFar;\nuniform float uFogNear;\nuniform float uFogFar;\nuniform vec3 uFogColor;\n\n#if dLightCount != 0\n    uniform vec3 uLightDirection[dLightCount];\n    uniform vec3 uLightColor[dLightCount];\n#endif\nuniform vec3 uAmbientColor;\nuniform vec3 uLightStrength;\n\nuniform int uFrameNo;\n\nuniform float uRayDistance;\nuniform float uMinThickness;\nuniform float uThicknessFactor;\nuniform float uThickness;\n\nuniform float uShadowSoftness;\nuniform float uShadowThickness;\n\nuniform mat4 uProjection;\nuniform mat4 uInvProjection;\n\n#include common\n\n// parts adapted from\n// - https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/\n// - https://github.com/0beqz/realism-effects/blob/v2-debug/src/ssgi/shader/ssgi.frag\n\n//\n\n// after a hit, it moves the ray this far along the normal away from a surface.\n// Helps prevent incorrect intersections when rays bounce off of objects.\n#define RayPosNormalNudge 0.0001\n\n#if __VERSION__ == 100\n    #define StateType float\n\n    // from https://www.shadertoy.com/view/4djSRW\n    float hash14(vec4 p4) {\n        p4 = fract(p4  * vec4(0.1031, 0.1030, 0.0973, 0.1099));\n        p4 += dot(p4, p4.wzxy + 33.33);\n        return fract((p4.x + p4.y) * (p4.z + p4.w));\n    }\n\n    float randomFloat(inout float state) {\n        state += 0.06711056;\n        return 1.0 - hash14(vec4(gl_FragCoord.xy, float(uFrameNo), state));\n    }\n#else\n    #define StateType uint\n\n    // https://www.pcg-random.org/\n    // https://jcgt.org/published/0009/03/02/\n    uint pcg(inout uint seed) {\n        seed = seed * 747796405u + 2891336453u;\n        uint word = ((seed >> ((seed >> 28u) + 4u)) ^ seed) * 277803737u;\n        return (word >> 22u) ^ word;\n    }\n\n    float randomFloat(inout uint state) {\n        return float(pcg(state)) / 4294967296.0;\n    }\n#endif\n\nvec3 randomUnitVector(inout StateType state) {\n    float z = randomFloat(state) * 2.0 - 1.0;\n    float a = randomFloat(state) * TWO_PI;\n    float r = sqrt(1.0 - z * z);\n    float x = r * cos(a);\n    float y = r * sin(a);\n    return vec3(x, y, z);\n}\n\nstruct RayHitInfo {\n    bool missed;\n    vec3 position;\n    vec3 normal;\n    vec3 color;\n    vec3 emissive;\n};\n\n//\n\nfloat getDepth(const in vec2 coords) {\n    vec2 c = vec2(clamp(coords.x, uBounds.x, uBounds.z), clamp(coords.y, uBounds.y, uBounds.w));\n    return texture2D(tDepth, c).r;\n}\n\nfloat getThickness(const in vec2 coords) {\n    vec2 c = vec2(clamp(coords.x, uBounds.x, uBounds.z), clamp(coords.y, uBounds.y, uBounds.w));\n    return unpackRGBAToDepth(texture2D(tThickness, c));\n}\n\nbool isBackground(const in float depth) {\n    return depth == 1.0;\n}\n\nfloat getViewZ(const in float depth) {\n    #if dOrthographic == 1\n        return orthographicDepthToViewZ(depth, uNear, uFar);\n    #else\n        return perspectiveDepthToViewZ(depth, uNear, uFar);\n    #endif\n}\n\nvec2 viewSpaceToScreenSpace(const vec3 position) {\n    vec4 projectedCoord = uProjection * vec4(position, 1.0);\n    projectedCoord.xy /= projectedCoord.w;\n    // [-1, 1] --> [0, 1] (NDC to screen position)\n    projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;\n    return projectedCoord.xy;\n}\n\nvec2 binarySearch(inout vec3 dir, inout vec3 hitPos) {\n    float rayHitDepthDifference;\n    vec2 coords;\n\n    dir *= 0.5;\n    hitPos -= dir;\n\n    for (int i = 0; i < dRefineSteps; i++) {\n        coords = viewSpaceToScreenSpace(hitPos);\n        float depth = getDepth(coords);\n        float z = getViewZ(depth);\n        rayHitDepthDifference = z - hitPos.z;\n\n        dir *= 0.5;\n        if (rayHitDepthDifference >= 0.0) {\n            hitPos -= dir;\n        } else {\n            hitPos += dir;\n        }\n    }\n\n    coords = viewSpaceToScreenSpace(hitPos);\n\n    return coords;\n}\n\nfloat calculateGrowthFactor(float begin, float end, float steps) {\n    return pow(end / begin, 1.0 / steps);\n}\n\nvec2 rayMarch(in vec3 dir, in float thickness, inout vec3 hitPos, out bool missed) {\n    float rayHitDepthDifference;\n    vec2 coords;\n\n    float begin = float(dFirstStepSize);\n    dir *= begin;\n    missed = false;\n    float gf = calculateGrowthFactor(begin, uRayDistance, float(dSteps));\n\n    for (int i = 1; i < dSteps; i++) {\n        hitPos += dir;\n        dir *= gf;\n\n        coords = viewSpaceToScreenSpace(hitPos);\n        float depth = getDepth(coords);\n        float z = getViewZ(depth);\n        rayHitDepthDifference = z - hitPos.z;\n\n        if (thickness == 0.0) {\n            #ifdef dThicknessMode_auto\n                thickness = max(uMinThickness, (getViewZ(getThickness(coords)) - z) * uThicknessFactor * texture2D(tColor, coords).a);\n            #else\n                thickness = uThickness;\n            #endif\n        }\n\n        if (rayHitDepthDifference >= 0.0 && rayHitDepthDifference < thickness) {\n            if (dRefineSteps == 0) {\n                return coords;\n            } else {\n                return binarySearch(dir, hitPos);\n            }\n        }\n    }\n\n    missed = true;\n\n    return coords;\n}\n\n//\n\nvoid trace(in vec3 rayPos, in vec3 rayDir, inout RayHitInfo hitInfo) {\n    vec3 hitPos = vec3(rayPos);\n    bool missed;\n    vec2 coords;\n    coords = rayMarch(rayDir, 0.0, hitPos, missed);\n\n    hitInfo.missed = missed;\n    hitInfo.position = hitPos;\n    hitInfo.normal = -texture2D(tNormal, coords).rgb;\n    hitInfo.color = texture2D(tColor, coords).rgb;\n    hitInfo.emissive = texture2D(tColor, coords).rgb * texture2D(tNormal, coords).a * 2.0;\n\n    float depth = getDepth(coords);\n    if (isBackground(depth)) {\n        hitInfo.emissive = vec3(0.0);\n    }\n}\n\nvec3 viewPos;\n\nvec3 colorForRay(in vec3 startRayPos, in vec3 startRayDir, inout StateType rngState) {\n    vec3 ret = vec3(0.0, 0.0, 0.0);\n\n    vec3 throughput = vec3(1.0, 1.0, 1.0);\n    vec3 rayPos = startRayPos;\n    vec3 rayDir = startRayDir;\n\n    RayHitInfo hitInfo;\n    RayHitInfo prevHitInfo;\n\n    for (int bounceIndex = 0; bounceIndex <= dBounces; ++bounceIndex) {\n        // shoot a ray out into the world\n        if (bounceIndex == 0) {\n            vec2 coords = gl_FragCoord.xy / uTexSize;\n            float depth = getDepth(coords);\n\n            hitInfo.missed = false;\n            hitInfo.position = screenSpaceToViewSpace(vec3(coords, depth), uInvProjection);\n            hitInfo.normal = -texture2D(tNormal, coords).rgb;\n            hitInfo.color = texture2D(tShaded, coords).rgb;\n            hitInfo.emissive = texture2D(tColor, coords).rgb * texture2D(tNormal, coords).a;\n\n            // shadow\n            #ifdef dShadowEnable\n                #if dLightCount != 0\n                    vec3 directLight = vec3(uAmbientColor);\n                    #pragma unroll_loop_start\n                    bool missed;\n                    vec3 hitPos;\n                    for (int i = 0; i < dLightCount; ++i) {\n                        missed = false;\n                        hitPos = viewPos + hitInfo.normal * RayPosNormalNudge;\n                        hitPos += -uLightDirection[i] * (randomFloat(rngState));\n                        rayMarch(-uLightDirection[i] + randomUnitVector(rngState) * uShadowSoftness, uShadowThickness, hitPos, missed);\n                        if (missed) directLight += uLightColor[i];\n                    }\n                    #pragma unroll_loop_end\n                    hitInfo.color *= directLight / uLightStrength;\n                #endif\n            #endif\n\n            if (hitInfo.normal == vec3(0.0)) {\n                hitInfo.missed = true;\n            }\n        } else {\n            prevHitInfo = hitInfo;\n            trace(rayPos, rayDir, hitInfo);\n        }\n\n        // if the ray missed, we are done\n        if (hitInfo.missed) {\n            vec3 accIrradiance = vec3(1.0);\n            #ifdef dGlow\n                if (bounceIndex > 1) {\n                    accIrradiance = uLightStrength;\n                }\n            #else\n                if (bounceIndex > 1) {\n                    accIrradiance = uAmbientColor;\n                    #if dLightCount != 0\n                        #pragma unroll_loop_start\n                        float dotNL;\n                        vec3 irradiance;\n                        for (int i = 0; i < dLightCount; ++i) {\n                            dotNL = saturate(dot(prevHitInfo.normal, -uLightDirection[i]));\n                            irradiance = dotNL * uLightColor[i];\n                            accIrradiance += irradiance;\n                        }\n                        #pragma unroll_loop_end\n                    #endif\n                }\n            #endif\n            ret += prevHitInfo.color * accIrradiance * throughput;\n            break;\n        }\n\n        // add emissive light\n        ret += hitInfo.emissive * throughput;\n\n        // update the ray position\n        rayPos = hitInfo.position + hitInfo.normal * RayPosNormalNudge;\n\n        // new ray direction from normal oriented cosine weighted hemisphere sample\n        rayDir = normalize(hitInfo.normal + randomUnitVector(rngState));\n\n        if (bounceIndex == 0) {\n            continue;\n        }\n\n        // update the colorMultiplier.\n        throughput *= hitInfo.color;\n\n        // Russian Roulette\n        // As the throughput gets smaller, the ray is more likely to get terminated early.\n        // Survivors have their value boosted to make up for fewer samples being in the average.\n        {\n            float p = max(throughput.r, max(throughput.g, throughput.b));\n            if (randomFloat(rngState) > p)\n                break;\n\n            // Add the energy we 'lose' by randomly terminating paths\n            throughput *= 1.0 / p;\n        }\n    }\n\n    // return pixel color\n    return ret;\n}\n\nvoid main() {\n    vec2 coords = gl_FragCoord.xy / uTexSize;\n    float depth = getDepth(coords);\n\n    if (isBackground(depth)) {\n        gl_FragColor = texture2D(tColor, coords);\n        return;\n    }\n\n    #if __VERSION__ == 100\n        float rngState = 26699.0;\n    #else\n        // initialize a random number state based on gl_FragCoord and uFrameNo\n        uint rngState = uint(uint(gl_FragCoord.x) * 1973u + uint(gl_FragCoord.y) * 9277u + uint(uFrameNo) * 26699u) | 1u;\n    #endif\n\n    vec3 cameraPos = vec3(0.0, 0.0, 0.0);\n    viewPos = screenSpaceToViewSpace(vec3(coords, depth), uInvProjection);\n    vec3 rayDir = normalize(viewPos);\n\n    // raytrace for this pixel\n    vec3 color = vec3(0.0, 0.0, 0.0);\n    for (int index = 0; index < int(dRendersPerFrame); ++index) {\n        color += colorForRay(cameraPos, rayDir, rngState) / float(dRendersPerFrame);\n    }\n\n    // average the frames together\n    vec4 lastFrameColor = texture2D(tAccumulate, coords);\n    float blend = (uFrameNo < 1 || lastFrameColor.a == 0.0) ? 1.0 : 1.0 / (1.0 + (1.0 / lastFrameColor.a));\n    color = mix(lastFrameColor.rgb, color, blend);\n\n    // show the result\n    gl_FragColor = vec4(color, blend);\n}\n";
