#version 430 core
/*
* Copyright 2018 Vienna University of Technology.
* Institute of Computer Graphics and Algorithms.
* This file is part of the ECG Lab Framework and must not be redistributed.
*/

// reference for cook torrance shader: https://github.com/pboechat/cook_torrance/tree/master/application/shaders

#define PI 3.14159265

in VertexData {
	vec3 position_world;
	vec3 normal_world;
	vec2 uv;
} vert;

in vec3 view_direction;
in vec3 light_direction;
in float light_distance;

layout (location = 0) out vec4 color;
layout (location = 1) out vec4 brightColor;

uniform vec3 camera_world;

uniform vec3 materialCoefficients; // x = ambient, y = diffuse, z = specular 
uniform float specularAlpha;
uniform float brightness;
uniform sampler2D diffuseTexture;
uniform sampler2D specularTexture;
uniform sampler2D normalTexture;
uniform sampler2D lightTexture;
uniform int normalmap_toggle;

uniform vec3 specular_color = vec3(1, 0.8, 0.8);
uniform float F0 = 0.3;
uniform float roughness = 0.3;
uniform float k = 0.3;

uniform struct DirectionalLight {
	vec3 color;
	vec3 direction;
} dirL;

uniform struct PointLight {
	vec3 color;
	vec3 position;
	vec3 attenuation;
} pointL;

vec3 CookTorrance(vec3 material_diffuse_color, vec3 material_specular_color, vec3 normal, vec3 light_direction, vec3 view_direction, vec3 light_color)
{
	float NdotL = max(0, dot(normal, light_direction));
	float Rs = 0.0;
	if(NdotL > 0)
	{
		vec3 H = normalize(light_direction + view_direction);
		float NdotH = max(0, dot(normal, H));
		float NdotV = max(0, dot(normal, view_direction));
		float VdotH = max(0, dot(light_direction, H));

		// Fresnel term
		float F = pow(1.0 - VdotH, 5.0);
		F *= (1.0 - F0);
		F += F0;

		// Beckmann distribution
		float m_squared = roughness * roughness;
		float r1 = 1.0 / (4.0 * m_squared * pow(NdotH, 4.0));
		float r2 = (NdotH * NdotH - 1.0) / (m_squared * NdotH * NdotH);
		float D = r1 * exp(r2);

		// Geometric shadowing
		float two_NdotH = 2.0 * NdotH;
		float g1 = (two_NdotH * NdotV) / VdotH;
		float g2 = (two_NdotH * NdotL) / VdotH;
		float G = min(1.0, min(g1, g2));

		Rs = (F * D * G) / (PI * NdotL * NdotV);
	}
	return material_diffuse_color * light_color * NdotL + light_color * material_specular_color * NdotL * (k + Rs * (1.0 - k));
}

//reference: https://www.gamasutra.com/blogs/RobertBasler/20131122/205462/Three_Normal_Mapping_Techniques_Explained_For_the_Mathematically_Uninclined.php
mat3 cotangent(vec3 N, vec3 p, vec2 uv)
{
	// edge vectors
	vec3 dp1 = dFdx(p);
	vec3 dp2 = dFdy(p);
	vec2 duv1 = dFdx(uv);
	vec2 duv2 = dFdy(uv);

	//solve linear system
	vec3 dp2perp = cross(dp2, N);
	vec3 dp1perp = cross(N, dp1);
	vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
	vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;

	// scale invariant frame
	float invmax = inversesqrt(max(dot(T,T), dot(B,B)));
	return mat3(T*invmax, B*invmax, N);
}

vec3 perturb_normal( vec3 N, vec3 V, vec2 texcoord )
{
	// assume N, the interpolated vertex normal and V, the view vector
	vec3 map = texture2D( normalTexture, vert.uv ).xyz;

	// WITH_NORMALMAP_UNSIGNED
	map = map * 255./127. - 128./127.;

	mat3 TBN = cotangent( N, -V, texcoord );
	return normalize( TBN * map );
}

void main() {	

	// apply the specular map intensity
	vec3 spec_col = vec3(1, 1, 1) * texture(specularTexture, vert.uv).rgb;

	vec3 faceNormal;
	float lambert = 1.0;
	if(normalmap_toggle == 1)
	{
		faceNormal = perturb_normal(normalize(vert.normal_world), normalize(view_direction), vert.uv.st);
		lambert = max(0.0, dot(faceNormal, normalize(light_direction))) * 5.0; //strengthening factor
	}
	vec3 lightMapValue = texture(lightTexture, vert.uv).rgb;
	vec3 shader_col = brightness * lambert * CookTorrance(texture(diffuseTexture, vert.uv).rgb * lightMapValue, spec_col, vert.normal_world, light_direction, view_direction, pointL.color * 1.0);

	//adjust contrast
	vec3 shader_col_contrasted = (shader_col) * 1.2;
	vec3 bright = shader_col_contrasted + vec3(.05, .05, .05);

	vec3 shader_final_col = bright;
	color = vec4(shader_final_col, 1.0);

	//used for bloom
	if(texture(diffuseTexture, vert.uv).r > 1.0)
		brightColor = vec4(10.0, 0.0, 0.0, 1.0);
}

