#version 400

uniform sampler2D normalMap;
uniform sampler2D depthMap;
uniform sampler2D noiseMap;

uniform mat4 projectionMatrix;
uniform vec2 planes;
uniform vec2 noiseScale;

uniform int numSamples;
uniform vec3 sampleKernel[50];
uniform float radius;

smooth in vec2 VS_TexCoord;
smooth in vec3 VS_ViewVector;

layout(location = 0) out vec4 occlusionMap;

vec3 spheremapDec(vec2 enc)
{
  vec3 ret = vec3(enc * 2.0f, 0.0f) + vec3(-1.0f, -1.0f, 1.0f);
  float l = dot(ret.xy, -ret.xy) + 1.0f;
  ret.z = l;
  ret.xy *= sqrt(l);
  return ret.xyz * 2.0f + vec3(0.0f, 0.0f, -1.0f);
}

void main()
{
	float depth = texture(depthMap, VS_TexCoord).x;
	if(depth == 1.0)
		occlusionMap = vec4(1.0);
	else
	{	
		//get normal
		vec3 normal = spheremapDec(texture(normalMap, VS_TexCoord).xy);

		//find fragment position
		vec3 viewVector = normalize(VS_ViewVector);
		vec3 fragment_pos;
		fragment_pos.z  = -planes.y / (planes.x + depth);
		fragment_pos.xy = viewVector.xy / viewVector.z * fragment_pos.z;

		vec3 rvec = texture(noiseMap, VS_TexCoord * noiseScale).xyz * 2.0 - 1.0;
		vec3 tangent = normalize(rvec - normal * dot(rvec, normal));
		vec3 bitangent = cross(normal, tangent);

		mat3 tbn = mat3(tangent, bitangent, normal);


		float occlusion = 0.0;
		for (int i = 0; i < numSamples; ++i) 
		{
			//get sample position:
			vec3 sample_pos = tbn * sampleKernel[i];
			sample_pos = sample_pos * radius + fragment_pos;
		
			//project sample position:
			vec4 offset = vec4(sample_pos, 1.0);
			offset = projectionMatrix * offset;
			offset.xy /= offset.w;
			offset.xy = offset.xy * 0.5 + 0.5;
		
			//get linear sample depth:
			float depthMapDepth = texture(depthMap, offset.xy).x;
			depthMapDepth = -planes.y / (planes.x + depthMapDepth);

			//range check & accumulate:
			occlusion += abs(fragment_pos.z - depthMapDepth) > radius || depthMapDepth < sample_pos.z + 0.001 ? 0.0 : 1.0;
		}  

		occlusion = 1.0 - (occlusion / numSamples);
		occlusionMap = vec4(occlusion);
	}
}
