//Vertex Shader for shadow map and normal mapping - file "texture_shadownorm.vert"

#version 330 

precision highp float;

#define SAMPLES_COUNT 4

uniform sampler2D in_texture;
uniform sampler2DShadow in_shadowmap;
uniform sampler2D normal_map;
uniform samplerCube shadow_cubemap;
uniform sampler3D shadow_offsets;
uniform int soft_shadows;
uniform vec4 ws_odsm_light_position; //needed for odsm

in vec2 fragment_texcoord;
in vec3 texture_light;
in vec3 texture_eye;
in vec3 texture_light_odsm;
in vec4 ex_lightspace_position;
in vec4 worldspace_pos;

out vec4 out_color;

//consts for odsm
const int offset_size = 8;
const int big_offset_size = 64;
const float eps = 0.15; //small offset
const float weight = 0.25; //shadow offset max distance
const float odsm_weight = 0.25; //odsm shadow offset max distance
const float big_odsm_weight = 0.05; //odsm shadow offset max distance
const float light_farplane = 60;
const float light_farplane_squared = pow(light_farplane, 2);

vec3 cm_lookup_vec = worldspace_pos.xyz - ws_odsm_light_position.xyz;

//return odsm shadow value and distance to odsm light 
vec2 odsm_shadow_value() {
		//--------------------------
		// accumulate shadow samples
		//--------------------------
		//float shadow = 1.0f;
		//do testing samples first
		float r = 0;
		float rstep = 1.0f / offset_size;
		float odsm_shadow = 0;
		float current_dist_to_light;
		for (int i=0; i< offset_size; i++) {
			r += rstep;
			//vec3 offset_lookup_vec = vec3(gl_FragCoord.xy / vec2(800,600), r);
			vec3 offset_lookup_vec = vec3(0, 0, r);
			vec3 offset = texture(shadow_offsets, offset_lookup_vec).rgb;

			//calculate vector from surface point to light pos
			//(both in worldspace)
			// read depth val from shadowmap
			vec3 sample_lookup_vec = cm_lookup_vec + offset*odsm_weight;
			float smallest_dist_to_light = texture(shadow_cubemap, sample_lookup_vec).r;

			//dist to light for current fragment
			current_dist_to_light = length(sample_lookup_vec);
			//TODO: un-hardcode this
			//undo previous [0,1] mapping of lightsource distance
			smallest_dist_to_light *= light_farplane;

			if	(smallest_dist_to_light+eps < current_dist_to_light) {
				//the further away, the weaker the shadow
				if (smallest_dist_to_light > 0) {
					odsm_shadow += rstep;
				}
			}
		}
			//if we are in penumbra, take more samples
	//if ((odsm_shadow != 1.0) && (odsm_shadow > 0.0001) ) {
	if (false) {

	odsm_shadow = 0.0;
	float big_rstep = 1.0f / big_offset_size;
	for (int i=8; i< big_offset_size; i++) {
		//vec3 offset = texture(offset_map, vec2((i % offset_size), i / offset_size) ).xyz;
		//vec3 offset = vec3(offsets[i % offset_size], 0);
		vec3 offset = vec3((i % offset_size), (i / offset_size), 0 );
		// read depth val from shadowmap
		vec3 sample_lookup_vec = cm_lookup_vec + offset*big_odsm_weight;
		float smallest_dist_to_light = texture(shadow_cubemap, sample_lookup_vec).r;

		//dist to light for current fragment
		float current_dist_to_light = length(sample_lookup_vec);
		//weight = 0.05 * current_dist_to_light;

		//undo previous [0,1] mapping of lightsource distance
		smallest_dist_to_light *= light_farplane;
		  
		if (smallest_dist_to_light+eps < current_dist_to_light) {
			//fragment lies in shadow
			odsm_shadow += big_rstep;
		}

	}
	}

		//weight = 0.05 * current_dist_to_light;

		//diminish shadow by distance
		float diminish = pow((1 - current_dist_to_light / light_farplane),3);
		return vec2(odsm_shadow * diminish, current_dist_to_light);
		//return pow((1 - current_dist_to_light / light_farplane),2);
}

void main(void)
{
	float diffuse_intensity;
	float diffuse_intensity_odsm;
	float specular_intensity;

	vec3 light;
	vec3 light_odsm;
	vec3 normal;
	vec3 eye;
	vec3 reflection;

	vec4 smCoord = ex_lightspace_position;
	float shadow = 0;
	//maximum distance of the neighbour samples
	float fsize = 0.05;

	
	//soft shadow testing
	vec2 offsets[offset_size];
	offsets[0] = vec2(0,0);
	offsets[1] = vec2(1,0);
	offsets[2] = vec2(0,1);
	offsets[3] = vec2(1,1);
	offsets[4] = vec2(-1.0, 0.0);
	offsets[5] = vec2(0.0, -1.0);
	offsets[6] = vec2(-1.0, -1.0);
	offsets[7] = vec2(0.0, 0.0);

	//--------------
	// shadow calculation
	//--------------------
	if (false) {
		shadow = textureProj(in_shadowmap, ex_lightspace_position);
	}
	else {
		//--------------------------
		// accumulate shadow samples
		//--------------------------
		//float shadow = 1.0f;
		//do testing samples first
		for (int i=0; i< offset_size; i++) {
			//vec3 offset = texture(offset_map, vec2((i % offset_size), i / offset_size) ).xyz;
			vec4 offset = vec4(offsets[i], 0, 0);
			//vec3 offset = vec3((i % offset_size), (i / offset_size), 0 );
			// read depth val from shadowmap
			smCoord = offset * fsize + ex_lightspace_position;
			shadow += textureProj(in_shadowmap, smCoord) * (1.0 / offset_size);
		}


		//if we are in penumbra, take more samples
		//if ((shadow < 0.99) && (shadow > 0.0001) ) {
		if (false) {
			float weight = 0.1;
			shadow = 0.0;
			int offset_size_square = offset_size * offset_size;
			for (int i=0; i< offset_size_square; i++) {
				//vec3 offset = texture(offset_map, vec2((i % offset_size), i / offset_size) ).xyz;
				//vec3 offset = vec3(offsets[i % offset_size], 0);
				vec4 offset = vec4((i % offset_size), (i / offset_size), 0, 0 );
				// read depth val from shadowmap
				smCoord = offset * weight + ex_lightspace_position;
				shadow += textureProj(in_shadowmap, smCoord) * (1.0 / offset_size);
			}

		}
	}
	//--------------------------------
	// accumulate shadow samples done
	//--------------------------------
	//second shadow source
	vec2 odsm_shadow = odsm_shadow_value();
	//make sun shadows fade if odsm light is near
	//if (odsm_shadow.x < 0.1) {
		//shadow += 0;
	//}
	//shadow *= (1 - (odsm_shadow.y / 60)); //interesting: this makes things darker
	//shadow -= pow((odsm_shadow.y / 60), 3);
	//shadow = shadow * (1 - odsm_shadow.x);
	//shadow = (shadow + odsm_shadow.x) / 2;

	light = normalize(texture_light);
	//light = normalize(ex_lightspace_position.xyz);
	light_odsm = normalize(texture_light_odsm);
	
	
	eye = normalize(texture_eye);
	normal = normalize(texture2D(normal_map, fragment_texcoord).xyz * 2.0 - 1.0);
	normal.y = -normal.y;	//Left handed to right handed spaced
	//because most normal maps are generated for direct x -> difference to opengl


	//diffuse part of the surface
	diffuse_intensity = clamp(max(dot(normal, light), 0.0), 0.0, 1.0);
	//odsm diffuse intensity
	diffuse_intensity_odsm = clamp(max(dot(normal, light_odsm), 0.0), 0.0, 1.0);

	//mix the diffuse intensities
	diffuse_intensity = (diffuse_intensity + diffuse_intensity_odsm) / 2;

	//get the specular part of the surface
	if(shadow > 0.01)
	{
	//war früher -light
		reflection = normalize(reflect(light, normal));
		specular_intensity = pow(clamp(max(dot(reflection, eye), 0.0), 0.0, 1.0), 30.0);
	}
	else
	{
		specular_intensity = 0.0;
	}
 
	//reflection = normalize(reflect(-light, normal));
	//specular_intensity = pow(clamp(max(dot(reflection, eye), 0.0), 0.0, 1.0), 30.0);
		
	
	vec4 texel_color = texture2D(in_texture, fragment_texcoord);

	//damit nie komplett finster!!!! aber sieht nicht schlecht aus!
	//out_color = vec4(0.0, 0.0, 0.0, 1.0) + texel_color * 0.05 +
				//texel_color * diffuse_intensity * clamp(shadow, 0.01, 1.0) +
			    //vec4(0.0, 0.0, 0.0, 1.0) * specular_intensity; 
				//texel_color * 0.05; //ambient term?
	
	out_color = vec4(0.0, 0.0, 0.0, 1.0) + vec4(0.0, 0.0, 0.0, 1.0) +
				texel_color * 0.05 + 
				texel_color * diffuse_intensity * clamp(shadow, 0.1, 1.0) +   
				vec4(0.0, 0.0, 0.0, 1.0) * specular_intensity;


	//dürfte komplett falsch sein...
	//out_color = vec4(0.0, 0.0, 0.0, 1.0) + vec4(0.0, 0.0, 0.0, 1.0) + texel_color * diffuse_intensity +
		//vec4(0.0, 0.0, 0.0, 1.0) * specular_intensity;

}//end of main(void)