SamplerState sm:register(s0);
Texture2D texture_diffuse:register(t0);
Texture2D texture_specular:register(t1);
Texture2D texture_emissive:register(t2);
Texture2D texture_normal:register(t3);

cbuffer cbPerObject :register(b0)
{
	matrix WVP;
	matrix WorldMatrix;
	float4 MaterialDiffuseColor;
	float4 MaterialAmbientColor;
	float4 MaterialSpecularColor;
	float fMaterialSelfIllumination;
	float fMaterialShininess;
	float fMaterialNormalMap;
	float _padding_01;
	float4 WorldCameraPosition;
};

struct light_t {
	float4 position;
	float4 color;
	float attenuationConstant;
	float attenuationLinear;
	float attenuationQuadratic;
	float _padding_00;
};

cbuffer cbLighting : register(b1) {
	light_t lights[8];

	int lightCount;
}

struct VOut
{
	float4 position : SV_POSITION;
	//float3 unprocessed_pos : TEXCOORD;
	float4 normal : NORMAL;
	float4 worldPosition: TEXCOORD;
	float2 texcoord : COLOR;
};

float3x3 cotangent_frame(float3 N, float3 p, float2 uv)
{
	// get edge vectors of the pixel triangle
	float3 dp1 = ddx(p);
	float3 dp2 = ddy(p);
	float3 duv1 = ddx(float3(uv, .0));
	float3 duv2 = ddy(float3(uv, .0));

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

	// construct a scale-invariant frame 
	float invmax = 1.0 / sqrt(max(dot(T, T), dot(B, B)));
	return transpose(float3x3(T * invmax, B * invmax, N));
}

float3 perturb_normal(float3 N, float3 V, float2 texcoord)
{
	// assume N, the interpolated vertex normal and 
	// V, the view vector (vertex to eye)
	float3 map = texture_normal.Sample(sm, texcoord).xyz;
	map = map * 255. / 127. - 128. / 127.;
	map *= float3(-1, -1, 1);
	float3x3 TBN = cotangent_frame(N, -V, texcoord);
	return normalize(mul(TBN, map));
}
float4 main(VOut vin) : SV_TARGET
{
	float3 surface_normal = normalize(vin.normal.xyz);
	float3 ray_dir = normalize(vin.worldPosition.xyz - WorldCameraPosition.xyz);

	float fresnel = 1.0 + dot(surface_normal, ray_dir);
	fresnel *= fresnel;

	float4 surfaceDiffuseColor = MaterialDiffuseColor;
	float4 surfaceSpecularColor = MaterialSpecularColor;
	float surfaceSelfIllumination = fMaterialSelfIllumination;
	float3 surfaceNormal = vin.normal.xyz;

	if (surfaceDiffuseColor.a < -0.9) {
		surfaceDiffuseColor = float4(texture_diffuse.Sample(sm, vin.texcoord).xyz, 1.0);
	}

	if (surfaceSpecularColor.a < -0.9) {
		surfaceSpecularColor = float4(texture_specular.Sample(sm, vin.texcoord).xyz, 1.0);
	}

	if (surfaceSelfIllumination < -0.9)
	{
		surfaceSelfIllumination = texture_emissive.Sample(sm, vin.texcoord).x;
	}

	float3 view_ray = vin.worldPosition.xyz - WorldCameraPosition.xyz;
	if (fMaterialNormalMap > .0) 
	{
		surface_normal = normalize(lerp(surface_normal, perturb_normal(surface_normal, view_ray, vin.texcoord.xy), fMaterialNormalMap));
	}

	float3 colorAccumulator = surfaceSelfIllumination*surfaceDiffuseColor.xyz;

	for (int i = 0; i < lightCount; i++)
	{
		float3 lightDirection;
		float3 lightColor = lights[i].color.xyz;
		float attenuation;

		if (lights[i].position.w < .01f) //directional light
		{
			lightDirection = normalize(lights[i].position.xyz);
			attenuation = 1.0; //disable
		}
		else { //point light
			float3 difference = lights[i].position.xyz - vin.worldPosition.xyz;
			lightDirection = normalize(difference);
			float distance = length(difference);
			attenuation = 1.0f / (lights[i].attenuationConstant + lights[i].attenuationLinear*distance + lights[i].attenuationQuadratic*distance*distance);
		}

		float diffuse_coef = dot(lightDirection, surface_normal);
		diffuse_coef = max(diffuse_coef, .0);
		colorAccumulator += diffuse_coef * surfaceDiffuseColor.xyz * lightColor * attenuation;

		float3 reflected_light = reflect(lightDirection, surface_normal);
		float specular = pow(max(dot(ray_dir, reflected_light), 0.0), fMaterialShininess);
		colorAccumulator += surfaceSpecularColor.xyz * lightColor * specular;
	}

	return float4(colorAccumulator, 1.0);

}