uniform sampler2DShadow shadowtex0;

#ifdef COLORED_SHADOWS
uniform sampler2DShadow shadowtex1;
uniform sampler2D shadowcolor0;
#endif

vec2 shadowOffsets[8] = vec2[8](
   vec2(0.2921473492144121, 0.03798942536906266),
   vec2(-0.27714274097351554, 0.3304853027892154),
   vec2(0.09101981507673855, -0.5188871157785563),
   vec2(0.44459182774878003, 0.5629069824170247),
   vec2(-0.6963877647721594, -0.09264703741542105),
   vec2(0.7417522811565185, -0.4070419658858473),
   vec2(-0.191856808948964, 0.9084732299066597),
   vec2(-0.40412395850181015, -0.8212788214021378)
);

float biasDistribution[10] = float[10](
    0.0, 0.057, 0.118, 0.184, 0.255, 0.333, 0.423, 0.529, 0.667, 1.0
);

vec2 offsetDist(float x, float s) {
	float n = fract(x * 1.414) * PI;
    return vec2(cos(n), sin(n)) * 1.4 * x / s;
}

vec3 DistortShadow(vec3 worldPos, float distortFactor) {
	worldPos.xy /= distortFactor;
	worldPos.z *= 0.2;
	return worldPos * 0.5 + 0.5;
}

float GetCurvedBias(int i, float dither) {
    return mix(biasDistribution[i], biasDistribution[i+1], dither);
}

vec3 SampleBasicShadow(vec3 shadowPos) {
    float shadow0 = shadow2D(shadowtex0, vec3(shadowPos.st, shadowPos.z)).x;

    vec3 shadowCol = vec3(0.0);

    #ifdef COLORED_SHADOWS
        if(shadow0 < 1.0){
            shadowCol = texture2D(shadowcolor0, shadowPos.st).rgb *
                        shadow2D(shadowtex1, vec3(shadowPos.st, shadowPos.z)).x;
        }
    #endif

    return clamp(shadowCol * (1.0 - shadow0) + shadow0, vec3(0.0), vec3(1.0));
}

vec3 SampleFilteredShadow(vec3 shadowPos, float offset, float biasStep) {

    float shadow0=0.0;

    float sz     =shadowPos.z;

    float dither =InterleavedGradientNoise();
    #if AA > 1
		float ditherAnimate = 1.61803398875 * mod(float(frameCounter), 3600.0);
              dither = fract(dither + ditherAnimate);
	#endif

    for(int i = 0; i < 8; i++){
        vec2 shadowOffset = shadowOffsets[i] * offset;
        shadow0 += shadow2D(shadowtex0, vec3(shadowPos.st + shadowOffset, shadowPos.z)).x;

        if (biasStep > 0.0) shadowPos.z = sz - biasStep * GetCurvedBias(i, dither);
    }
    shadow0 /= 8.0;

    vec3 shadowCol = vec3(0.0);

    #ifdef COLORED_SHADOWS
        if(shadow0 < 1.0){
            for(int i = 0; i < 8; i++){
                vec2 shadowOffset = shadowOffsets[i] * offset;
                shadowCol += texture2D(shadowcolor0, shadowPos.st + shadowOffset).rgb *
                            shadow2D(shadowtex1, vec3(shadowPos.st + shadowOffset, shadowPos.z)).x;

                if (biasStep > 0.0) shadowPos.z = sz - biasStep * GetCurvedBias(i, dither);
            }
            shadowCol /= 8.0;
        }
    #endif

    return clamp(shadowCol * (1.0 - shadow0) + shadow0, vec3(0.0), vec3(1.0));
}

#ifdef TAA_SHADOW_FILTER

vec3 SampleTAAFilteredShadow(vec3 shadowPos, float offset, float biasStep) {

    float shadow0=0.0;

    float sz = shadowPos.z;

    float dither =InterleavedGradientNoise();
    #if AA > 1
		float ditherAnimate = 1.61803398875 * mod(float(frameCounter), 3600.0);
              dither = fract(dither + ditherAnimate);
	#endif

    for(int i = 0; i < 4; i++){
        vec2 shadowOffset = offsetDist(dither + i, 2) * offset;
        shadow0 += shadow2D(shadowtex0, vec3(shadowPos.st + shadowOffset, shadowPos.z)).x;
        shadow0 += shadow2D(shadowtex0, vec3(shadowPos.st - shadowOffset, shadowPos.z)).x;

        if (biasStep > 0.0) shadowPos.z = sz - biasStep * GetCurvedBias(i, dither);

    }
    shadow0 /= 4;

    vec3 shadowCol = vec3(0.0);

    #ifdef COLORED_SHADOWS
        if(shadow0 < 1.0){
            for(int i = 0; i < 4; i++){
                vec2 shadowOffset = offsetDist(dither + i, 2) * offset;
                shadowCol+=texture2D(shadowcolor0, shadowPos.st + shadowOffset).rgb *
                            shadow2D(shadowtex1, vec3(shadowPos.st + shadowOffset, shadowPos.z)).x;

                if (biasStep > 0.0) shadowPos.z = sz - biasStep * GetCurvedBias(i, dither);

            }
            shadowCol /= 4;
        }
    #endif

    return clamp(shadowCol * (1.0 - shadow0) + shadow0, vec3(0.0), vec3(1.0));
}

#endif

vec3 CausticsFilteredShadow(vec3 shadowPos, float offset, float biasStep) {

    float shadow0 = 0.0;

    float sz = shadowPos.z;

    float dither=InterleavedGradientNoise();
    #if AA > 1
		float ditherAnimate = 1.61803398875 * mod(float(frameCounter), 3600.0);
              dither = fract(dither + ditherAnimate);
	#endif

    for(int i = 0; i < 6; i++){
        vec2 shadowOffset = offsetDist(dither + i, 2) * offset;
        shadow0 += shadow2D(shadowtex0, vec3(shadowPos.st + shadowOffset, shadowPos.z)).x;
        shadow0 += shadow2D(shadowtex0, vec3(shadowPos.st - shadowOffset, shadowPos.z)).x;

        if (biasStep > 0.0) shadowPos.z = sz - biasStep * GetCurvedBias(i, dither);

    }
    shadow0 /= 6.0;

    vec3 shadowCol = vec3(0.0);

    #ifdef COLORED_SHADOWS
        if(shadow0 < 1.0){
            for(int i = 0; i < 6; i++){
                vec2 shadowOffset = offsetDist(dither + i, 2) * offset;
                shadowCol+=texture2D(shadowcolor0, shadowPos.st + shadowOffset).rgb *
                            shadow2D(shadowtex1, vec3(shadowPos.st + shadowOffset, shadowPos.z)).x;

                if (biasStep > 0.0) shadowPos.z = sz - biasStep * GetCurvedBias(i, dither);

            }
            shadowCol /= 6.0;
        }
    #endif

    return clamp(shadowCol * (1.0 - shadow0) + shadow0, vec3(0.0), vec3(1.0));
}


vec3 GetShadow(vec3 worldPos, float NoL, float subsurface, float skylight) {
    vec3 shadow=vec3(0.0);

    vec3 shadowPos=WorldToShadow(worldPos);

    float dither=InterleavedGradientNoise();
    #if AA > 1
		float ditherAnimate = 1.61803398875 * mod(float(frameCounter), 3600.0);
              dither = fract(dither + ditherAnimate);
	#endif

    float distb        =sqrt(dot(shadowPos.xy, shadowPos.xy));
    float distortFactor=distb * shadowMapBias + (1.0 - shadowMapBias);
          shadowPos    =DistortShadow(shadowPos, distortFactor);

    bool doShadow = shadowPos.x > 0.0 && shadowPos.x < 1.0 &&
                    shadowPos.y > 0.0 && shadowPos.y < 1.0;

    #ifdef OVERWORLD
        if (isEyeInWater == 0){

            doShadow = doShadow && skylight > 0.001;
        }
    #endif

    if (!doShadow){

        return vec3(skylight);
    }

    float biasFactor = sqrt(1.0 - pow2(NoL)) / NoL;
    float distortBias = distortFactor * shadowDistance / 256.0;
          distortBias *= 8.0 * distortBias;
    float distanceBias = sqrt(dot(worldPos.xyz, worldPos.xyz)) * 0.005;


    float bias = (distortBias * biasFactor + distanceBias + 0.05) / shadowMapResolution;
    float offset = 1.0 / shadowMapResolution;

    if (subsurface > 0.0) {
        bias = 0.0002;

        #if defined SHADOW_FILTER
        bias *= mix(subsurface, 1.0, NoL);
        #endif

        offset = 0.0007;
    }

    float biasStep = 0.001 * subsurface * (1.0 - NoL);

    shadowPos.z -= bias;

    #if (defined OVERWORLD  && defined WATER_CAUSTICS)
        if(isEyeInWater == 1)
        {
            shadow=CausticsFilteredShadow(shadowPos, offset, biasStep);
        }
        else
    #endif

        #ifdef SHADOW_FILTER
            #ifdef TAA_SHADOW_FILTER
                shadow=SampleTAAFilteredShadow(shadowPos, offset, biasStep);
            #else
                shadow=SampleFilteredShadow(shadowPos, offset, biasStep);
            #endif
        #else
            shadow=SampleBasicShadow(shadowPos);
        #endif

    return shadow;
}
