vec3 GetShadowPos(vec3 playerPos) {
    vec3 shadowPos = PlayerToShadow(playerPos);
    float distb = sqrt(shadowPos.x * shadowPos.x + shadowPos.y * shadowPos.y);
    float distortFactor = distb * shadowMapBias + (1.0 - shadowMapBias);
    shadowPos.xy /= distortFactor;
    shadowPos.z *= 0.2;
    return shadowPos * 0.5 + 0.5;
}

#ifndef COMPOSITE1
    vec3 SampleShadow(vec3 shadowPos, float colorMult, float colorPow) {
        float shadow0 = shadow2D(shadowtex0, vec3(shadowPos.st, shadowPos.z)).x;

        vec3 shadowcol = vec3(0.0);
        if (shadow0 < 1.0) {
            float shadow1 = shadow2D(shadowtex1, vec3(shadowPos.st, shadowPos.z)).x;
            float colorWeight = smoothstep(0.985, 0.9999, shadow1);
            if (colorWeight > 0.0) {
                shadowcol = texture2D(shadowcolor0, shadowPos.st).rgb * shadow1 * colorWeight;
                shadowcol *= colorMult;
                shadowcol = pow(shadowcol, vec3(colorPow));
            }
        }

        return shadowcol * (1.0 - shadow0) + shadow0;
    }

    float InterleavedGradientNoiseForShadows() {
        float n = 52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y);
        #if !defined GBUFFERS_ENTITIES && !defined GBUFFERS_HAND && !defined GBUFFERS_TEXTURED && defined TAA
            return fract(n + goldenRatio * mod(float(frameCounter), 3600.0));
        #else
            return fract(n);
        #endif
    }

    // ================================================================
    //  PCF SOFT SHADOWS - Procedural Poisson Disk
    // ================================================================
    // Poisson disk points are generated procedurally using the
    // golden-ratio spiral method. No const arrays needed (GLSL 1.30
    // compatible). The disk is rotated per-frame for TAA convergence.
    // ================================================================

    // Procedural Poisson disk: golden-ratio spiral with per-ring offset
    // Gives well-distributed points inside a unit circle
    vec2 PoissonDiskSample(int index, float rotation) {
        float phi = rotation + float(index) * 2.39996323;
        float r = sqrt(float(index) + 0.5) * 0.39894228;
        return vec2(cos(phi), sin(phi)) * r;
    }

    // ================================================================
    //  SHADOW QUALITY 0: Basic PCF (8 samples, opaque-only)
    // ================================================================
    vec3 SampleBasicFilteredShadow(vec3 shadowPos, float offset) {
        float shadow = 0.0;
        float rotation = 52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y);
        rotation = fract(rotation + 0.618 * float(frameCounter)) * 6.2831853;

        for (int i = 0; i < 8; i++) {
            vec2 sampleOffset = PoissonDiskSample(i, rotation) * offset;
            shadow += shadow2D(shadowtex0, vec3(shadowPos.st + sampleOffset, shadowPos.z)).x;
        }

        return vec3(shadow / 8.0);
    }

    // ================================================================
    //  SHADOW QUALITY 1: Standard PCF (12 samples, colored shadows)
    // ================================================================
    vec3 SampleFilteredShadow(vec3 shadowPos, float offset, float colorMult, float colorPow) {
        vec3 shadow = vec3(0.0);
        float rotation = 52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y);
        rotation = fract(rotation + 0.618 * float(frameCounter)) * 6.2831853;

        for (int i = 0; i < 12; i++) {
            vec2 sampleOffset = PoissonDiskSample(i, rotation) * offset;
            shadow += SampleShadow(vec3(shadowPos.st + sampleOffset, shadowPos.z), colorMult, colorPow);
        }

        return shadow / 12.0;
    }

    // ================================================================
    //  SHADOW QUALITY 2+: TAA-filtered PCF (variable samples)
    // ================================================================
    vec3 SampleTAAFilteredShadow(vec3 shadowPos, float offset, int shadowSamples, bool leaves, float colorMult, float colorPow) {
        vec3 shadow = vec3(0.0);
        float gradientNoise = InterleavedGradientNoiseForShadows();

        #if !defined GBUFFERS_ENTITIES && !defined GBUFFERS_HAND && !defined GBUFFERS_TEXTURED
            offset *= 1.3875;
        #else
            offset *= 0.69375;
        #endif

        float rotation = gradientNoise * 6.2831853;

        float shadowPosZM = shadowPos.z;
        for (int i = 0; i < shadowSamples; i++) {
            vec2 sampleOffset = PoissonDiskSample(i, rotation) * offset;
            if (leaves) shadowPosZM = shadowPos.z - 0.12 * offset * float(i) / float(shadowSamples);
            shadow += SampleShadow(vec3(shadowPos.st + sampleOffset, shadowPosZM), colorMult, colorPow);
        }

        shadow /= float(shadowSamples);

        return shadow;
    }

    vec3 GetShadow(vec3 shadowPos, float lightmapY, float offset, int shadowSamples, bool leaves, vec3 playerPos) {
        #if SHADOW_QUALITY > 0
            #if ENTITY_SHADOW <= 1 && defined GBUFFERS_BLOCK
                offset *= 4.0;
            #else
                #ifdef OVERWORLD
                    offset *= 1.0 + rainFactor2
                        #ifdef SUN_MOON_DURING_RAIN
                            * 2.0
                        #else
                            * 4.0
                        #endif
                    ;
                #else
                    offset *= 3.0;
                #endif
            #endif
        #endif

        float lightmapY2 = pow2(lightmapY);
        float lightmapY4 = pow2(lightmapY2);

        float colorMult = 2.5 + 5.5 * pow1_5(lightmapY) + 2.0 * lightmapY2;
        float colorPow = mix(1.5 + 0.5 * float(isEyeInWater == 0), 0.5, lightmapY4);

        #if defined DISTANT_HORIZONS || defined VOXY
            float horizontalDistance = length(playerPos.xz);
            float verticalDistance = abs(playerPos.y);
            float fadeEndistance = max(horizontalDistance, verticalDistance);
            #ifdef DISTANT_HORIZONS
                float farM = far * 0.8;
            #else
                float farM = far * 0.95;
            #endif
            float fade = smoothstep(far * 0.4, far * 0.9, fadeEndistance);

            colorMult *= mix(1.0, 0.1 + 0.99 * pow2(pow2(lightmapY4)), fade);
            colorPow *= mix(1.0, 1.0 - 0.99 * lightmapY4, fade);
        #endif

        #if SHADOW_QUALITY >= 2 && defined TAA
            vec3 shadow = SampleTAAFilteredShadow(shadowPos, offset, shadowSamples, leaves, colorMult, colorPow);
        #elif SHADOW_QUALITY >= 1
            vec3 shadow = SampleFilteredShadow(shadowPos, offset, colorMult, colorPow);
        #elif SHADOW_QUALITY == 0
            vec3 shadow = SampleBasicFilteredShadow(shadowPos, offset);
        #endif

        return shadow;
    }
#endif
