//TerrainGenerator.fx 
//FirstName: Simon
//LastName: Coenen 
//Class: 2DAE1

//Constant values
cbuffer cbPerObject
{
	float4x4 gMatrixWVP : WORLDVIEWPROJECTION;
	float4x4 gMatrix : WORLD;
	float3 gLightDirection = float3(0.2f, -1.0f, 0.2f);
}

//An atlas of all block textures
Texture2D gTexture;
//Size (2 * width) of a block
float gCubeSize = 1.0f;
//The thresholds for each type of block
float gTypeValues[8] = {0.2f, 0.25f, 0.40f, 0.50f, 0.75f, 0.8f, 0.85f, 1.0f};
//The amount of different types of block
int gTypes = 8;
//The maximum height of the terrain ([0, 1] --> [0, gMaxHeight])
float gMaxHeight = 30.0f;
//The amount of images per block (top, side and bottom)
int gSides = 3;

SamplerState samPoint
{
	Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

struct VS_DATA
{
	float3 Position : POSITION;
};

struct GS_DATA
{
	float4 Position : SV_POSITION;
	float3 Normal : NORMAL;
	float2 TexCoord : TEXCOORD0;
};

DepthStencilState EnableDepth
{
	DepthEnable = TRUE;
	DepthWriteMask = ALL;
};

RasterizerState BackfaceCulling
{
	CullMode = BACK;
};

void CreateVertex(inout TriangleStream<GS_DATA> triStream, float3 pos, float3 normal, float2 TexCoord)
{
	GS_DATA data = (GS_DATA)0;
	//Transform point with the MatrixWVP
	data.Position = mul(float4(pos, 1.0f), gMatrixWVP);
	//Transform normal into world space (No translation!)
	data.Normal = normalize(mul(normal, (float3x3)gMatrix));
	//Transfer texture coordinates
	data.TexCoord = TexCoord;
	//Append data to the triangle stream
	triStream.Append(data);
}

void CreateCube(inout TriangleStream<GS_DATA> triStream, float3 cp, float typeOffset)
{
	//Cube representation
	//	   e-------f
	//	  /|      /|
	//	 / |     / |
	//	a--|----b  |
	//	|  g----|--h
	//	| /     | /
	//	c-------d

	//Set the offsets for the texture sampling
	float dx = 1.0f / gSides;
	float dy = 1.0f / gTypes;

	typeOffset *= dy;

	//Calculate each points (position)
	float3 a = float3(cp.x - gCubeSize / 2.0f, cp.y + gCubeSize / 2.0f, cp.z - gCubeSize / 2.0f);
	float3 b = float3(cp.x + gCubeSize / 2.0f, cp.y + gCubeSize / 2.0f, cp.z - gCubeSize / 2.0f);
	float3 c = float3(cp.x - gCubeSize / 2.0f, cp.y - gCubeSize / 2.0f, cp.z - gCubeSize / 2.0f);
	float3 d = float3(cp.x + gCubeSize / 2.0f, cp.y - gCubeSize / 2.0f, cp.z - gCubeSize / 2.0f);

	float3 e = float3(cp.x - gCubeSize / 2.0f, cp.y + gCubeSize / 2.0f, cp.z + gCubeSize / 2.0f);
	float3 f = float3(cp.x + gCubeSize / 2.0f, cp.y + gCubeSize / 2.0f, cp.z + gCubeSize / 2.0f);
	float3 g = float3(cp.x - gCubeSize / 2.0f, cp.y - gCubeSize / 2.0f, cp.z + gCubeSize / 2.0f);
	float3 h = float3(cp.x + gCubeSize / 2.0f, cp.y - gCubeSize / 2.0f, cp.z + gCubeSize / 2.0f);

	//Create all the vertices (position, normal, texture coordinates)
	
	//RIGHT
	CreateVertex(triStream, d, float3(1.0f, 0.0f, 0.0f), float2(0.0f, dy + typeOffset));
	CreateVertex(triStream, b, float3(1.0f, 0.0f, 0.0f), float2(0.0f, typeOffset));
	CreateVertex(triStream, h, float3(1.0f, 0.0f, 0.0f), float2(dx, dy + typeOffset));
	CreateVertex(triStream, f, float3(1.0f, 0.0f, 0.0f), float2(dx, typeOffset));
	triStream.RestartStrip();

	//TOP
	CreateVertex(triStream, e, float3(0.0f, 1.0f, 0.0f), float2(dx, typeOffset));
	CreateVertex(triStream, f, float3(0.0f, 1.0f, 0.0f), float2(dx * 2, typeOffset));
	CreateVertex(triStream, a, float3(0.0f, 1.0f, 0.0f), float2(dx, dy + typeOffset));
	CreateVertex(triStream, b, float3(0.0f, 1.0f, 0.0f), float2(dx * 2, dy + typeOffset));
	triStream.RestartStrip();

	//LEFT
	CreateVertex(triStream, g, float3(-1.0f, 0.0f, 0.0f), float2(0.0f, dy + typeOffset));
	CreateVertex(triStream, e, float3(-1.0f, 0.0f, 0.0f), float2(0.0f, typeOffset));
	CreateVertex(triStream, c, float3(-1.0f, 0.0f, 0.0f), float2(dx, dy + typeOffset));
	CreateVertex(triStream, a, float3(-1.0f, 0.0f, 0.0f), float2(dx, typeOffset));
	triStream.RestartStrip();

	//BACK
	CreateVertex(triStream, f, float3(0.0f, 0.0f, 1.0f), float2(0.0f, typeOffset));
	CreateVertex(triStream, e, float3(0.0f, 0.0f, 1.0f), float2(dx, typeOffset));
	CreateVertex(triStream, h, float3(0.0f, 0.0f, 1.0f), float2(0.0f, dy + typeOffset));
	CreateVertex(triStream, g, float3(0.0f, 0.0f, 1.0f), float2(dx, dy + typeOffset));
	triStream.RestartStrip();

	//FRONT
	CreateVertex(triStream, a, float3(0.0f, 0.0f, -1.0f), float2(0.0f, typeOffset));
	CreateVertex(triStream, b, float3(0.0f, 0.0f, -1.0f), float2(dx, typeOffset));
	CreateVertex(triStream, c, float3(0.0f, 0.0f, -1.0f), float2(0.0, dy + typeOffset));
	CreateVertex(triStream, d, float3(0.0f, 0.0f, -1.0f), float2(dx, dy + typeOffset));
	triStream.RestartStrip();

	//BOTTOM: not really nessecary
	//CreateVertex(triStream, h, float3(0.0f, -1.0f, 0.0f), float2(3 * dx, dy + typeOffset));
	//CreateVertex(triStream, g, float3(0.0f, -1.0f, 0.0f), float2(2 * dx, dy + typeOffset));
	//CreateVertex(triStream, d, float3(0.0f, -1.0f, 0.0f), float2(3 * dx, typeOffset));
	//CreateVertex(triStream, c, float3(0.0f, -1.0f, 0.0f), float2(2 * dx, typeOffset));
	//triStream.RestartStrip();
}

[maxvertexcount(20)]
void CubeGenerator(point VS_DATA vertex[1], inout TriangleStream<GS_DATA> triStream)
{
	//Get the center point of the cube
	float3 center = vertex[0].Position;
	//Store the height [0, 1] for later use
	float height = center.y;

	//Transform the center at its according position
	center.x *= gCubeSize;
	center.z *= gCubeSize;
	//Y-value [0, 1] has to be on the grid too
	center.y = (int)(height * gMaxHeight / gCubeSize) * gCubeSize;

	//Get the texturecoordinate offset according to the height of the center
	float typeOffset = gTypes - 1;
	for (int i = 0; i < gTypes; ++i)
	{
		if (height < gTypeValues[i])
		{
			typeOffset = i;
			break;
		}
	}

	//Finally, create the cube with all the collected data
	CreateCube(triStream, center, typeOffset);
}

VS_DATA VS(VS_DATA vs_data)
{
	return vs_data;
}

float4 PS_HalfLambert(GS_DATA input) : SV_TARGET
{
	//Sample from the diffuse map
	float4 colorSample = gTexture.Sample(samPoint, input.TexCoord);
	float3 color_rgb = colorSample.rgb;
	float color_a = colorSample.a;

	//Half Lambert diffuse
	float diffuseStrength = dot(input.Normal, -gLightDirection);
	diffuseStrength = diffuseStrength * 0.5 + 0.5;
	diffuseStrength = saturate(diffuseStrength);
	color_rgb = color_rgb * diffuseStrength;

	return float4(color_rgb, color_a);
}

float4 PS_Default(GS_DATA input) : SV_TARGET
{
	//Sample from the diffuse map
	float4 colorSample = gTexture.Sample(samPoint, input.TexCoord);
	float3 color_rgb = colorSample.rgb;
	float color_a = colorSample.a;

	//Lambert diffuse
	float diffuseStrength = dot(input.Normal, -gLightDirection);
	diffuseStrength = saturate(diffuseStrength);
	color_rgb = color_rgb * diffuseStrength;

	return float4(color_rgb, color_a);
}

technique11 Default
{
    pass P0
    {
		SetRasterizerState(BackfaceCulling);
		SetDepthStencilState(EnableDepth, 0);

		SetVertexShader( CompileShader( vs_4_0, VS() ) );
		SetGeometryShader( CompileShader(gs_4_0, CubeGenerator()) );
		SetPixelShader( CompileShader( ps_4_0, PS_Default() ) );
    }
}

technique11 HalfLambert
{
	pass P0
	{
		SetRasterizerState(BackfaceCulling);
		SetDepthStencilState(EnableDepth, 0);

		SetVertexShader(CompileShader(vs_4_0, VS()));
		SetGeometryShader(CompileShader(gs_4_0, CubeGenerator()));
		SetPixelShader(CompileShader(ps_4_0, PS_HalfLambert()));
	}
}