//Billboard pixel shader - performs lighting on the billboard

SamplerState Sampler0 : register(s0);
SamplerState shadowSampler : register(s1);
Texture2D billboardTexture : register(t0);
Texture2D sunShadowMapTexture : register(t1);
Texture2D spotShadowMapTexture : register(t2);
Texture2D hillShadowMapTextures[6] : register(t3);

cbuffer LightBuffer : register(b0)
{
	float4 sunAmbient;
	float4 sunDiffuse;
	float3 sunDirection;
	float padding;
	float4 spotDiffuse;
	float3 spotPosition;
	float constantFactor;
	float linearFactor;
	float quadraticFactor;
	float2 paddingTwo;
	float3 spotDirection;
	float paddingThree;
	float4 hillDiffuse;
	float3 hillPosition;
	float paddingFour;
};

cbuffer MatrixBuffer : register(b1)
{
	matrix worldMatrix;
	matrix viewMatrix;
	matrix projectionMatrix;
	matrix sunViewMatrix;
	matrix sunProjectionMatrix;
	matrix spotViewMatrix;
	matrix spotProjectionMatrix;
	matrix hillViews[6];
	matrix hillProjections[6];
};

struct InputType
{
	float4 position : SV_POSITION;
	float2 tex : TEXCOORD0;
	float3 normal : NORMAL;
	float4 sunViewPos : TEXCOORD1;
	float4 spotViewPos : TEXCOORD2;
	float3 worldPosition : TEXCOORD3;
	float4 hillViews[6] : TEXCOORD4;
};

//Directional lighting
float4 calculateLighting(float3 lightDirection, float3 normal, float4 diffuse)
{
	float intensity = saturate(dot(normal, lightDirection));	//Intensity defined by the angle between the direction of the light and the normal
	float4 colour = saturate(diffuse * intensity);	//Output the diffuse colour multipled by the intensity
	return colour;
}

//Calculates attenutation for the spot light
float getAttenuation(float3 spotPos, float3 worldPos)
{
	float distance = length(spotPos - worldPos);	//Calculate the distance between the spot light's position and the world position

	//Check the denominator won't exceed one
	if ((constantFactor + (linearFactor * distance) + (quadraticFactor * pow(distance, 2))) > 1.0f)
	{
		return 1.0f;	//Return 1 so attenuation stays at a minimum of 1
	}

	else
	{
		return 1.0f / (constantFactor + (linearFactor * distance) + (quadraticFactor * pow(distance, 2)));	//Return attenutation value
	}
}

//Spot lighting
float4 calculateSpotLighting(float3 spotVect, float3 spotDir, float3 spotPos, float worldPos, float3 normal, float4 diffuse)
{
	float4 colour = float4(0.0f, 0.0f, 0.0f, 1.0f);	//Default colour to black
	float cosA = dot(spotDir, -spotVect);	//Calculate the angle between theh spot direction and its vector

	float intensity = saturate(dot(normal, spotVect));	//Intensity defined by the angle between the direction of the light and the normal
	colour = saturate(diffuse * intensity);	//Set the colour to be equal to the diffuse colour multipled by the intensity
	colour *= getAttenuation(spotPos, worldPos);	//Now apply attenuation
	colour *= pow(max(cosA, 0), 32);	//Finally, apply the roundness/curve to the spot light

	return colour;
}


float4 main(InputType input) : SV_TARGET
{
	float4 textureColour = billboardTexture.Sample(Sampler0, input.tex);	//Get the initial colour from the texture of the billboard

	//Initialize light and shadow variables
	float depthValue;
	float lightDepthValue;
	float shadowMapBias = 0.02f;
	bool IsLit = false;

	float4 colour = float4(0.0f, 0.0f, 0.0f, 1.0f);	//Default the colour to black

	//Calculate and normalize the texture coordinates of the sun and spot light
	float2 sunTexCoord = input.sunViewPos.xy / input.sunViewPos.w;
	sunTexCoord *= float2(0.5, -0.5);
	sunTexCoord += float2(0.5f, 0.5f);

	float2 spotTexCoord = input.spotViewPos.xy / input.spotViewPos.w;
	spotTexCoord *= float2(0.5, -0.5);
	spotTexCoord += float2(0.5f, 0.5f);

	//Determine if the projected coordinates are in the 0 to 1 range.  If not don't do lighting.
	if (!(sunTexCoord.x < 0.f || sunTexCoord.x > 1.f || sunTexCoord.y < 0.f || sunTexCoord.y > 1.f))
	{
		//Sample the shadow map (get depth of geometry)
		depthValue = sunShadowMapTexture.Sample(shadowSampler, sunTexCoord).r;

		//Calculate the depth from the light.
		lightDepthValue = input.sunViewPos.z / input.sunViewPos.w;
		lightDepthValue -= shadowMapBias;

		//Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
		if (lightDepthValue < depthValue)
		{
			colour += sunAmbient + calculateLighting(-sunDirection, input.normal, sunDiffuse);
			IsLit = true;
		}
	}

	//Determine if the projected coordinates are in the 0 to 1 range.  If not don't do lighting.
	if (!(spotTexCoord.x < 0.f || spotTexCoord.x > 1.f || spotTexCoord.y < 0.f || spotTexCoord.y > 1.f))
	{
		//Sample the shadow map (get depth of geometry)
		depthValue = spotShadowMapTexture.Sample(shadowSampler, spotTexCoord).r;

		//Calculate the depth from the light.
		lightDepthValue = input.spotViewPos.z / input.spotViewPos.w;
		lightDepthValue -= shadowMapBias;

		//Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
		if (lightDepthValue < depthValue)
		{
			//Calculate the vector between the position of the spot light and the world position
			float3 spotVector = normalize(spotPosition - input.worldPosition);
			colour += calculateSpotLighting(spotVector, spotDirection, spotPosition, input.worldPosition, input.normal, spotDiffuse);
			IsLit = true;
		}
	}

	//Loop through, calculate depth for all directions of the point light
	for (int i = 0; i < 6; i++)
	{
		//Calculate and normalize the texture coordinates of point light in this direction
		float2 hillTexCoords = input.hillViews[i].xy / input.hillViews[i].w;
		hillTexCoords *= float2(0.5, -0.5);
		hillTexCoords += float2(0.5f, 0.5f);

		//Determine if the projected coordinates are in the 0 to 1 range.  If not don't do lighting.
		if (!(hillTexCoords.x < 0.f || hillTexCoords.x > 1.f || hillTexCoords.y < 0.f || hillTexCoords.y > 1.f))
		{
			//Sample the shadow map (get depth of geometry)
			depthValue = hillShadowMapTextures[i].Sample(shadowSampler, hillTexCoords).r;

			//Calculate the depth from the light.
			lightDepthValue = input.hillViews[i].z / input.hillViews[i].w;
			lightDepthValue -= shadowMapBias;

			//Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
			if (lightDepthValue < depthValue)
			{
				//Calculate the vector between the position of the point light and the world position
				float3 lightVector = normalize(hillPosition.xyz - input.worldPosition);
				colour += calculateLighting(lightVector, input.normal, hillDiffuse);
				IsLit = true;
				break;	//Make sure to break here - we only want the spot light to apply its lighting effect in this direction
			}
		}
	}

	if (!IsLit)
	{	
		return sunAmbient * textureColour; //We're not lit, so apply the ambient of the sun to the texture colour
	}

	else
	{
		return saturate(colour) * textureColour;	//We are lit, so saturate the lit colour to between 0 and 1 and apply it to the texture colour
	}
}