
#if defined RSM

#endif
#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)

// #define SSS_ALT

float phaseg(float x, float g)
{
    float gg = g * g;
    return (gg * -0.25 + 0.25) * pow(-2.0 * (g * x) + (gg + 1.0), -1.5) / PI;
}

vec2 tapLocationBlueNoise(int sampleNumber, int nb, float nbRot, vec2 blueNoise)
{
    float nbInv = 1.0 / nb;                     // Pre-calculate reciprocal if nb is constant
    float alpha = (sampleNumber + 0.5) * nbInv; // Use pre-calculated reciprocal here

    // Incorporate blue noise in angle calculation
    float angle = (alpha * nbRot + blueNoise.x) * TWO_PI;

    // Calculate the position using sine and cosine for better distribution
    float sin_v = sin(angle);
    float cos_v = cos(angle);

    // Slightly vary the radius using blue noise
    float radius = alpha + blueNoise.y * nbInv; // Use pre-calculated reciprocal here as well

    return vec2(cos_v, sin_v) * radius;
}

float rayTraceShadow(vec3 dir, vec3 position, float dither)
{
    float quality = 16.0;
    float farMultiplier = far * sqrt(3.0);
    float rayLength = (position.z + dir.z * farMultiplier > -near) ? (-near - position.z) / dir.z : farMultiplier;

    vec3 clipPosition = toClipSpace3(position);
    vec3 endPosition = position + dir * rayLength;
    vec3 clipEndPosition = toClipSpace3(endPosition);
    vec3 direction = clipEndPosition - clipPosition;

    float maxStep = max(abs(direction.x) / texelSize.x, abs(direction.y) / texelSize.y);
    direction = direction / maxStep;

    vec3 step = direction * 3.0;
    vec3 spos = clipPosition + step * dither;
    float shadow = 1.0;

    for (int i = 0; i < int(quality); ++i)
    {
        spos += step;
        float depthSample = texture2D(depthtex0, spos.xy).x;

        if (depthSample < spos.z)
        {
            float depthDifference = abs(linZ(depthSample) - linZ(spos.z)) / linZ(spos.z);
            if (depthDifference < 0.01)
            {
                shadow = 0.0;
                break;
            }
        }
    }

    return shadow;
}

float computeSSS(float sssAmount, vec3 albedo, vec3 normal, vec3 lightPos, float avgDepth, inout float diffuseSun)
{
    avgDepth = sqrt(avgDepth * 1000.0);

    float extinction = 1.0 - luma(albedo) * 0.85;

    float angleFactor = dot(normalize(normal), lightPos);

    float extinctionFactor = exp(-avgDepth * 11.0 * extinction);

    float sssBase = extinctionFactor + 3.0 * pow(extinctionFactor, 1.0 / 3.0);

    float phaseFactor = phaseg(angleFactor, 0.85);

    float scattering = clamp((0.7 + 0.3 * 3.14159265 * phaseFactor) * 1.5 / 4.0 * sssAmount, 0.0, 1.0);

    // diffuseSun *= 1.0 - sssAmount;

    return clamp(sssBase * scattering, 0.0, 1.0);
}
/*
float cloud_height = 1500.;
float cubeSmooth(float x)
{
    return x * x * (3.0 - 2.0 * x);
}

float cloudTextureCubic(sampler2D tex, vec2 pos)
{
    vec2 texSize = textureSize(tex, 0) * 2.0;
    vec2 texelSize = 1.0 / texSize;

    vec2 fractPos = fract(pos * texSize);
    vec2 smoothPos = vec2(cubeSmooth(fractPos.x), cubeSmooth(fractPos.y));

    float baseTexel = texture(tex, pos).a;
    float rightTexel = texture(tex, pos + vec2(texelSize.x, 0.0)).a;
    float upTexel = texture(tex, pos + vec2(0.0, texelSize.y)).a;
    float diagonalTexel = texture(tex, pos + texelSize).a;

    float mixX1 = mix(baseTexel, rightTexel, smoothPos.x);
    float mixX2 = mix(upTexel, diagonalTexel, smoothPos.x);

    return mix(mixX1, mixX2, smoothPos.y);
}





float cloudCov(in vec3 pos, vec3 samplePos)
{

    const float CloudBaseOffset = 100.0;
    const float CloudTopOffset = 450.0;
    const float CloudBottomOffset = 125.0;
    const float HeightDivisionFactor = 500.0;
    const float BottomHeightDivisionFactor = 100.0;
    const float InverseHeightDivisionFactor = 1.0 / HeightDivisionFactor;
    const float InverseBottomHeightDivisionFactor = 1.0 / BottomHeightDivisionFactor;

    // Height differences simplified with constants
    float heightAboveCloud = max(pos.y - (cloud_height + CloudBaseOffset), 0.0);
    float heightBelowCloudTop = max(-pos.y + (cloud_height + CloudTopOffset), 0.0);
    float heightBelowCloudBottom = min(-pos.y + (cloud_height + CloudBottomOffset), 0.0);

    // Multipliers based on height using multiplication for optimization
    float mult = heightAboveCloud * InverseHeightDivisionFactor;
    float mult2 = heightBelowCloudTop * InverseHeightDivisionFactor;
    float mult3 = heightBelowCloudBottom * InverseBottomHeightDivisionFactor;

    // Cloud coverage calculation with optimizations
    float textureContribution = cloudTextureCubic(depthtex1, samplePos.xz / CloudSize) * 2.0;
    // float textureContribution = texture(depthtex1, samplePos.xz / CloudSize).x * 2.0;

    float coverage = (textureContribution + rainStrength) / (0.8 - rainStrength * 0.8);
    coverage = clamp(coverage, 0.0, 1.0);

    // Final cloud coverage with adjustments for readability and optimization
    float cloud = max(coverage - mult - mult2 + mult3, 0.0);

    return cloud;
}
*/

#if defined RSM

vec2 tapLocation3(int sampleNumber, float spinAngle, int nb, float nbRot)
{
    float alpha = float(sampleNumber + spinAngle / 6.28) / nb;
    float angle = alpha * (nbRot * 6.28) + spinAngle;

    float ssR = alpha * alpha;
    float sin_v, cos_v;

    sin_v = sin(angle);
    cos_v = cos(angle);

    return vec2(cos_v, sin_v) * ssR;
}
vec3 get_GI(vec3 normal, float noise, vec3 ProjShadowPos)
{
    float Depth = texture2D(depthtex0, texcoord).x;
    vec4 fragpos = gbufferProjectionInverse * (vec4(texcoord, Depth, 1.0) * 2.0 - 1.0);
    fragpos /= fragpos.w;

    vec4 worldposition = gbufferModelViewInverse * vec4(fragpos);
    worldposition = shadowModelView * worldposition;
    vec3 wpos2 = worldposition.xyz;
    worldposition = shadowProjection * worldposition;
    worldposition /= worldposition.w;
    worldposition = worldposition * vec4(0.5, 0.5, 1 / 6, 1) + 0.5;

    vec3 t1 = mat3(shadowModelView) * (normal);

    vec3 projNormal = t1.rgb;

    vec3 gi = vec3(0.);

    noise = noise * 2.0 * PI;

    const int nbSamples = 8;

    float sampleRadius = 0.25;
    for (int i = 0; i < nbSamples; i++)
    {

        vec2 offset = tapLocation3(i, noise, nbSamples, 90);

        vec2 TC = ((ProjShadowPos.xy + offset * sampleRadius) * 2.0 - 1.0) * 0.5 + 0.5;
        vec2 TC2 = ((worldposition.xy + offset * sampleRadius) * 2.0 - 1.0) * 0.5 + 0.5;

        vec4 shadowSampleLowRes = texelFetch2D(shadowcolor0, ivec2(TC * shadowMapResolution), 0);
        float sampleDepth = texelFetch2D(shadowtex0, ivec2(TC * shadowMapResolution), 0).x;
        vec3 shadowNormal = texelFetch2D(shadowcolor1, ivec2(TC * shadowMapResolution), 0).xyz * 2 - 1;
        vec3 sAlbedo = pow(shadowSampleLowRes.rgb, vec3(2.2));

        vec3 S1 = vec3((TC2 * 2.0 - 1.0) * 0.5 + 0.5, sampleDepth);
        vec4 shadowPos = shadowProjectionInverse * ((vec4(S1, 1.0) * 2.0 - 1.0) * vec4(1.0, 1.0, 6.0, 1.0));
        vec3 V = shadowPos.xyz - wpos2;

        float VdotV = dot(V, V);
        float NdotS = clamp(dot(projNormal, V * inversesqrt(VdotV)), 0.0, 1.0);
        float inHemisphere = sqrt(clamp(-dot(shadowNormal, V * inversesqrt(VdotV)), 0.0, 1.0)) * 0.5 + 0.5;
        float Scoef = 1.0 / (1.0 + VdotV);
        float weight = length(offset);

        gi += inHemisphere * NdotS * Scoef * pow(weight, 1.3) * sAlbedo;
        // gi += sAlbedo;
    }

    const float dS = pow(576 / 512.0, 2.0);
    return gi * (sampleRadius * sampleRadius * 512.0 * 512.0 / PI / nbSamples) * dS;
    // return gi;
}

vec3 calculateAlbedo(vec3 ProjShadowPos, vec4 noise_2, float distortFac, float ambientA, vec2 lightmap)
{
    vec3 sAlbedo = vec3(0.0);
    float avgDepth2 = 0.0;
    const int iterationsRSM = RSM_SAMPLES;
    int counteRSM = (frameCounter % 40000) * int(iterationsRSM);

    for (int i = 0; i < iterationsRSM; i++)
    {
        int seed = counteRSM + i;
        vec2 ij = fract(R2_samples3(seed) + noise_2.rg);
        vec2 offset = tapLocationBlueNoise(i, 64, 80, ij) * 512.0;
        vec2 offset2 = tapLocationBlueNoise(i, 64, 80, ij);
        vec2 shadowCoord2 = (ProjShadowPos.xy + offset2);
        float d2 = texture(shadowtex0, (shadowCoord2.xy)).x;
        avgDepth2 += max(ProjShadowPos.z - d2, 0.0);
        float falloff = 1.0 - ((avgDepth2) * 128.0);

        vec2 coord = ProjShadowPos.xy + offset * (distortFac / shadowMapResolution);
        sAlbedo += mix(texelFetch(shadowcolor0, ivec2(coord * shadowMapResolution), 0).rgb, vec3(ambientA / 6), clamp(falloff, 0.0, 1.0));
    }

    sAlbedo /= float(iterationsRSM);
    // sAlbedo = ((sAlbedo - luma(sAlbedo)) * 1.0 + sAlbedo);
    sAlbedo *= ambientA * lightmap.y;

    return sAlbedo;
}
#endif
#if CLOUD_STYLE == 2

#include "/lib/clouds/clouds_loop_a.glsl"
#else
#ifdef ALT_CLOUDS
#include "/lib/clouds/clouds_bloop_a.glsl"
#else
#include "/lib/clouds/clouds_bloop.glsl"
#endif
#endif

uniform vec3 sunPosition;
float cloudShadow(vec3 eyePlayerPos, float noise)
{

    const int rayMarchSteps = 6;
    vec3 rPos = eyePlayerPos + cameraPosition;

    float cloudShadow = 0.0;

    for (int i = 0; i < rayMarchSteps; i++)
    {

        vec3 cloudPos = rPos + sunPosWorld / abs(sunPosWorld.y) * (cloud_height + (noise + i) / rayMarchSteps * cloud_height_max - rPos.y);
        cloudShadow += getCloudDensity(cloudPos, 1);
    }
    cloudShadow = mix(1.0, exp(-cloudShadow * cloudDensity * cloud_height_max / rayMarchSteps), mix(0.9, 1.0, rainStrength));

    return cloudShadow;
}
vec2 interpolate(float x)
{
    // Define mix1 and mix2 for all ranges in parallel
    float mix1_1 = 10.0;
    float mix2_1 = -0.0002;

    float mix1_2 = 10.0;
    float mix2_2 = -0.00050;

    float mix1_3 = 1.0;
    float mix2_3 = -0.00050;

    float mix1_4 = 1.0;
    float mix2_4 = -0.0008;

    float mix1_5 = 0.0;
    float mix2_5 = 0.004;

    float mix1_6 = 0.0;
    float mix2_6 = -0.0;

    // Use step functions to create masks for the ranges
    float mask1 = step(0.76, x);
    float mask2 = step(0.67, x) - mask1;
    float mask3 = step(0.55, x) - step(0.67, x);
    float mask4 = step(0.38, x) - step(0.55, x);
    float mask5 = step(0.18, x) - step(0.38, x);
    float mask6 = 1.0 - step(0.18, x);

    // Calculate mix1 and mix2 based on masks
    float mix1 = mask1 * mix1_1 + mask2 * mix1_2 + mask3 * mix1_3 + mask4 * mix1_4 + mask5 * mix1_5 + mask6 * mix1_6;
    float mix2 = mask1 * mix2_1 + mask2 * mix2_2 + mask3 * mix2_3 + mask4 * mix2_4 + mask5 * mix2_5 + mask6 * mix2_6;

    // Calculate the interpolated values based on x
    float interpolatedMix1 = mix(mix1, mix1, (x - 0.76) / (0.67 - 0.76));
    float interpolatedMix2 = mix(mix2, mix2, (x - 0.76) / (0.67 - 0.76));

    // Return the interpolated values
    return vec2(interpolatedMix1, interpolatedMix2);
}

float shadowFunc(float diffuseSun, float sssAmount, vec3 p3, float noise, vec2 lightmap, vec3 fragpos,
                 inout vec3 sAlbedo, vec3 albedo, vec3 normals, inout vec3 colored, float entity)
{
    // sssAmount = 2;
    float mix1 = interpolate(shadowPosWorld.y).x;
    // float mix1 = 1.0;
    //  float mix2 = interpolate(shadowPosWorld.y).y;
    float mix2 = 0.0;

#ifdef DISTANT_HORIZONS
    mix1 = 1000;
#endif
    float SSS = 0;

    float shading = 1.0;
    p3 += normalize(shadowPosWorld) * 0.01;

    vec3 ProjShadowPos = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    ProjShadowPos = diagonal3(shadowProjection) * ProjShadowPos + shadowProjection[3].xyz;

    float distortFac = calcDistort(ProjShadowPos.xy);

    ProjShadowPos += vec3(shadowProjection[0].x, shadowProjection[1].y, shadowProjection[2].z) * (mat3(shadowModelView) * normals) * (distortFac * 0.004);

    ProjShadowPos.xy *= distortFac;

    vec2 bounds = vec2(1.0, 1.0) - 1.5 / shadowMapResolution;
    float pdepth = 0;
    float avgDepth = 0.3;
    if (all(lessThan(abs(ProjShadowPos.xy), bounds)) && abs(ProjShadowPos.z) < 6)
    {
        vec4 noise_2 = blueNoise4(gl_FragCoord.xy);

        shading = 0.0;
        pdepth = 0;
        avgDepth = 0;
        ProjShadowPos = ProjShadowPos * vec3(0.5, 0.5, 0.5 / 6.0) + 0.5;

        float d01k = 0.2 * distortFac;
        float rdMul0 = d01k / shadowMapResolution;

        const int iterations = SHADOW_SAMPLES;
        // float scaleFac = (mix(distortFac, noise * 10, float(sssAmount > 0.0)));
        float scaleFac = distortFac;

        ///////////////
        vec3 filterDepth = vec3(1, 16, 128.0); // x=min radius, y=max radius, z=max depth
        float mixval = 10;
#ifdef DISTANT_HORIZONS
        mixval = 0.5;
        filterDepth = vec3(3, 16, 128.0); // x=min radius, y=max radius, z=max depth

#endif
        float RadiusMax = distortFac / shadowMapResolution;

        float blockerCount = 0.0;

        float avgBlockerDepth0 = 0.0;
        float filterDepthRange = filterDepth.y - filterDepth.x;

        //////////////
        float threshMulMult = mix(2048, 4096, float(eyeAltitude > 400));
        float threshMulMult2 = mix(threshMulMult, 8192, float(eyeAltitude > 500));
        float threshMul = max(threshMulMult2 / shadowMapResolution * shadowDistance / 100.0, 0.95);

        float distortThresh = clamp(sqrt(1.0 - diffuseSun * diffuseSun) / diffuseSun, 0.0, mix1) / scaleFac;

        float diffthresh = distortThresh / 6000.0 * threshMul;

        int counter = (frameCounter % 40000) * int(iterations);
        float prtecomp = RadiusMax / (iterations * shadowMapResolution * scaleFac / 2.7);
        vec4 shadowcolor = vec4(0.0);
        for (int i = 0; i < iterations; i++)
        {
            vec2 ij = fract(R2_samples3(counter + i) + noise_2.rg);
            vec2 offset = tapLocationBlueNoise(i, iterations, 80, ij);

            float d = texture(shadowtex0, ((ProjShadowPos.xy + offset * RadiusMax).xy)).x;

            float weightpu = 3.0 + (i + noise) * prtecomp;
            float b = smoothstep(weightpu * diffthresh / 2.0, weightpu * diffthresh, ProjShadowPos.z - d);

            blockerCount += b;
            avgBlockerDepth0 += d * b;

            float isBlocker = step(0.9, blockerCount);

            float avgBlockerDepth = mix(ProjShadowPos.z, avgBlockerDepth0 / blockerCount, isBlocker);

            float t0 = max((ProjShadowPos.z - avgBlockerDepth) / filterDepth.z, 0.0) * 1500.0;

            pdepth = (t0 * filterDepthRange + filterDepth.x) * isBlocker * rdMul0;

            avgDepth += max(ProjShadowPos.z - d, 0.0);

            float shadowMapFactor = shadowMapResolution * (pdepth / iterations);

            float weight = 1.0 + noise * shadowMapFactor;

            vec3 shadowCoordWeighted = ProjShadowPos + vec3(offset * pdepth, -diffthresh * weight);
            float shadow0 = texture(shadowtex1, shadowCoordWeighted);

#ifdef COLORED_SHADOWS
            float shadow1 = texture(shadow, shadowCoordWeighted);

            shadowcolor += texture(shadowcolor0, shadowCoordWeighted.xy);

            float transparentshadow = distance(shadow1, shadow0);

            shadowcolor.rgb = mix(shadowcolor.rgb, vec3(0.0), 1 - transparentshadow);

            shading += clamp(shadow1 + (1 / shadowcolor.a) * transparentshadow, 0, 1);

#else
            shading += shadow0;

#endif
        }
        avgDepth = (avgDepth / iterations);

        shading /= iterations;

#ifdef COLORED_SHADOWS

        colored = (shadowcolor.rgb / iterations) * ambientA;
        // colored += colored;
#endif
#ifdef CLOUD_SHADOWS
        shading *= cloudShadow(p3, noise);
#endif
        // shading = mix(float(shading > 0.5), shading, biasblend3);

#if defined RSM
        if (entity < 0.5)
            //        sAlbedo = get_GI(normals, noise, ProjShadowPos) * lightmap.y;

            sAlbedo = calculateAlbedo(ProjShadowPos, noise_2, distortFac, ambientA, lightmap);
            // sAlbedo = mix(vec3(1 / 6), sAlbedo, distance(albedo / 6, sAlbedo));
#endif

        if (sssAmount > 0)
            SSS = computeSSS(sssAmount, albedo, p3, lightPos, avgDepth, diffuseSun);
    }

    if (diffuseSun * shading > 0.00001)
    {
#ifdef CONTACT_SHADOWS
        if (sssAmount == 0.0)
            shading = min(rayTraceShadow(sunVec, fragpos, noise), shading);
#endif
        shading = mix(0.0, shading, min(eyeBrightnessSmooth.y * recip + lightmap.y, 1.0));
    }

    diffuseSun = min(diffuseSun, shading) * lightmap.y;
    diffuseSun = max(diffuseSun, SSS);

    diffuseSun = mix(0.0, diffuseSun, shadowblend);

    diffuseSun *= lightmap.y * (1 - rainStrength * 0.9);

    // return SSS;
    return diffuseSun;
}
