#version 120
// shader level settings
const bool advancedProcedurals = 	#{{level:extreme}};
const bool parallax = 				#{{level:extreme}};
const bool scattering =				#{{level:extreme}};
const bool pbrLight = 				#{{level:high}};
const bool simpleProcedurals = 		#{{level:high}};
const bool advancedAmbience = 		#{{level:high}};
const bool normalMapping = 			#{{level:medium}};
const bool simpleAmbience = 		#{{level:medium}};
const bool selfIllumination = 		#{{level:medium}};

const int nLightCount = 2;
const float pi = 3.14159265358;
const float emissiveIntensity = 5.0;
const float lightIntensity = 5.0;

uniform sampler2D wreckage;
uniform samplerCube skybox;
uniform float lightRadius[nLightCount];
uniform float life;
varying vec4 pos;
varying vec3 normal;
varying vec3 npos;
varying vec2 uv;

vec3 light[nLightCount];
float dist[nLightCount];

// parallax scale, bias and steps
const vec2 scaleBias = vec2(0.001, 0.0005);


// pow alternatives
vec3 square(vec3 x) {
	return x*x;
}

float square(float x) {
	return x*x;
}

vec2 square(vec2 x) {
	return x*x;
}



vec3 toLinear(vec3 x) {
	return pow(x, vec3(2.2));
}

vec3 toGamma(vec3 x) {
	return pow(x, vec3(0.45));
}


// get normal map z component
float deriveZ(vec2 n) {	

	float z = pow(abs(1.0 - n.x * n.x - n.y * n.y), 0.5);
	
	return z;
}

float D_GGX(float HdotN, float Roughness)
{
    float m = Roughness * Roughness;
    float m2 = m * m;
    float denominator = HdotN * HdotN * (m2 - 1.0) + 1.0;
    float D = m2 /( pi * denominator * denominator);
    return D;
}

// Schlick GGX approximation
float V_SchlickforGGX(float Roughness, float NdotV, float NdotL)
{
    float k = Roughness * Roughness * 0.5f;
    float G1V = NdotV * (1.0 - k) + k;
    float G1L = NdotL * (1.0 - k) + k;
    return 0.25f / (G1V * G1L);
}
// diffuse term
float OrenNayerforDiffuseOnly(float roughness, float NdotL, float NdotV)
{
	float O = 0.62 - pow(1.0-NdotL * clamp(1.0-NdotV/2.0, 0.0,1.0), pi) * 0.62;
	O = mix(O, NdotL, roughness);
	return O;
}

vec3 Fresnel(vec3 substance, vec3 l, vec3 h)
{
	return substance + (1.0 - substance) * pow(1.0 - clamp((dot(l, h)), 0.0, 1.0), 5.0);
}

vec3 Fresnel2(vec3 substance, float dotProduct, float roughness)
{
	return substance + (1.0 - substance) * pow((1.0 - dotProduct), 5.0) / (6.0 - 5.0 * roughness);
}

vec3 dp1Calc(vec3 p)
{
	return dFdx(p);
}

vec3 dp2Calc(vec3 p)
{
	return dFdy(p);
}

vec2 duv1Calc(vec2 uv)
{
	return dFdx(uv);
}

vec2 duv2Calc(vec2 uv)
{
	return dFdy(uv);
}

void main() {
	vec2 uvP = uv;
	vec3 v = normalize(npos);
	vec3 n = normalize(normal);
	// first NdotV - used for parallax and low shading
	float NdotV = max(0.0, dot(normal, v));
	mat3 TBN = mat3(0.0);
	
	// part one of normal mapping
	if (normalMapping){		
		// tbn screenspace cotangent derivative
		vec3 dp1 = dp1Calc(-v);
		vec3 dp2 = dp2Calc(-v);
		vec2 duv1 = duv1Calc(uvP);
		vec2 duv2 = duv2Calc(uvP);
		
		// solve the linear system
		vec3 dp2perp = cross(dp2, normal);
		vec3 dp1perp = cross(normal, dp1);
		vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
		vec3 binormal = dp2perp * duv1.y + dp1perp * duv2.y;
	
		// construct a scale-invariant frame 
		float invmax = pow(max(dot(tangent, tangent), dot(binormal, binormal)), -0.5);
		TBN = mat3(tangent * invmax, binormal * invmax, normal);
		
		// do parallax for both main uv and paint, so paint doesn't float about
		if(parallax){
			float p = texture2D(wreckage, uvP).a * scaleBias.r - scaleBias.g;
			float fDet = dot(dp1, dp2perp);
			vec2 vProjVScr = (1.0/fDet) * vec2(dot(dp2perp, v), dot(dp1perp, v));
			vec2 vProjVTex = (duv1.xy * vProjVScr.x + duv2.xy * vProjVScr.y);	
			float vProjVTexZ = NdotV * p;
	
			uvP += (vProjVTex.xy * vProjVTexZ);
			
			float sampDiscard = texture2D(wreckage, uvP, 0.0).a;
			if (sampDiscard <= 0.025f)
			{
				discard;
			}
		}
		else{
			float sampDiscard = texture2D(wreckage, uvP, 0.0).a;
			if (sampDiscard <= 0.025f)
			{
				discard;
			}	
		}
	}
	else{
		float sampDiscard = texture2D(wreckage, uvP, 0.0).a;
		if (sampDiscard <= 0.025f)
		{
			discard;
		}	
	}
	

	vec3 sampData = texture2D(wreckage, uvP).rgb;
	
	float metalness = min(1.0, sampData.b * 4.0);
	sampData.xy *= 2.0;
	sampData.xy -= 1.0;
		
	// Roughness setup
	float orgRoughness = sampData.b * 0.5 + 0.25;
	// Albedo setup
	vec3 albedo = toLinear(mix(vec3(0.271, 0.247, 0.216), vec3(0.541, 0.518, 0.502), sampData.b));
	
	orgRoughness += 0.5;
	n = normalize(TBN * normalize(vec3(sampData.xy, deriveZ(sampData.xy))));
	vec3 r = normalize(reflect(-v, n));
	// Substance setup and Albedo adjust
	vec3 substance = clamp((0.04 - 0.04 * metalness) + albedo * metalness,0.0, 1.0);
	albedo -= substance;
	
	// Light model specials setup
	NdotV = max(0.0, dot(n, v));	

	// actual shading starts here
	vec3 color = vec3(0.0);

	vec3 ambientFresnel = Fresnel2(substance, NdotV ,orgRoughness);
		
	if (advancedAmbience){
		
		color += square((textureCube(skybox, r, sqrt(orgRoughness) * 4.0).rgb) + 0.024) * ambientFresnel;
	
		// ambient light
		color += square(textureCube(skybox, n.xyz, 4.0).rgb + 0.024) * albedo * (1.0 - ambientFresnel);
	}
	else if (simpleAmbience){
		// Ambient reflections with fix mip and n instead of refect
		color += square(textureCube(skybox, r, 3.0).rgb * ambientFresnel);
	
		// Ambient light - average color of skybox squared
		color += vec3(0.006724, 0.014884, 0.067081) * albedo * (1.0 - ambientFresnel);
	}
	else{
		// Ambient 
		color += vec3(0.006724, 0.014884, 0.067081) * (ambientFresnel + albedo * (1.0 - ambientFresnel));
	}
	
	if (pbrLight){		
		for (int i = 0; i < nLightCount; i++) {
			float distance = length(gl_LightSource[i].position.xyz - pos.xyz); // EYE SPACE, I'm sorry:o(
			// rest is world space

			vec3 L = normalize(light[i] / distance);
			float NdotL = max(0.0, dot(n.xyz,L));
			
			float sqrLightRadius = square(lightRadius[i]);
			float illuminance = lightIntensity * pi * (sqrLightRadius / (max(sqrLightRadius, dot(L,L))));
			
			// note the square to kill hard spec in deep space!
			float attenuation = square(1.0 / (1.0 + (gl_LightSource[i].constantAttenuation
			+ gl_LightSource[i].linearAttenuation * distance
			+ gl_LightSource[i].quadraticAttenuation * square(distance)))) * illuminance * NdotL;
			
			if (attenuation >0.0){
				vec3 VplusL = L + v;
				vec3 halfVec = normalize(VplusL);
				float HdotN = max(0.0, dot(halfVec,n));
				vec3 F = Fresnel(substance, L, halfVec);
				float D = max(0.0, D_GGX(HdotN, orgRoughness));
				float V = max(0.0, V_SchlickforGGX((1.0 + orgRoughness) * 0.5, NdotV, NdotL));
				float O = OrenNayerforDiffuseOnly(orgRoughness, NdotL, NdotV);
				
				color += ((D * V * F) + (1.0 - F) * O * albedo) * gl_LightSource[i].diffuse.rgb * attenuation;
			}
		}
		// hard cavity multiplier
		color *= (sampData.b + 1.0);
	}
	// this light model is pretty loose in terms of what it does, design focus to match 
	// the pbr in terms of visibility and overall feel for a fraction of the price
	else{
		for (int i = 0; i < nLightCount; i++) {
			float distance = length(gl_LightSource[i].position.xyz - pos.xyz); // EYE SPACE, I'm sorry:o(
			// rest is world space

			vec3 L = normalize(light[i] / distance);
			float NdotL = max(0.0, dot(n.xyz,L));
			
			float sqrLightRadius = square(lightRadius[i]);
			float illuminance = lightIntensity * pi * (sqrLightRadius / (max(sqrLightRadius, dot(L,L))));
			
			// note the square to kill hard spec in deep space!
			float attenuation = square(1.0 / (1.0 + (gl_LightSource[i].constantAttenuation
			+ gl_LightSource[i].linearAttenuation * distance
			+ gl_LightSource[i].quadraticAttenuation * square(distance)))) * illuminance * NdotL;
			
			vec3 VplusL = L + v; 
			vec3 halfVec = normalize(VplusL);
			float HdotN = max(0.0, dot(halfVec, n));
			vec3 S = Fresnel2(substance, HdotN ,orgRoughness);
			// albedo * (maskSamp.b * 0.5) to sorta hack albedo into a classical diffuse texture
			color += (albedo * (sampData.b * 0.5) + pow(S * HdotN, vec3(sampData.b + 5.0))) * gl_LightSource[i].diffuse.rgb * attenuation;
		}
	}

	float fadeCurve = clamp(1.0 - pow(life, 0.33) + 0.5, 0.0, 1.0);

	gl_FragColor.rgb = toGamma(color + toLinear(mix(vec3(0.69, 0.231, 0.027), vec3(1.0, 0.969, 0.6), fadeCurve) * fadeCurve * 5.0 * sampData.b));
	gl_FragColor.a = 1.0;
}
