
  
/**
 * Author: Kaj Dijksta
 */
 
    attribute vec3 position;
    attribute vec2 uv;
	
    uniform mat4 viewProjection;

    varying vec2 v_textureCoord;

    void main(void) {
	
        v_textureCoord = uv;
		gl_Position = viewProjection * vec4(position, 1.0);
		
    }
	
	// #keplerEngine - Split


	precision highp float;

	uniform sampler2D positionSampler;
	uniform sampler2D shadowDepthSampler;
	uniform sampler2D shadowNoiseSampler;
	uniform sampler2D ambientOcclusionSampler;

	uniform float bias;
	uniform float minVariance;
	
	uniform vec3 lightPosition;
	uniform mat4 lightViewProjection;
	
	varying vec2 v_textureCoord;
	float DecodeFloatRGBA( vec4 rgba ) {
		return (rgba).x;
	
	  //return dot( rgba, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0) );
	}
	
	float poissonPCFmultitap(vec4 projCoords, float shadowDepth, vec2 uv)
	{
		const mediump float step = 1.0 - 1.0 / 8.0;
		const mediump float fScale = 0.025; // 0.025
		
		mediump float n = 0.0;
		
		mediump vec3 directions[8];
		float vSampleScale = 1.0 / 2048.0;
		
		
		directions[0] = normalize(vec3( 1.0, 1.0, 1.0))*fScale*(n+=step);
		directions[1] = normalize(vec3(-1.0,-1.0,-1.0))*fScale*(n+=step);
		directions[2] = normalize(vec3(-1.0,-1.0, 1.0))*fScale*(n+=step);
		directions[3] = normalize(vec3(-1.0, 1.0,-1.0))*fScale*(n+=step);
		directions[4] = normalize(vec3(-1.0, 1.0 ,1.0))*fScale*(n+=step);
		directions[5] = normalize(vec3( 1.0,-1.0,-1.0))*fScale*(n+=step);
		directions[6] = normalize(vec3( 1.0,-1.0, 1.0))*fScale*(n+=step);
		directions[7] = normalize(vec3( 1.0, 1.0,-1.0))*fScale*(n+=step);

		mediump vec3 randomSample = texture2D(shadowNoiseSampler, vec2(64.0, 64.0) * uv.xy / 4.0).xyz  * 2.0 - 1.0;
		
		

		float sum = 0.0;

		// for( int i = 0; i < 4; i++ ) {
		// 	vec3 sampler = reflect(directions[0], randomSample) * vSampleScale;	
			
			float pixelDepth = DecodeFloatRGBA( texture2D(shadowDepthSampler, projCoords.xy ) ) ; // + sampler.xy     + sampler.z

			if( pixelDepth + bias > shadowDepth) {
				sum += 1.0;
			} else {
				sum += 0.5;
			}
		// }

		return sum;
	}
	float shadow_sample(sampler2D depthMap, vec2 coord)
	{
		return ( texture2D(depthMap, coord.xy).x );
	}
	
	vec2 DoubleSampleRotated(sampler2D depthMap, vec4 p, vec4 rotMatr, vec4 kernel) {

		vec4 rotatedOff;

		rotatedOff = rotMatr.xyzw * kernel.xxww +
					 rotMatr.zwxy * kernel.yyzz;

		vec4 fetchPos = p.xyxy + rotatedOff;// + rotatedOff

		vec2 result;
		
		result.x = shadow_sample(depthMap, fetchPos.xy);
		result.y = shadow_sample(depthMap, fetchPos.zw);

		
		return result;
	}
	float PCF(sampler2D depthMap, vec4 p, vec2 randDirTC, float depth)
	{
	
		vec2 kernelRadius = vec2(4.0);
		
		vec4 irreg_kernel_2d[8];
		irreg_kernel_2d[0] = vec4(-0.556641,-0.037109,-0.654297, 0.111328); 
		irreg_kernel_2d[1] = vec4(0.173828,0.111328,0.064453, -0.359375); 
		irreg_kernel_2d[2] = vec4(0.001953,0.082031,-0.060547, 0.078125); 
		irreg_kernel_2d[3] = vec4(0.220703,-0.359375,-0.062500, 0.001953);
		irreg_kernel_2d[4] = vec4(0.242188,0.126953,-0.250000, -0.140625); 
		irreg_kernel_2d[5] = vec4(0.070313,-0.025391,0.148438, 0.082031); 
		irreg_kernel_2d[6] = vec4(-0.078125,0.013672,-0.314453, 0.013672);
		irreg_kernel_2d[7] = vec4(0.117188,-0.140625,-0.199219, 0.117188);

		vec2 vInvShadowMapWH = vec2(1.0 / 2048.0);
		
		const int kernelSize = 8;
		
		mediump float P_Z = depth; // p.z;

		vec4 p0 = vec4(p.xyz, 1.0);

		
		mediump vec2 rotScale = vec2(kernelRadius.y * 2.0);

		float shadowTest = 0.0;

		#define KERNEL_STEP_SIZE 2

			vec2 rotSample = 2.0 * texture2D(shadowDepthSampler, randDirTC.xy).xy - 1.0;
			rotSample.xy = normalize(rotSample.xy);
			rotSample.xy *= (kernelRadius.xy * vInvShadowMapWH.xy);

			vec4 rot = vec4(rotSample.x, -rotSample.y, rotSample.y, rotSample.x);

			const int kernelOffset = 0;

			for(int i=kernelOffset; i<kernelSize; i+=KERNEL_STEP_SIZE) // Loop over taps
			{

				mediump vec4 sampleDepth = vec4(0.0);
				vec4 irr =  irreg_kernel_2d[i+0];
				sampleDepth.xy = DoubleSampleRotated(depthMap, p0, rot, irr);
				sampleDepth.zw = DoubleSampleRotated(depthMap, p0, rot, irreg_kernel_2d[i+1]);
			
				mediump vec4 InShadow;
				InShadow.x = ( P_Z < sampleDepth.x + bias ) ? 1. : 0.0;
				InShadow.y = ( P_Z < sampleDepth.y + bias ) ? 1. : 0.0;
				InShadow.z = ( P_Z < sampleDepth.z + bias  ) ? 1. : 0.0;
				InShadow.w = ( P_Z < sampleDepth.w + bias  ) ? 1. : 0.0;

		
				const mediump float quality = 8.0; // 8 == high
				const mediump float fInvSamplNum = (1.0 / quality);
				
				shadowTest += dot(InShadow, vec4(fInvSamplNum));
			}

		return shadowTest;
	}
	float linestep(float min, float max, float value) {  
		return clamp((value - min) / (max - min), 0., 1.);
	}

	float reduceBleeding(float p_max, float amount) {
		return linestep(amount, 1.0, p_max);
	}

	float ChebyshevUpperBound(vec2 moments, float distance) {  
		if (distance <= moments.x)
			return 1.0;
	

		float variance = moments.y - (moments.x*moments.x);
		variance = max(variance, minVariance);

		float d = distance - moments.x;
		float p_max = variance / (variance + d*d);

		return reduceBleeding(p_max, bias);
	}
	
	float calculateShadowOcclusion( vec3 worldPosition, vec2 uv ) {

		vec4 projCoords = lightViewProjection *  vec4(worldPosition, 1.0) ;
		
		float shadowDepth = length( lightPosition - worldPosition ) ;

		projCoords.xy /= projCoords.w;
		projCoords = 0.5 * projCoords + 0.5;

		vec4 moments = texture2D( shadowDepthSampler, projCoords.xy );
		mediump vec2 randomSample = texture2D(shadowNoiseSampler, vec2(1024.) * uv / 64.0).xy  * 2.0 - 1.0;
			
		float outFrustum = 0.0;
	
		if(projCoords.x <  0.0 || projCoords.x > 1.0) {
			return 0.0;
		}
		
		if(projCoords.y <  0.0 || projCoords.y > 1.0) {
			return 0.0;
		}
		
		//if(projCoords.z <  1.0) {
		//	return 0.0;
		//}	
			
		//return ChebyshevUpperBound(moments.xy, shadowDepth);
			//return poissonPCFmultitap(projCoords, shadowDepth, uv);
		return PCF( shadowDepthSampler, projCoords, randomSample, shadowDepth );
	}
	
	
	void main() {
		
		vec3 position = texture2D( positionSampler, v_textureCoord ).xyz;

		float shadow = calculateShadowOcclusion( position, v_textureCoord );
		float ambientOcclusion = texture2D( ambientOcclusionSampler, v_textureCoord ).x;
		
		
		gl_FragColor = vec4(ambientOcclusion, shadow, 0.0, 1.0);

		
	}

	