#ifndef NROFLIGHTS
#define NROFLIGHTS 1
#endif 

DepthStencilState depthState
{
DepthEnable = TRUE;
DepthWriteMask = ALL;
DepthFunc = LESS_EQUAL;
};

RasterizerState rsSolid
{
	  FillMode = Solid;
	  CullMode				= BACK;
	  DepthClipEnable		= TRUE;
	  AntialiasedLineEnable	= TRUE;
	  FrontCounterClockwise = TRUE;
	  SlopeScaledDepthBias	= 0.0f;
	  DepthBiasClamp		= 0.0f;
	  DepthBias				= 0;
};

BlendState noBlend_BlendState
{
	AlphaToCoverageEnable	= FALSE;
	BlendEnable[0]			= FALSE;
	BlendEnable[1]			= FALSE;
	BlendEnable[2]			= FALSE;
	BlendEnable[3]			= FALSE;
	BlendEnable[4]			= FALSE;
	BlendEnable[5]			= FALSE;
	BlendEnable[6]			= FALSE;
	BlendEnable[7]			= FALSE;
};

SamplerState diffuseTextureSampler
{
    Filter = ANISOTROPIC;
    MaxAnisotropy = 8;
    AddressU = Wrap;
    AddressV = Wrap;
};

SamplerState normalMapSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

SamplerState shadowMapSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = CLAMP;
    AddressV = CLAMP;
};

struct Material
{
	float4 ambient;
	float4 diffuse;
	float4 specular;
	float4 emissive;
	float exp;
	bool hasTexture;
	bool hasNormalMap;
	bool hasEmissive;
};

struct LightSource
{
	float4 color;
	float3 position;
};

cbuffer cbChangeOnResize
{
    matrix projectionMatrix;
};

cbuffer cbChangesEveryFrame
{
	matrix viewMatrix;
	matrix lightView;
	matrix lightProj;   
	float3 eye;
	LightSource light[2];
	float2 pcfOffsets[8];
};

matrix modelMatrix;
Material material;


Texture2D shadowMap;
Texture2D diffuseTexture;
Texture2D normalMap;

float bumpFactor = 1;
float4 ambientLight = float4(0.15,0.15,0.15,1.0);

struct VS_INPUT
{
    float4 pos : POSITION;
    float4 normal : NORMAL;
    float4 tangent : TANGENT;
    float4 binormal : BINORMAL;
    float2 texCoords : TEXCOORD;
};

struct PS_INPUT
{
    float4 pos : SV_POSITION;
    float4 worldPos : TEXCOORD5;
    float4 viewPos : TEXCOORD6;
    float4 lightPos : TEXCOORD7;
    float3 normal : TEXCOORD1;
    float3 tangent : TEXCOORD2;
    float3 binormal : TEXCOORD3;
    float2 texCoords : TEXCOORD0;
    float2 depth : TEXCOORD4;    
};

struct PS_OUTPUT
{
    float4 color : SV_Target0;
    float depth : SV_Target1;   
};


float4 calcPhongIllumination(Material mat, float4 lightCol, float3 N, float3 L, float3 H)
{
	float4 Id = mat.diffuse* saturate( dot(L,N));
	float4 Is = 0;
	
	if(saturate(dot(L,N)) > 0)
		Is = mat.specular * pow( saturate(dot(N,H)), mat.exp);
	
	return (Id +Is) * lightCol;
}

PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    output.worldPos = mul( input.pos, modelMatrix );
    output.viewPos = mul( output.worldPos, viewMatrix );
    output.pos = mul( output.viewPos, projectionMatrix );
    
    output.viewPos = output.pos;
    
    output.texCoords = input.texCoords;
    
    output.lightPos =  mul( input.pos, modelMatrix );
    output.lightPos = mul( output.lightPos, lightView );
    output.lightPos = mul( output.lightPos, lightProj );
    
	output.normal = normalize( mul((float3)input.normal, (float3x3)modelMatrix) );
	output.tangent = normalize( mul((float3)input.tangent, (float3x3)modelMatrix) );	
	output.binormal = normalize( mul((float3)input.binormal, (float3x3)modelMatrix) );		
	
	output.depth = output.pos.zw;
    
    return output;
}

PS_OUTPUT PS( PS_INPUT input) : SV_Target
{
	PS_OUTPUT output;
	float3 usedNormal = normalize(input.normal);
	float3 viewDir = normalize(eye-(float3)input.worldPos);
	float2 projectedLightPos;
    projectedLightPos.x = input.lightPos.x/input.lightPos.w/2.0f +0.5f;
    projectedLightPos.y = -input.lightPos.y/input.lightPos.w/2.0f +0.5f;
	float4 I = material.ambient * ambientLight;
	bool outOfShadowMap = false;
	float realDistance=0;
	float shadowMapDepth=0;
	
	float percInLight = 0;
	
	if(material.hasEmissive)
				I += material.emissive;
	
	if ((saturate(projectedLightPos).x == projectedLightPos.x) && (saturate(projectedLightPos).y == projectedLightPos.y))
	{
		realDistance = input.lightPos.z;
		shadowMapDepth = (shadowMap.Sample(shadowMapSampler, projectedLightPos)).r;
		
		float4 shadowMapValues;
		shadowMapValues.r = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[0])).r;
		shadowMapValues.g = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[1])).r;
		shadowMapValues.b = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[2])).r;
		shadowMapValues.a = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[3])).r;
		float4 shadowMapValues2;
		shadowMapValues2.r = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[4])).r;
		shadowMapValues2.g = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[5])).r;
		shadowMapValues2.b = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[6])).r;
		shadowMapValues2.a = (shadowMap.Sample(shadowMapSampler, projectedLightPos+pcfOffsets[7])).r;
		
		float4 inlight = (realDistance-1/4.0f < shadowMapValues);
		percInLight = dot(inlight,float4(0.125f,0.125f,0.125f,0.125f));	
		inlight = (realDistance-1/4.0f < shadowMapValues2);
		percInLight += dot(inlight,float4(0.125f,0.125f,0.125f,0.125f));	
				
	}
	else
	{
		outOfShadowMap = true;
	}
	
	if(outOfShadowMap || percInLight > 0.0f)
	{
		if(material.hasNormalMap)
		{
			float3 bump = bumpFactor * ((float3)(normalMap.Sample( normalMapSampler, input.texCoords )) - 0.5f);
			usedNormal = normalize(input.normal + (bump.x * input.tangent + bump.y * input.binormal));
		}
		
		for(int i=0;i<NROFLIGHTS;i++)
		{
			//convert to directional light, better looking for "sun"
			float3 lightDir = normalize(light[i].position);
			float3 h = normalize((lightDir+viewDir));
			if(outOfShadowMap)
				I += calcPhongIllumination(material, light[i].color, usedNormal, lightDir , h);
			else
				I += calcPhongIllumination(material, light[i].color, usedNormal, lightDir , h)*percInLight;
		}
	}
		
	if(material.hasTexture)
	{
		output.color =  diffuseTexture.Sample( diffuseTextureSampler, input.texCoords )*I;
	}
	else
		output.color = I;
		
	output.depth = abs(input.viewPos.z);
	
	return output;
}


technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
        SetDepthStencilState( depthState, 0 );
		SetRasterizerState( rsSolid );
		SetBlendState(noBlend_BlendState, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);
    }
}


