#include "world.inc"
#include "texture.inc"
#include "light.inc"
#if OIT
#include "oit.inc"
#endif

cbuffer ConstantBufferMaterial : register( b2 )
{
	float4 Alpha;
	float4 BaseColor;
	int Toon;
	float Diffuse;
	float4 Specular;
	float  SpecularPower;
	float4 Ambient;
	int Edge;
}


#ifndef ALPHAMAP
#define ALPHAMAP TEXTURE
#endif

#if TEXTURE
Texture2D ColorMap : register( t0 );
SamplerState ColorMapSampler : register( s0 );
#endif
#if ALPHAMAP
Texture2D AlphaMap : register( t1 );
SamplerState AlphaMapSampler : register( s1 );
#endif
#if NORMALMAP
Texture2D NormalMap : register( t2 );
SamplerState NormalMapSampler : register( s2 ) {
	Filter   = MIN_MAG_MIP_POINT;
	AddressU = Wrap;
	AddressV = Wrap;
};
#endif
#if SHADOW
Texture2D ShadowMap : register( t3 );
SamplerState ShadowMapSampler : register( s3 ) {
	Filter   = MIN_MAG_MIP_LINEAR;
	AddressU = Border;
	AddressV = Border;
	AddressW = Border;
	BorderColor = float4(1,1,1,0);
};
#endif

struct VS_INPUT
{
	float4 Pos : POSITION;
	float3 Normal : NORMAL;
#if 1 //always //TEXTURE || ALPHAMAP || NORMALMAP
	float2 TexCoord : TEXCOORD0;
#endif
#if VERTEXCOLOR
	float4 Col : COLOR0;
#endif
#if NORMALMAP
	float4 Tangent : TANGENT;
#endif
};

struct VS_OUTPUT
{
	float4 Pos : SV_POSITION;
	float4 Col : COLOR;
	float3 Normal : NORMAL;
#if TEXTURE || ALPHAMAP || NORMALMAP
	float2 TexCoord : TEXCOORD0;
#endif
#if NORMALMAP
	float3 Tangent : TANGENT;
	float3 Binormal : BINORMAL;
#endif
	float4 WorldPos : TEXCOORD3;
#if SHADOW
	float4 SMPos : TEXCOORD4;
#endif
};

struct PS_INPUT
{
	float4 Pos : SV_POSITION;
	float4 Col : COLOR;
	float3 Normal : NORMAL;
#if TEXTURE || ALPHAMAP || NORMALMAP
	float2 TexCoord : TEXCOORD0;
#endif
#if NORMALMAP
	float3 Tangent : TANGENT;
	float3 Binormal : BINORMAL;
#endif
	float4 WorldPos : TEXCOORD3;
#if SHADOW
	float4 SMPos : TEXCOORD4;
#endif
	bool IsBack : SV_IsFrontFace; // Why inverted?
#if MSAA
    uint Coverage : SV_COVERAGE;
#endif
};

struct PS_OUTPUT
{
	float4 Color    : SV_Target0;
};

#include "oit_store.inc"


// Vertex shader
VS_OUTPUT VS(const VS_INPUT In)
{
	VS_OUTPUT Out;
	Out.Pos = mul(In.Pos, WorldViewProj);
#if VERTEXCOLOR
	Out.Col.xyz = In.Col.xyz;	//RGB
	Out.Col.w = BaseColor.w * In.Col.w;
#else
	Out.Col = BaseColor;
#endif
	Out.Normal = In.Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	Out.TexCoord = In.TexCoord;
#endif
#if NORMALMAP
	Out.Tangent = In.Tangent.xyz;
	Out.Binormal = normalize(cross(In.Tangent.xyz, In.Normal)) * In.Tangent.w;
#endif
	Out.WorldPos = In.Pos;
#if SHADOW
	Out.SMPos = mul(In.Pos, ShadowMapProj);
#endif
	return Out;
}


float4 GetOutlinePos(VS_OUTPUT input)
{
	float width = 1.0;
	if(input.Pos.z <= 0){
		return input.Pos;
	}else{
		float4 axisX = float4(WorldView[0][0], WorldView[1][0], WorldView[2][0], 0);
		float4 pos = mul(input.WorldPos + axisX, WorldViewProj);
		float2 p1 = pos.xy / pos.w;
		float2 p0 = input.Pos.xy / input.Pos.w;
		float w = 1.0 / Resolution.x * 2;
		float scale = width * w / length(p1 - p0);
		return mul(input.WorldPos + float4(input.Normal,0) * scale, WorldViewProj);
	}
}

// Geometry shader
[maxvertexcount(6)]
void GS(triangle VS_OUTPUT input[3], inout TriangleStream<VS_OUTPUT> stream)
{
	VS_OUTPUT output;
	output.Pos = input[0].Pos;
	output.Col = input[0].Col;
	output.Normal = input[0].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	output.TexCoord = input[0].TexCoord;
#endif
#if NORMALMAP
	output.Tangent = input[0].Tangent;
	output.Binormal = input[0].Binormal;
#endif
	output.WorldPos = float4(input[0].WorldPos.xyz, 1);
#if SHADOW
	output.SMPos = input[0].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[1].Pos;
	output.Col = input[1].Col;
	output.Normal = input[1].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	output.TexCoord = input[1].TexCoord;
#endif
#if NORMALMAP
	output.Tangent = input[1].Tangent;
	output.Binormal = input[1].Binormal;
#endif
	output.WorldPos = float4(input[1].WorldPos.xyz, 1);
#if SHADOW
	output.SMPos = input[1].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[2].Pos;
	output.Col = input[2].Col;
	output.Normal = input[2].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	output.TexCoord = input[2].TexCoord;
#endif
#if NORMALMAP
	output.Tangent = input[2].Tangent;
	output.Binormal = input[2].Binormal;
#endif
	output.WorldPos = float4(input[2].WorldPos.xyz, 1);
#if SHADOW
	output.SMPos = input[2].SMPos;
#endif
	stream.Append(output);

	stream.RestartStrip();
	
	if(Edge != 0){
		output.Pos = GetOutlinePos(input[0]);
		output.Col = input[0].Col;
		output.Normal = input[0].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[0].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[0].Tangent;
		output.Binormal = input[0].Binormal;
#endif
		output.WorldPos = float4(input[0].WorldPos.xyz, 0);
#if SHADOW
		output.SMPos = input[0].SMPos;
#endif
		stream.Append(output);

		output.Pos = GetOutlinePos(input[1]);
		output.Col = input[1].Col;
		output.Normal = input[1].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[1].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[1].Tangent;
		output.Binormal = input[1].Binormal;
#endif
		output.WorldPos = float4(input[1].WorldPos.xyz, 0);
#if SHADOW
		output.SMPos = input[1].SMPos;
#endif
		stream.Append(output);

		output.Pos = GetOutlinePos(input[2]);
		output.Col = input[2].Col;
		output.Normal = input[2].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[2].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[2].Tangent;
		output.Binormal = input[2].Binormal;
#endif
		output.WorldPos = float4(input[2].WorldPos.xyz, 0);
#if SHADOW
		output.SMPos = input[2].SMPos;
#endif
		stream.Append(output);

		stream.RestartStrip();
	}
}

// Geometry shader
//   Convert LineAdjacent (with 4 vertices) to TriangleStrip(with 3 or 4 vertices).
[maxvertexcount(8)]
void GSpatch(lineadj VS_OUTPUT input[4], inout TriangleStream<VS_OUTPUT> stream)
{
	VS_OUTPUT output;

	output.Pos = input[1].Pos;
	output.Col = input[1].Col;
	output.Normal = input[1].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	output.TexCoord = input[1].TexCoord;
#endif
#if NORMALMAP
	output.Tangent = input[1].Tangent;
	output.Binormal = input[1].Binormal;
#endif
	output.WorldPos = float4(input[1].WorldPos.xyz, 1);
#if SHADOW
	output.SMPos = input[1].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[0].Pos;
	output.Col = input[0].Col;
	output.Normal = input[0].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	output.TexCoord = input[0].TexCoord;
#endif
#if NORMALMAP
	output.Tangent = input[0].Tangent;
	output.Binormal = input[0].Binormal;
#endif
	output.WorldPos = float4(input[0].WorldPos.xyz, 1);
#if SHADOW
	output.SMPos = input[0].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[2].Pos;
	output.Col = input[2].Col;
	output.Normal = input[2].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
	output.TexCoord = input[2].TexCoord;
#endif
#if NORMALMAP
	output.Tangent = input[2].Tangent;
	output.Binormal = input[2].Binormal;
#endif
	output.WorldPos = float4(input[2].WorldPos.xyz, 1);
#if SHADOW
	output.SMPos = input[2].SMPos;
#endif
	stream.Append(output);

	if(length(input[3].Normal) > 0.0){
		output.Pos = input[3].Pos;
		output.Col = input[3].Col;
		output.Normal = input[3].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[3].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[3].Tangent;
		output.Binormal = input[3].Binormal;
#endif
		output.WorldPos = float4(input[3].WorldPos.xyz, 1);
#if SHADOW
		output.SMPos = input[3].SMPos;
#endif
		stream.Append(output);
	}

	stream.RestartStrip();
	
	if(Edge != 0){
		if(length(input[3].Normal) > 0.0){
			output.Pos = GetOutlinePos(input[3]);
			output.Col = input[3].Col;
			output.Normal = input[3].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
			output.TexCoord = input[3].TexCoord;
#endif
#if NORMALMAP
			output.Tangent = input[3].Tangent;
			output.Binormal = input[3].Binormal;
#endif
			output.WorldPos = float4(input[3].WorldPos.xyz, 0);
#if SHADOW
			output.SMPos = input[3].SMPos;
#endif
			stream.Append(output);
		}

		output.Pos = GetOutlinePos(input[2]);
		output.Col = input[2].Col;
		output.Normal = input[2].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[2].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[2].Tangent;
		output.Binormal = input[2].Binormal;
#endif
		output.WorldPos = float4(input[2].WorldPos.xyz, 0);
#if SHADOW
		output.SMPos = input[2].SMPos;
#endif
		stream.Append(output);

		output.Pos = GetOutlinePos(input[0]);
		output.Col = input[0].Col;
		output.Normal = input[0].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[0].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[0].Tangent;
		output.Binormal = input[0].Binormal;
#endif
		output.WorldPos = float4(input[0].WorldPos.xyz, 0);
#if SHADOW
		output.SMPos = input[0].SMPos;
#endif
		stream.Append(output);

		output.Pos = GetOutlinePos(input[1]);
		output.Col = input[1].Col;
		output.Normal = input[1].Normal;
#if TEXTURE || ALPHAMAP || NORMALMAP
		output.TexCoord = input[1].TexCoord;
#endif
#if NORMALMAP
		output.Tangent = input[1].Tangent;
		output.Binormal = input[1].Binormal;
#endif
		output.WorldPos = float4(input[1].WorldPos.xyz, 0);
#if SHADOW
		output.SMPos = input[1].SMPos;
#endif
		stream.Append(output);

		stream.RestartStrip();
	}
}


float3 GetNormal(PS_INPUT In)
{
#if NORMALMAP
	float3 normal_col = NormalMap.Sample(NormalMapSampler, In.TexCoord).xyz * 2.0f - 1.0f;
	normal_col *= NormalMapFlip;

	float3x3 mtx = {In.Tangent, In.Binormal, In.Normal};
	float3 nv = normalize(mul(normal_col, mtx));
#else
	float3 nv = normalize(In.Normal);
#endif
	return nv;
}

float3 GetDiffuse(PS_INPUT In, float3 nv, float3 light_dir)
{
	float dif = dot(nv, light_dir);
	float t;
	switch(Toon){
	case 1:
		if(dif > 0.065)
			return float3(1.0,1.0,1.0);
		else if(dif < -0.065)
			return float3(0.8,0.8,0.8);
		t = (dif+0.065)/(0.065+0.065);
		return float3(
			1.0*t + 0.8*(1-t),
			1.0*t + 0.8*(1-t),
			1.0*t + 0.8*(1-t));
	case 2:
		if(dif > 0.065)
			return float3(1.0,1.0,1.0);
		else if(dif < -0.065)
			return float3(0.96,0.88,0.88);
		t = (dif+0.065)/(0.065+0.065);
		return float3(
			1.0*t + 0.96*(1-t),
			1.0*t + 0.88*(1-t),
			1.0*t + 0.88*(1-t));
	case 3:
		if(dif > 0.065)
			return float3(1.0,1.0,1.0);
		else if(dif < -0.065)
			return float3(0.60,0.60,0.60);
		t = (dif+0.065)/(0.065+0.065);
		return float3(
			1.0*t + 0.60*(1-t),
			1.0*t + 0.60*(1-t),
			1.0*t + 0.60*(1-t));
	case 4:
		if(dif > 0.065)
			return float3(1.0,1.0,1.0);
		else if(dif < -0.065)
			return float3(0.97,0.94,0.92);
		t = (dif+0.065)/(0.065+0.065);
		return float3(
			1.0*t + 0.97*(1-t),
			1.0*t + 0.94*(1-t),
			1.0*t + 0.92*(1-t));
	case 5:
		if(dif > -0.10) return float3(1.0,1.0,1.0);
		if(dif < -0.38) return float3(1,0.90,0.87);
		t = (dif+0.38)/(-0.10+0.38);
		return float3(
			1.0,
			1.0*t + 0.90*(1-t),
			1.0*t + 0.87*(1-t));
	case 6:
		if(0.52 < dif && dif < 0.64){
			t = abs((dif-0.58)/0.06);
			return float3(
				1.0,
				1.0*(1-t) + 0.93*t,
				1.0*(1-t) + 0.38*t);
		}
		if(dif > -0.33) return float3(1.00,0.93,0.38);
		if(dif < -0.60) return float3(0.77,0.68,0.02);
		t = (dif+0.60)/(-0.33+0.60);
		return float3(
			1.00*t + 0.77*(1-t),
			0.93*t + 0.68*(1-t),
			0.38*t + 0.02*(1-t));
	default:
		if(dif > 0.065)
			return float3(1,1,1);
		else if(dif < -0.065)
			return float3(0,0,0);
		t = (dif+0.065)/(0.065+0.065);
		return float3(t,t,t);
	}
}

float GetShadow(PS_INPUT In)
{
#if SHADOW
	float2 smt = float2((In.SMPos.x+1)*0.5, 1.0-(In.SMPos.y+1)*0.5);
	float smz = ShadowMap.Sample(ShadowMapSampler, smt.xy).r;
	float sm_coef = (In.SMPos.z < smz+0.005) ? 1.0 : 0.5;
	return sm_coef;
#else
	return 1.0;
#endif
}

float GetSpecular(PS_INPUT In, float3 nv, float3 light_dir)
{
	float3 Reflect = normalize(ViewDir + light_dir);
	float spc_coef = pow(saturate(dot(Reflect, nv)), SpecularPower);
	return spc_coef;
}

float4 GetBaseColor(PS_INPUT In)
{
	float4 col = float4(1,1,1,1);

#if TEXTURE
	float4 tex_col = LINEARtoSRGB(ColorMap.Sample(ColorMapSampler, In.TexCoord), (HasTextureMap&3)==2);
	col *= tex_col;
#endif
#if ALPHAMAP
	float4 alpha_col = AlphaMap.Sample(AlphaMapSampler, In.TexCoord);
	col.a *= alpha_col.a;
#endif

	// Cancel to write if the color is almost transparent.
	clip(col.a - 1.0/255.0);
	return col;
}

// Pixel shader
#if OIT
[earlydepthstencil]
#endif
PS_OUTPUT PS(PS_INPUT In)
{
	PS_OUTPUT output;

	if(IsArbClipped(In.WorldPos.xyz))
		discard;

	if(In.WorldPos.w == 0){
		if(!In.IsBack)
			discard;
	}

	// Calculate a normal vector of the face.
	float3 nv = GetNormal(In);
	if(In.IsBack && In.WorldPos.w != 0){
		if(BackFaceColor.w == 0)
			discard;
		nv = -nv;
		In.Col.xyz *= BackFaceColor.xyz;
	}

	// Lighting
#if MULTILIGHT
	float3 litdif = float3(0,0,0);
	float3 litcol = float3(0,0,0);
	float3 spc = float3(0,0,0);
	for(int i=0; i<LightNum && i<1; i++){
		float3 light_dir;
		if(LightPos[i].w == 0){
			light_dir = normalize(LightPos[i].xyz - In.WorldPos.xyz); // point light
		}else{
			light_dir = LightPos[i].xyz; // directional light
		}
		litdif += GetDiffuse(In, nv, light_dir);
		litcol += LightCol[i];
		spc += LightSpc[i] * GetSpecular(In, nv, light_dir);
	}
#else
	float3 litdif = GetDiffuse(In, nv, LightDir);
	float3 litcol = float3(1,1,1);
	float spc = GetSpecular(In, nv, LightDir);
#endif
#if SHADOW
	float sd = GetShadow(In);
	litdif *= sd;
#endif

	float4 col;
	col.xyz = saturate(Ambient.xyz + In.Col.xyz * litcol * Diffuse) * litdif;
	col.w = In.Col.w;
	col *= GetBaseColor(In);
	col.xyz += Specular.xyz * spc;
	
	if(In.WorldPos.w == 0 && Edge != 0)
		col.xyz = float3(0,0,0);

	output.Color = saturate(col);

#if OIT
    StoreOIT(In, output.Color);
    output.Color = float4(0,0,0,0);	// This does not affect anything because RenderTargetWriteMask is 0.
#endif

	return output;
}

