#ifdef HARDWARE_SHADOWS
    #define SHADOWMAP sampler2DShadow
    #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r 
#else
    #define SHADOWMAP sampler2D
    #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
#endif

#if FILTER_MODE == 0
    #define GETSHADOW Shadow_DoShadowCompare
    #define KERNEL 1.0
#elif FILTER_MODE == 1
    #ifdef HARDWARE_SHADOWS
        #define GETSHADOW Shadow_DoShadowCompare
    #else
        #define GETSHADOW Shadow_DoBilinear_2x2
    #endif
    #define KERNEL 1.0
#elif FILTER_MODE == 2
    #define GETSHADOW Shadow_DoDither_2x2
    #define KERNEL 1.0
#elif FILTER_MODE == 3
    #define GETSHADOW Shadow_DoPCF
    #define KERNEL 4.0
#elif FILTER_MODE == 4
    #define GETSHADOW Shadow_DoPCFPoisson
    #define KERNEL 4.0
#elif FILTER_MODE == 5
    #define GETSHADOW Shadow_DoPCF
    #define KERNEL 8.0
#endif


uniform SHADOWMAP m_ShadowMap0;
uniform SHADOWMAP m_ShadowMap1;
uniform SHADOWMAP m_ShadowMap2;
uniform SHADOWMAP m_ShadowMap3;
#ifdef POINTLIGHT
uniform SHADOWMAP m_ShadowMap4;
uniform SHADOWMAP m_ShadowMap5;
#endif

#ifdef PSSM
uniform vec4 m_Splits;
#endif

uniform float m_ShadowIntensity;

const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
float shadowBorderScale = 1.0;

float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){
    vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
    return SHADOWCOMPARE(tex, coord);
}

float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){
    return SHADOWCOMPARE(tex, projCoord);
}

float Shadow_BorderCheck(vec2 coord){
    // Fastest, "hack" method (uses 4-5 instructions)
    vec4 t = vec4(coord.xy, 0.0, 1.0);
    t = step(t.wwxy, t.xyzz);
    return dot(t,t);
}

float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){
    float border = Shadow_BorderCheck(projCoord.xy);
    if (border > 0.0){
        return 1.0;
    }
    return Shadow_DoShadowCompare(tex,projCoord);
}

float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){
    float border = Shadow_BorderCheck(projCoord.xy);
    if (border > 0.0)
        return 1.0;
  

    float shadow = 0.0;
    vec2 o = mod(floor(gl_FragCoord.xy), 2.0);
    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5,  1.5) + o);
    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5,  1.5) + o);
    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o);
    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o);
    shadow *= 0.25 ;
    return shadow;
}

float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){
    float border = Shadow_BorderCheck(projCoord.xy);
    if (border > 0.0)
        return 1.0;
    vec4 gather = vec4(0.0);
    gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
    gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
    gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
    gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));

    vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
    vec2 mx = mix( gather.xz, gather.yw, f.x );
    return mix( mx.x, mx.y, f.y );
}

float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
    float shadow = 0.0;
    float border = Shadow_BorderCheck(projCoord.xy);
    if (border > 0.0)
        return 1.0;
    float bound = KERNEL * 0.5 - 0.5;
    bound *= PCFEDGE;
    for (float y = -bound; y <= bound; y += PCFEDGE){
        for (float x = -bound; x <= bound; x += PCFEDGE){
            shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) +
                            border,
                            0.0, 1.0);
        }
    }

    shadow = shadow / (KERNEL * KERNEL);
    return shadow;
}


//12 tap poisson disk
    const vec2 poissonDisk0 =  vec2(-0.1711046, -0.425016);
    const vec2 poissonDisk1 =  vec2(-0.7829809, 0.2162201);
    const vec2 poissonDisk2 =  vec2(-0.2380269, -0.8835521);
    const vec2 poissonDisk3 =  vec2(0.4198045, 0.1687819);
    const vec2 poissonDisk4 =  vec2(-0.684418, -0.3186957);
    const vec2 poissonDisk5 =  vec2(0.6026866, -0.2587841);
    const vec2 poissonDisk6 =  vec2(-0.2412762, 0.3913516);
    const vec2 poissonDisk7 =  vec2(0.4720655, -0.7664126);
    const vec2 poissonDisk8 =  vec2(0.9571564, 0.2680693);
    const vec2 poissonDisk9 =  vec2(-0.5238616, 0.802707);
    const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
    const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);

float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){   
    float shadow = 0.0;
    float border = Shadow_BorderCheck(projCoord.xy);
    if (border > 0.0)
        return 1.0;

    vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale);        
    
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize);
     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize);

    shadow = shadow * 0.08333333333;//this is divided by 12
    return shadow;
}


#ifdef POINTLIGHT       
    float getPointLightShadows(vec4 worldPos,vec3 lightPos,
                           SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5,
                           vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){
        float shadow = 1.0;
        vec3 vect = worldPos.xyz - lightPos;
        vec3 absv= abs(vect);
        float maxComp = max(absv.x,max(absv.y,absv.z));
        if(maxComp == absv.y){
           if(vect.y < 0.0){
               shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w);
           }else{
               shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
           }
        }else if(maxComp == absv.z){
           if(vect.z < 0.0){
               shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
           }else{
               shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
           }
        }else if(maxComp == absv.x){
           if(vect.x < 0.0){
               shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
           }else{
               shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
           }
        }  
        return shadow;
    }
#else
 #ifdef PSSM
    float getDirectionalLightShadows(vec4 splits,float shadowPosition,
                                    SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,
                                    vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){    
        float shadow = 1.0;   
        if(shadowPosition < splits.x){
            shadow = GETSHADOW(shadowMap0, projCoord0 );   
        }else if( shadowPosition <  splits.y){
            shadowBorderScale = 0.5;
            shadow = GETSHADOW(shadowMap1, projCoord1);  
        }else if( shadowPosition <  splits.z){
            shadowBorderScale = 0.25;
            shadow = GETSHADOW(shadowMap2, projCoord2); 
        }else if( shadowPosition <  splits.w){
            shadowBorderScale = 0.125;
            shadow = GETSHADOW(shadowMap3, projCoord3); 
        }
        return shadow;
    }
 #else
    float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){
        float shadow = 1.0;         
        projCoord /= projCoord.w;
        shadow = GETSHADOW(shadowMap, projCoord);
        
        //a small falloff to make the shadow blend nicely into the not lighten
        //we translate the texture coordinate value to a -1,1 range so the length 
        //of the texture coordinate vector is actually the radius of the lighten area on the ground
        projCoord = projCoord * 2.0 - 1.0;
        float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
        return mix(shadow,1.0,clamp(fallOff,0.0,1.0));

    }
 #endif
#endif
