
#if defined RSM

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

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 rayTraceShadow3(vec3 dir, vec3 position, float dither)
{

    float quality = 8;
    vec3 clipPosition = toClipSpace3(position);
    float rayLength = ((position.z + dir.z * far * sqrt(3.0)) > -near) ? (-near - position.z) / dir.z : far * sqrt(3.0);

    vec3 direction = toClipSpace3(position + dir * rayLength) - clipPosition;                            // convert to clip space
    direction.xyz = direction.xyz / max(abs(direction.x) / texelSize.x, abs(direction.y) / texelSize.y); // fixed step size

    vec3 stepv = direction * (dither + 0.1);

    vec3 spos = clipPosition + stepv;
    spos.xy += offsets[framemod8] * texelSize * 0.5;

    for (int i = 0; i < int(quality); i++)
    {
        spos += stepv * dither;

        float sp = texture2D(depthtex0, spos.xy).x;
        if (sp < spos.z)
        {

            float dist = abs(ld(sp) - ld(spos.z)) / ld(spos.z);

            if (dist < 0.01)
                return clamp(1 - exp2(position.z / 16.0) * 2.0, 0, 1);
        }
    }
    return 1.0;
}
float invld(float lindepth)
{
    return -((2.0 * near / lindepth) - far - near) / (far - near);
}
vec3 nvec3(vec4 pos)
{
    return pos.xyz / pos.w;
}

vec4 nvec4(vec3 pos)
{
    return vec4(pos.xyz, 1.0);
}

float rayTraceShadow2(float NdotL, vec3 sundir, vec3 fragpos, float dither)
{

    float stepSize = 16;
    int maxSteps = 8;
    vec3 clipPosition = nvec3(gbufferProjection * nvec4(fragpos)) * 0.5 + 0.5;
    float rayLength = ((fragpos.z + sundir.z * 1.73205080757 * far) > -1.73205080757 * near) ? (-1.73205080757 * near - fragpos.z) / sundir.z : 1.73205080757 * far;

    vec3 end = toClipSpace3(fragpos + sundir * rayLength);
    vec3 direction = end - clipPosition;

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

    vec3 maxLengths = (step(0., direction) - clipPosition) / direction;
    float mult = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = direction / len;

    int iterations = min(int(min(len, mult * len) - 2), maxSteps);

    vec3 spos = clipPosition + stepv / stepSize;

    for (int i = 0; i < int(iterations); i++)
    {
        spos += stepv * dither;
        float sp = texture2D(depthtex0, spos.xy).x;

        if (sp < spos.z + 0.00000001)
        {
            float dist = abs(ld(sp) - ld(spos.z)) / ld(spos.z);

            if (dist < 0.05)
                return clamp(1 - NdotL * 10.0, 0, 1);
        }
    }
    return 1.0;
}
float rayTraceShadow(float NdotL, vec3 sundir, vec3 fragpos, float dither)
{

    float stepSize = 8;
    int maxSteps = 16;
    vec3 clipPosition = nvec3(gbufferProjection * nvec4(fragpos)) * 0.5 + 0.5;
    float rayLength = ((fragpos.z + sundir.z * 1.73205080757 * far) > -1.73205080757 * near) ? (-1.73205080757 * near - fragpos.z) / sundir.z : 1.73205080757 * far;

    vec3 end = toClipSpace3(fragpos + sundir * rayLength);
    vec3 direction = end - clipPosition;

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

    vec3 maxLengths = (step(0., direction) - clipPosition) / direction;
    float mult = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = direction / len;

    vec3 spos = clipPosition + stepv / stepSize;
    spos.xy += offsets[framemod8] * texelSize * 0.5;
    for (int i = 0; i < int(maxSteps); i++)
    {
        spos += stepv * dither;
        float sp = texture2D(depthtex0, spos.xy).x;

        if (sp < spos.z)
        {
            float dist = abs(ld(sp) - ld(spos.z)) / ld(spos.z);

            if (dist < 0.005)
                return clamp(1 - (NdotL * 32.0), 0, 1);
        }
    }
    return 1.0;
}

float rayTraceShadowAlt(float NdotL, vec3 sundir, vec3 fragpos, float dither, bool sss)
{
    float stepSize = 3;

    if (sss)
        stepSize = mix(stepSize, 8, dither);
    int maxSteps = 32;
    vec3 clipPosition = nvec3(gbufferProjection * nvec4(fragpos)) * 0.5 + 0.5;
    float rayLength = ((fragpos.z + sundir.z * 1.73205080757 * far) > -1.73205080757 * near) ? (-1.73205080757 * near - fragpos.z) / sundir.z : 1.73205080757 * far;

    vec3 end = toClipSpace3(fragpos + sundir * rayLength);
    vec3 direction = end - clipPosition;

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

    vec3 maxLengths = (step(0., direction) - clipPosition) / direction;
    float mult = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = direction / len;

    int iterations = min(int(min(len, mult * len) - 2), maxSteps);

    vec3 spos = clipPosition + (stepv * dither) / stepSize;
    spos.xy += offsets[framemod8] * texelSize * 0.5;

    for (int i = 0; i < int(iterations); i++)
    {
        spos += stepv;
        float sp = texture2D(depthtex0, spos.xy).x;

        if (sp < spos.z + 0.00000001)
        {
            float dist = abs(ld(sp) - ld(spos.z)) / ld(spos.z);

            if (dist < 0.01)
                return 0.0;
        }
    }
    return 1.0;
}
float rayTraceShadow4(vec3 dir, vec3 position, float dither)
{
    float depth = texture2D(depthtex0, texcoord.xy).x;

    float stepSize = clamp(ld(depth) * 10.0, 15, 200);
    stepSize = clamp(90 * dither, 15, 200);
    int maxSteps = int(clamp(invld(depth) * 10.0, 15, 50));
    dither *= 1.0 - pow(depth, 256);
    maxSteps = 90;
    vec3 clipPosition = nvec3(gbufferProjection * nvec4(position)) * 0.5 + 0.5;
    float rayLength = ((position.z + dir.z * 1.73205080757 * far) > -1.73205080757 * near) ? (-1.73205080757 * near - position.z) / dir.z : 1.73205080757 * far;

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

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

    vec3 maxLengths = (step(0., direction) - clipPosition) / direction;
    float mult = min(min(maxLengths.x, maxLengths.y), maxLengths.z);
    vec3 stepv = direction / len;

    int iterations = min(int(min(len, mult * len) - 2), maxSteps);

    vec3 spos = clipPosition + stepv / stepSize;

    for (int i = 0; i < int(iterations); i++)
    {
        spos += stepv * dither;
        float sp = texture2D(depthtex0, spos.xy).x;

        if (sp < spos.z + 0.00000001)
        {
            float dist = abs(ld(sp) - ld(spos.z)) / ld(spos.z);

            if (dist < 0.05)
                return 1 - exp2(position.z / 16.0) * 2.0;
        }
    }
    return 1.0;
}
float computeSSS(float sssAmount, vec3 albedo, vec3 normal, vec3 lightPos, float avgDepth, float diffuseSun)
{
    // Adjust depth value to simulate realistic scattering (input avgDepth is derived from shadow mapping)
    avgDepth = sqrt(avgDepth);

    float lum = luma(albedo);
    vec3 diff = albedo - lum;

    // Calculate extinction factor based on albedo
    // float extinction = 1.0 - luma(albedo) * 0.85;
    float extinction = 1.0 - luma(diff) * 0.85;

    // Calculate the angle factor between the normal and light position
    float angleFactor = dot(normalize(normal), lightPos);

    // Calculate extinction factor based on depth and extinction
    float extinctionFactor = exp(-avgDepth * 11.0 * extinction);

    // Base subsurface scattering effect calculation
    float sssBase = extinctionFactor + 3.0 * pow(extinctionFactor, 1.0 / 3.0);

    // Calculate the phase factor using the angle factor
    float phaseFactor = phaseg(angleFactor, 0.75);

    // Calculate scattering using the phase factor
    float scattering = clamp((0.7 + 0.3 * PI * phaseFactor) * 1.5 / 3.0, 0.0, 1.0);

    // Calculate final subsurface scattering effect
    float sssValue = clamp(sssBase * scattering, 0.0, 1.0);

    return sssValue;
}

#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;
}
vec2 tapLocation(int sampleNumber, int nb, float nbRot, float jitter, float distort)
{
    float alpha = (sampleNumber + jitter) / nb;
    float angle = jitter * 6.28 + alpha * nbRot * 6.28;

    float sin_v, cos_v;

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

    return vec2(cos_v, sin_v) * sqrt(alpha);
}
vec2 tapLocation(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;
}
vec2 sincos(float x)
{
    return vec2(sin(x), cos(x));
}

const float goldenAngle = 137.5077640500378546463487;

vec2 circlemap(float i)
{
    float r = sqrt(i);
    float theta = i * goldenAngle;
    return vec2(cos(theta), sin(theta)) * r;
}

vec3 calculateAlbedo3(
    vec3 ProjShadowPos, // (still unused, but kept for signature)
    float distortFac,   // (kept for signature)
    float ambientA,     // (kept for signature)
    vec2 lightmap       // (kept for signature)
)
{
    ivec2 pix = ivec2(gl_FragCoord.xy);
    vec4 basic = texelFetch(colortex2, pix, 0);
    vec3 normal = decodeNormal(basic.rg);

    vec2 texcoord = gl_FragCoord.xy * texelSize;
    float noise = blueNoise(gl_FragCoord.xy);

    float Depth = texture(depthtex0, texcoord).x;
    vec4 fragpos = gbufferProjectionInverse *
                   (vec4(texcoord - vec2(texelSize) * 0.5, Depth, 1.0) * 2.0 - 1.0);
    fragpos /= fragpos.w;

    float d2 = dot(fragpos.xyz, fragpos.xyz);
    float giDistanceMask = 1.0 / (1.0 + d2 * 0.0001);

    vec4 worldposition = gbufferModelViewInverse * vec4(fragpos);
    worldposition = shadowModelView * worldposition;
    vec3 wpos2 = worldposition.xyz;

    worldposition = shadowProjection * worldposition;
    worldposition /= worldposition.w;
    worldposition = worldposition * 0.5 + 0.5;

    vec3 projNormal = mat3(shadowModelView) * normal;

    const int steps = 4;
    const float rSteps = 1.0 / steps;
    vec3 gi = vec3(0.0);
    float offsetSize = clamp(length(fragpos.xyz) * 0.25, 32.0, 160.0);

    vec3 GIprojectedShadowPosition2 = ProjShadowPos;
    float totalWeight = 0.0;

    for (int i = 0; i < steps; ++i)
    {
        vec2 offsetUnit = circlemap((float(i) + noise) * rSteps);
        vec2 offset = offsetUnit * offsetSize / shadowMapResolution;

        vec2 TC = ((worldposition.xy + offset * 0.95) * 2.0 - 1.0) * 0.5 + 0.5;
        vec2 GIprojectedShadowPosition3 = (GIprojectedShadowPosition2.xy * shadowMapResolution) + (offset * shadowMapResolution);

        float dist2 = 1.0; // will be set later based on sample position
        float lod = 0.0;

        // Estimate LOD based on projected distance (fallback value)
        {
            vec2 offsetUV = offset;
            float offsetLength = length(offsetUV * shadowMapResolution);
            float estDist = offsetLength * offsetSize * 0.5;
            lod = clamp(log2(estDist + 1.0), 0.0, 4.0); // MAX_LOD = 4.0
        }

        vec3 flux = textureLod(shadowcolor0, GIprojectedShadowPosition3 / shadowMapResolution, 2).rgb;
        float sampleDepth = textureLod(shadowtex0, GIprojectedShadowPosition3 / shadowMapResolution, 2).r + 1.0 / 2000.0;

        vec3 shadowNormal = textureLod(shadowcolor1, GIprojectedShadowPosition2.xy / shadowMapResolution, 4).rgb * 2.0 - 1.0;

        vec3 S1 = vec3((TC * 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, 7.0, 1.0));
        vec3 samplePos = shadowPos.xyz;
        vec3 V = samplePos - wpos2;

        dist2 = max(dot(V, V), 0.01); // safe geometry term
        vec3 L = normalize(V);

        float NdotL = clamp(dot(projNormal, L), 0.0, 1.0);
        float NdotV = clamp(dot(shadowNormal, -L), 0.0, 1.0);
        float geometry = 1.0 / dist2;
        float Scoef = 1.0 / (1.0 + dist2);
        float visibility = step(0.0, dot(projNormal, L));
        float contribution = visibility * NdotL * NdotV * geometry * Scoef;

        vec3 sampleContribution = flux * contribution;
        sampleContribution = clamp(sampleContribution, vec3(0.0), vec3(1.0)); // safety clamp
        gi += sampleContribution;
        totalWeight += contribution;
    }

    const float energyScale = 1.0;
    gi /= max(totalWeight, 1e-4);
    return gi * giDistanceMask * energyScale;
}

    #if defined RSM

const bool shadowcolor0Mipmap = true;
    #endif
vec3 calculateAlbedo(vec3 ProjShadowPos, float distortFac, float ambientA, vec2 lightmap)
{

    #ifndef GI_LOD

    const vec2 shadowOffsets[4] = vec2[4](
        vec2(0.5303, 0.5303),
        vec2(-0.6250, 0.0),
        vec2(0.3536, -0.3536),
        vec2(0.0, 0.3750));

    float ij = R2_dither(gl_FragCoord.xy);
    float angle = ij * 6.28318530718; // 2 * PI
    float cosA = cos(angle);
    float sinA = sin(angle);
    float sc = 0.05 * cosA; // Precomputed scale * cosine
    float ss = 0.05 * sinA; // Precomputed scale * sine
    vec2 basePos = ProjShadowPos.xy;

    vec3 sAlbedo = vec3(0.0);
    sAlbedo += texture(shadowcolor0, basePos + vec2(sc * shadowOffsets[0].x - ss * shadowOffsets[0].y, ss * shadowOffsets[0].x + sc * shadowOffsets[0].y)).rgb;
    sAlbedo += texture(shadowcolor0, basePos + vec2(sc * shadowOffsets[1].x - ss * shadowOffsets[1].y, ss * shadowOffsets[1].x + sc * shadowOffsets[1].y)).rgb;
    sAlbedo += texture(shadowcolor0, basePos + vec2(sc * shadowOffsets[2].x - ss * shadowOffsets[2].y, ss * shadowOffsets[2].x + sc * shadowOffsets[2].y)).rgb;
    sAlbedo += texture(shadowcolor0, basePos + vec2(sc * shadowOffsets[3].x - ss * shadowOffsets[3].y, ss * shadowOffsets[3].x + sc * shadowOffsets[3].y)).rgb;

    sAlbedo *= 0.25;

    #else
    vec2 ij = vec2(InterleavedGradientNoise2(vec2(gl_FragCoord.xy)));

    vec2 coord = ProjShadowPos.xy + ij * (distortFac / shadowMapResolution);

    vec3 sAlbedo = texture(shadowcolor0, coord, 4).rgb;
    sAlbedo += texture(shadowcolor0, coord, 8).rgb;
    sAlbedo *= 0.5;
    #endif

    // sAlbedo = ((sAlbedo - luma(sAlbedo)) * mix(-0.5, 2.0, pow(lightmap.y, 2.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

float cloudShadow(vec3 eyePlayerPos, float noise)
{

    const int rayMarchSteps = 6;
    vec3 rPos = eyePlayerPos + cameraPosition;
#if CLOUD_STYLE == 2

    vec2 wind = windGenerator(cloudTime * 0.01);
#else
    vec2 wind = vec2(0.0);
#endif
    float cloudShadow = 0.0;
    float dither = 0.5;
    for (int i = 0; i < rayMarchSteps; i++)
    {

        vec3 cloudPos = rPos + sunPosWorld / abs(sunPosWorld.y) * (cloud_height + (dither + i) / rayMarchSteps * cloud_height_max - rPos.y);
#if CLOUD_STYLE == 1
        cloudShadow += getCloudDensity(cloudPos, 1, wind);
#else
        cloudShadow += getCloudDensity(cloudPos, 1, wind) * 10.0;
#endif
    }

    cloudShadow /= rayMarchSteps;
    cloudShadow = mix(1.0, exp(-cloudShadow * cloudDensity * cloud_height_max / rayMarchSteps), mix(0.9, 1.0, rainStrength));
    return clamp(cloudShadow, 0, 1);
}
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);
}

// Transform the position and normals into shadow space and compute the projected shadow position.
vec3 computeProjectedShadowPos(vec3 p3, vec3 normals, mat3 shadowMV3, vec3 shadowProjDiag, vec3 shadowProjTrans)
{
    vec3 shadowMV_P3 = shadowMV3 * p3;
    vec3 shadowMV_Trans = shadowModelView[3].xyz;
    vec3 ProjShadowPos = shadowProjDiag * (shadowMV_P3 + shadowMV_Trans) + shadowProjTrans;
    vec3 shadowMV_Normals = shadowMV3 * normals;
    ProjShadowPos += shadowProjDiag * shadowMV_Normals * 0.01;

    return ProjShadowPos;
}