/*
====================================================================================================

    Copyright (C) 2020 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

struct ssrtData {
    vec3 bounce;
    vec4 emission;
};

const float ssrtDist    = euler;

vec3 screenspaceRT(vec3 position, vec3 direction, float noise) {
    const uint maxSteps     = 6;
    const float stepSize    = (ssrtDist * ssrtRange) / float(maxSteps);

    vec3 stepVector         = direction * stepSize;

    vec3 endPosition        = position + stepVector * maxSteps;
    vec3 endScreenPosition  = viewToScreenSpace(endPosition);

    vec2 maxPosXY           = max(abs(endScreenPosition.xy * 2.0 - 1.0), vec2(1.0));
    float stepMult          = min2(vec2(1.0) / maxPosXY);
        stepVector         *= stepMult;

    vec3 samplePos          = position + stepVector / maxSteps / 2.0;
        samplePos          += stepVector * noise;

    for (uint i = 0; i < maxSteps; ++i) {
        vec3 screenPos      = viewToScreenSpace(samplePos);
            samplePos      += stepVector;
        if (saturate(screenPos.xy) != screenPos.xy) break;

        float depthSample   = texelFetch(depthtex0, ivec2(screenPos.xy * viewSize), 0).x;
        float linearSample  = depthLinear(depthSample);
        float currentDepth  = depthLinear(screenPos.z);

        if (linearSample < currentDepth) {
            float dist      = abs(linearSample - currentDepth) / currentDepth;
            if (dist <= 0.2) return vec3(screenPos.xy, depthSample);
        }
    }

    return vec3(1.1);
}

ssrtData SSRT(vec3 viewPos, vec3 sceneNormal, float dither, bool hand, float skyOcclusion) {
    const uint samples  = ssrtSPP;

    vec3 viewDir    = normalize(viewPos);
    vec3 viewNormal = mat3(gbufferModelView) * sceneNormal;
    vec3 scenePos   = viewToSceneSpace(viewPos);

    vec3 bounced    = vec3(0.0);
    vec4 emission   = vec4(0.0);

    const float maxRayDist  = (ssrtDist * ssrtRange) * sqrt2;
    const float falloffScale = ssrtDist / maxRayDist;

    vec3 rayStart   = viewPos;
    vec3 rayPos     = rayStart;

    bool hit        = false;

    vec3 hitPos     = vec3(0.0);

    float noise     = ditherBluenoise();

    float gnoise    = ditherHashNoise();

    #ifdef rsmBounce
        vec3 shadowPos      = scenePos + vec3(0.08) * lightvec;
            shadowPos       = viewMAD(shadowModelView, shadowPos);
        vec3 shadowClipPos  = projMAD(shadowProjection, shadowPos);

        vec2 rsmDir     = sincos(ditherSize * goldenAngle);

        vec2 ditherNoise = vec2(dither, noise);
    #endif

    bool doRSM  = false;

    for (uint i = 0; i < samples; ++i) {
        vec3 rayDir = sphereMap(fermatsSpiralGoldenN(float(i + 1) * 64.0 + noise * 64.0, float(samples) * 64.0));
        vec3 rayDirScene = rayDir;
            rayDir  = mat3(gbufferModelView) * rayDir;

        if (dot(rayDir, viewNormal) < 0.0) {
            rayDir = -rayDir;
            rayDirScene = -rayDirScene;
        }

        vec3 hitPosition    = screenspaceRT(viewPos, rayDir, gnoise);

        ivec2 pixelPos      = ivec2(hitPosition.xy * viewSize);

        vec3 hitViewPos     = screenToViewSpace(hitPosition);

        float distFromStart = distance(hitViewPos, viewPos);

        if (hitPosition.z < 1.0) {
            vec3 tex2   = texelFetch(colortex2, pixelPos, 0).rgb;

            vec3 normal = texelFetch(colortex6, pixelPos, 0).xyz * 2.0 - 1.0;

            vec3 direction  = normalize(hitViewPos - viewPos);

            vec3 albedo     = texelFetch(colortex0, pixelPos, 0).rgb;

            float lambert   = diffuseLambert(viewNormal, direction) * pi;

            if (dot(sceneNormal, normal) > 0.75){
                #ifdef rsmBounce
                    bounced    += rsmBounceResponse(shadowClipPos, shadowPos, sceneNormal, ditherNoise, i, skyOcclusion);
                #endif

                doRSM = true;
            } else {
                vec4 currBounce     = vec4(decode2x8(tex2.x), decode2x8(tex2.y));
                if (currBounce.a < 1e-2) continue;

                bounced    += (albedo * currBounce.yzw) * (currBounce.x * lambert) * halfPi;
            }

            #ifdef ssrtEmissionToggle
                vec2 emissionParam = decode2x8(tex2.z);

                float emissionAlpha = max2(emissionParam);

                if (emissionAlpha < 1e-2) continue;

                //float albedoLum     = getLuma(albedo);

                float albedoLum = mix(v3avg(albedo), max3(albedo), 0.71);
                    albedoLum   = saturate(albedoLum * sqrt2);

                float emitterLum = saturate(mix(sqr(albedoLum), sqrt(max3(albedo)), albedoLum));

                #ifdef labEmissionEnabled
                    float emissionScale = max(emissionParam.x * emitterLum, emissionParam.y);
                #else
                    float emissionScale = emissionParam.x * emitterLum;
                #endif

                vec3 emissionColor = mix(sqr(normalize(albedo)), normalize(albedo), sqr(emissionScale)) * emissionScale;

                //vec3 emissionColor = saturate(mix((albedoLum) * sqr(normalize(albedo)), albedo, albedoLum));

                float emissionFalloff = cube(1.0 - linStep(distFromStart, maxRayDist / euler, maxRayDist));

                emission.rgb   += emissionColor * (lambert) * emissionFalloff;

                if ((emissionAlpha * lambert) > 0.0) emission.a += 1.0;
            #endif

        } else {

            #ifdef rsmBounce
                bounced    += rsmBounceResponse(shadowClipPos, shadowPos, sceneNormal, ditherNoise, i, skyOcclusion);
            #endif

            doRSM   = true;

        }
    }

    bounced    /= float(samples);
    emission.rgb /= float(samples);
    emission.rgb *= sqrPi;
    emission.a  = saturate(emission.a);

    return ssrtData(bounced, emission);
}

vec4 SSRT_Emission(vec3 viewPos, vec3 sceneNormal, float dither, bool hand) {
    const uint samples  = ssrtSPP;

    vec3 viewDir    = normalize(viewPos);
    vec3 viewNormal = mat3(gbufferModelView) * sceneNormal;

    vec3 bounced    = vec3(0.0);
    vec4 emission   = vec4(0.0);

    const float maxRayDist  = (ssrtDist * ssrtRange) * sqrt2;
    const float falloffScale = ssrtDist / maxRayDist;

    vec3 rayStart   = viewPos;
    vec3 rayPos     = rayStart;

    bool hit        = false;

    vec3 hitPos     = vec3(0.0);

    float noise     = ditherBluenoise();

    float gnoise    = ditherHashNoise();

    for (uint i = 0; i < samples; ++i) {
        vec3 rayDir = sphereMap(fermatsSpiralGoldenN(float(i + 1) * 64.0 + noise * 64.0, float(samples) * 64.0));
            rayDir  = mat3(gbufferModelView) * rayDir;

        if (dot(rayDir, viewNormal) < 0.0) rayDir = -rayDir;

        vec3 hitPosition    = screenspaceRT(viewPos, rayDir, gnoise);

        ivec2 pixelPos      = ivec2(hitPosition.xy * viewSize);

        vec3 hitViewPos     = screenToViewSpace(hitPosition);

        float distFromStart = distance(hitViewPos, viewPos);

        if (hitPosition.z < 1.0) {
            vec3 tex2   = texelFetch(colortex2, pixelPos, 0).rgb;

            vec3 normal = texelFetch(colortex6, pixelPos, 0).xyz * 2.0 - 1.0;

            vec3 direction  = normalize(hitViewPos - viewPos);

            vec3 albedo     = texelFetch(colortex0, pixelPos, 0).rgb;

            float lambert   = diffuseLambert(viewNormal, direction) * pi;

            vec2 emissionParam = decode2x8(tex2.z);

            float emissionAlpha = max2(emissionParam);

            if (emissionAlpha < 1e-2) continue;

            float albedoLum = mix(v3avg(albedo), max3(albedo), 0.71);
                albedoLum   = saturate(albedoLum * sqrt2);

            float emitterLum = saturate(mix(sqr(albedoLum), sqrt(max3(albedo)), albedoLum));

            #ifdef labEmissionEnabled
                float emissionScale = max(emissionParam.x * emitterLum, emissionParam.y);
            #else
                float emissionScale = emissionParam.x * emitterLum;
            #endif

            vec3 emissionColor = mix(sqr(normalize(albedo)), normalize(albedo), sqr(emissionScale)) * emissionScale;

            float emissionFalloff = cube(1.0 - linStep(distFromStart, maxRayDist / euler, maxRayDist));

            emission.rgb   += emissionColor * (lambert) * emissionFalloff;

            if ((emissionAlpha * lambert) > 0.0) emission.a += 1.0;
        }
    }

    emission.rgb /= float(samples);
    emission.rgb *= sqrPi;
    emission.a  = saturate(emission.a);

    return emission;
}