
#define DiffuseMap tex_0
#define Map tex_1
#define MapBorderColor Arg[0]

// The data structure from the vertex shader to the geometry shader
struct VS_to_GS {
	float4 Pos_WorldSpace : TEXCOORD1;
};

// The data structure from the geometry shader to the pixel shader
struct GS_to_PS {
	float4 Pos : SV_POSITION;
	float2 Tex : TEXCOORD0;
	float3 Color : COLOR;
	float3 Normal_WorldSpace : NORMAL;
	float4 Pos_WorldSpace : TEXCOORD1;
	float4 Pos_CameraSpace : TEXCOORD2;
};

VS_to_GS VertexSmoothMiddle(VS_to_GS X, VS_to_GS Y, float Round);
GS_to_PS PGVS( float3 Pos_WorldSpace, float3 Normal_WorldSpace, float3 Color );
void MakeTile(float3 StartPos, float WallHeight_Center, float WallHeight_Left, float WallHeight_Right, float WallHeight_Up, float WallHeight_Down, float3 Color, inout TriangleStream<GS_to_PS> TriStream);

//--------------------------------------------------------------------------------------
// Pre geometry vertex Shader
// This is an entry point that is required by the engine
//--------------------------------------------------------------------------------------
VS_to_GS VS( VS_structure input ) {
	VS_to_GS output = (VS_to_GS)0;
	
	// Convert vertice positions from object space to world space
	output.Pos_WorldSpace = mul( input.Pos, ObjectToWorld );
	
	return output;
}

float4 GetTile(int X, int Y, int Width, int Height) {
	if (X < 0 || Y < 0 || X >= Width || Y >= Height) {
		return MapBorderColor;
	} else {
		return Map.Load(int3(X,Y,0));
	}
}

//--------------------------------------------------------------------------------------
// Geometry Shader
//--------------------------------------------------------------------------------------
#define ADD_VERTEX(P,N,C) TriStream.Append(PGVS(P,N,C));
#define COMPLETE_STRIP TriStream.RestartStrip();
[maxvertexcount(20)]
void GS( triangle VS_to_GS input[3], inout TriangleStream<GS_to_PS> TriStream ) {
	// Make a bump
	float3 A = input[0].Pos_WorldSpace.xyz;
	
	int X = round(A.x);
	int Y = round(A.z);
	uint Width; uint Height; uint Levels;
	Map.GetDimensions(0,Width,Height,Levels);
	float4 Tile_Center = GetTile(X,Y,Width,Height);
	float TileHeight_Down = GetTile(X,Y-1,Width,Height).a;
	float TileHeight_Up = GetTile(X,Y+1,Width,Height).a;
	float TileHeight_Left = GetTile(X-1,Y,Width,Height).a;
	float TileHeight_Right = GetTile(X+1,Y,Width,Height).a;
	
	// Tile
	MakeTile(A,Tile_Center.a,TileHeight_Left,TileHeight_Right,TileHeight_Up,TileHeight_Down,Tile_Center.rgb,TriStream);
}

void MakeTile(float3 StartPos, float WallHeight_Center, float WallHeight_Left, float WallHeight_Right, float WallHeight_Up, float WallHeight_Down, float3 Color, inout TriangleStream<GS_to_PS> TriStream) {
	// Assume size 1 on triangles to speed up calculations
	float3 A = StartPos;
	float3 B = A + float3(0.0f,0.0f,1.0f);
	float3 C = A + float3(1.0f,0.0f,0.0f);
	float3 D = A + float3(1.0f,0.0f,1.0f);
	float3 N;
	if (WallHeight_Center > WallHeight_Left) {
		// Left -X
		N = float3(-1.0f,0.0f,0.0f);
		ADD_VERTEX(A + float3(0.0f,WallHeight_Left,0.0f),N,Color)
		ADD_VERTEX(A + float3(0.0f,WallHeight_Left,1.0f),N,Color)
		ADD_VERTEX(A + float3(0.0f,WallHeight_Center,0.0f),N,Color)
		ADD_VERTEX(A + float3(0.0f,WallHeight_Center,1.0f),N,Color)
		COMPLETE_STRIP
	}

	if (WallHeight_Center > WallHeight_Right) {
		// Right +X
		N = float3(1.0f,0.0f,0.0f);
		ADD_VERTEX(A + float3(1.0f,WallHeight_Center,0.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Center,1.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Right,0.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Right,1.0f),N,Color)
		COMPLETE_STRIP
	}
	
	if (WallHeight_Center > WallHeight_Down) {
		// Down -Z
		N = float3(0.0f,0.0f,-1.0f);
		ADD_VERTEX(A + float3(0.0f,WallHeight_Down,0.0f),N,Color)
		ADD_VERTEX(A + float3(0.0f,WallHeight_Center,0.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Down,0.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Center,0.0f),N,Color)
		COMPLETE_STRIP
	}
	
	if (WallHeight_Center > WallHeight_Up) {
		// Up +Z
		N = float3(0.0f,0.0f,1.0f);
		ADD_VERTEX(A + float3(0.0f,WallHeight_Center,1.0f),N,Color)
		ADD_VERTEX(A + float3(0.0f,WallHeight_Up,1.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Center,1.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Up,1.0f),N,Color)
		COMPLETE_STRIP
	}
	
	if (WallHeight_Center > 0.01f) {
		// Roof
		N = float3(0.0f,1.0f,0.0f);
		ADD_VERTEX(A + float3(0.0f,WallHeight_Center,0.0f),N,Color)
		ADD_VERTEX(A + float3(0.0f,WallHeight_Center,1.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Center,0.0f),N,Color)
		ADD_VERTEX(A + float3(1.0f,WallHeight_Center,1.0f),N,Color)
		COMPLETE_STRIP
	}
}

//--------------------------------------------------------------------------------------
// Post geometry vertex shader
// This is not an entry point since we just call it from GS
//--------------------------------------------------------------------------------------
GS_to_PS PGVS( float3 Pos_WorldSpace, float3 Normal_WorldSpace, float3 Color ) {
	GS_to_PS output = (GS_to_PS)0;
	
	// Get the world space position
	output.Pos_WorldSpace = float4(Pos_WorldSpace,1.0f);
	// Convert vertice positions from world space to camera space
	output.Pos_CameraSpace = mul( output.Pos_WorldSpace, WorldToCamera );
	// Convert vertice positions from camera space to 2D projection
	output.Pos = mul( output.Pos_CameraSpace, CameraToImage );
	
	// Give the normal to the pixel shader
	output.Normal_WorldSpace = Normal_WorldSpace;
	
	// Give the texture coordinates to the pixel shader
	if (abs(Normal_WorldSpace.x) > abs(Normal_WorldSpace.y)) {
		if (abs(Normal_WorldSpace.x) > abs(Normal_WorldSpace.z)) {
			// X plane
			output.Tex = Pos_WorldSpace.zy;
		} else {
			// Z plane
			output.Tex = Pos_WorldSpace.xy;
		}
	} else {
		if (abs(Normal_WorldSpace.y) > abs(Normal_WorldSpace.z)) {
			// Y plane
			output.Tex = Pos_WorldSpace.xz;
		} else {
			// Z plane
			output.Tex = Pos_WorldSpace.xy;
		}
	}
	output.Tex = output.Tex * 0.25f;
	
	// Give the color
	output.Color = Color;
	
	return output;
}

#define CUBIC(X) ((3.0f * pow(X,2.0f)) - (2.0f * pow(X,3.0f)))

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS( GS_to_PS input) : SV_Target {
	// If the camera has a world space cutting plane, clip on the negative side.
	UseCuttingPlane(input.Pos_WorldSpace)
	
	// Diffuse texture
	float4 finalColor = DiffuseMap.Sample( samAnisotropicMipmap, input.Tex ) * float4(input.Color,1.0f);
	
	float3 DiffuseLight;
	float3 SpecularLight;
	float3 N = normalize(input.Normal_WorldSpace);
	Engine_GetDiffuseLight(input.Pos_WorldSpace.xyz,N,DiffuseLight);
	
	// Ambient occlusion by subtracting a part of the ambient light
	float GroundHeight;
	float Occlusion;
	int i;
	uint MapWidth; uint MapHeight; uint MapLevels;
	Map.GetDimensions(0,MapWidth,MapHeight,MapLevels);
	Occlusion = 0.0f;
	float3 AbsN = abs(N);
		float2 FlatPos = input.Pos_WorldSpace.xz;
		float Depth = input.Pos_WorldSpace.y;
		int2 LowPos = floor(FlatPos - 0.5f);
		int2 HiPos = LowPos + 1;
		float2 FracPos = FlatPos - 0.5f - LowPos;
		float2 SmoothPos = CUBIC(FracPos);
	if (AbsN.y > AbsN.x && AbsN.y > AbsN.z) {
		//xz
		float UpperLeftOffset = saturate(GetTile(LowPos.x,HiPos.y,MapWidth,MapHeight).a - Depth);
		float UpperRightOffset = saturate(GetTile(HiPos.x,HiPos.y,MapWidth,MapHeight).a - Depth);
		float LowerLeftOffset = saturate(GetTile(LowPos.x,LowPos.y,MapWidth,MapHeight).a - Depth);
		float LowerRightOffset = saturate(GetTile(HiPos.x,LowPos.y,MapWidth,MapHeight).a - Depth);
		Occlusion = lerp(lerp(LowerLeftOffset,LowerRightOffset,SmoothPos.x),lerp(UpperLeftOffset,UpperRightOffset,SmoothPos.x),SmoothPos.y);
	} else if (AbsN.x > AbsN.z) {
		//yz
		if (N.x > 0.0f) {
			float UpperRightOffset = saturate(GetTile(HiPos.x,HiPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			float LowerRightOffset = saturate(GetTile(HiPos.x,LowPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			Occlusion = lerp(LowerRightOffset,UpperRightOffset,SmoothPos.y);
		} else {
			float UpperLeftOffset = saturate(GetTile(LowPos.x,HiPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			float LowerLeftOffset = saturate(GetTile(LowPos.x,LowPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			Occlusion = lerp(LowerLeftOffset,UpperLeftOffset,SmoothPos.y);
		}
	} else {
		//xy
		if (N.z > 0.0f) {
			float UpperLeftOffset = saturate(GetTile(LowPos.x,HiPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			float UpperRightOffset = saturate(GetTile(HiPos.x,HiPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			Occlusion = lerp(UpperLeftOffset,UpperRightOffset,SmoothPos.x);
		} else {
			float LowerLeftOffset = saturate(GetTile(LowPos.x,LowPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			float LowerRightOffset = saturate(GetTile(HiPos.x,LowPos.y,MapWidth,MapHeight).a - Depth + 0.5f);
			Occlusion = lerp(LowerLeftOffset,LowerRightOffset,SmoothPos.x);
		}
	}
	Occlusion = pow(Occlusion,2.0f) * 2.0f;
	
	DiffuseLight = DiffuseLight - (Occlusion * AmbientLight);
	
	finalColor = (finalColor * float4(DiffuseLight,1.0f));
	
	// Show the final color with fog on R,G,B channels
	return float4((lerp(finalColor,FogColor,saturate(length(input.Pos_CameraSpace) / FogDistance) * MaxFogIntensity)).xyz,finalColor.w);
}
