
// You may modify this to move more data from the vertex shader to the pixel shader.
// It needs at least the position to work.
struct VS_to_PS {
	float4 Pos : SV_POSITION;
	float4 Tex : TEXCOORD0;
	float4 Tex2 : TEXCOORD1;
	float4 Pos_WorldSpace : TEXCOORD2;
	float4 Pos_CameraSpace : TEXCOORD3;
};

// Arguments
#define HeightMapScale Arg[0].xy
#define HeightMapOffset Arg[0].zw
#define DiffuseScale Arg[1].xy
#define BumpMapAmplitude Arg[1].z

// Textures
#define HeightAndBlendMap tex_0
#define RoadMap tex_1
#define DiffuseMap1 tex_2
#define DiffuseMap2 tex_3
#define DiffuseMap3 tex_4
#define SideMap tex_5
#define TileAtlas tex_6
	#define Tile_LargeRoad_MIN_UV float2(0.0f,0.0f)
	#define Tile_LargeRoad_MAX_UV float2(0.5f,1.0f)
	#define Tile_SmallRoad_MIN_UV float2(0.5f,0.02f)
	#define Tile_SmallRoad_MAX_UV float2(1.0f,0.98f)
#define RoadTexture tex_7

// Texture coordinates
#define UV1 input.Tex.xy
#define UV2 input.Tex.zw
#define UV3 input.Tex2.xy
#define UV4 input.Tex2.zw

//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VS_to_PS VS( VS_structure input ) {
	VS_to_PS output = (VS_to_PS)0;
	
	// Convert vertice positions from object space to world space
	output.Pos_WorldSpace = mul( input.Pos, ObjectToWorld );
	
	// Give the texture coordinates to the pixel shader
	output.Tex = float4((output.Pos_WorldSpace.xz / HeightMapScale) + HeightMapOffset, output.Pos_WorldSpace.xz / DiffuseScale);
	
	// Apply offset
	float4 HeightMap = tex_0.SampleLevel(samClampedConstant,output.Tex.xy,0);
	output.Pos_WorldSpace.y = output.Pos_WorldSpace.y + HeightMap.r;
	
	// Give more texture coordinates to the pixel shader
	output.Tex2 = float4(output.Pos_WorldSpace.xy / DiffuseScale, output.Pos_WorldSpace.yz / DiffuseScale);
	
	// 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 );
	
	return output;
}

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS( VS_to_PS input) : SV_Target {
	// If the camera has a world space cutting plane, clip on the negative side.
	UseCuttingPlane(input.Pos_WorldSpace)
	
	// Sample the height map with some extra data
	float4 HeightMap = HeightAndBlendMap.Sample( samClampedSharp, UV1 );
	
	//Get the bump map dimensions and calculate the offsets needed when deriving normals
	uint Width; uint Height; uint Levels;
	HeightAndBlendMap.GetDimensions(0,Width,Height,Levels);
	float DeriveOffsetU = 0.5f / (float)Width;
	float DeriveOffsetV = 0.5f / (float)Height;
	
	//Derive the normal by using the heightmap as a bump map
	float DU = HeightAndBlendMap.Sample( samClampedSharp, UV1 + float2(-DeriveOffsetU,0) ).r - HeightAndBlendMap.Sample( samClampedSharp, UV1 + float2(DeriveOffsetU,0) ).r;
	float DV = HeightAndBlendMap.Sample( samClampedSharp, UV1 + float2(0,-DeriveOffsetV) ).r - HeightAndBlendMap.Sample( samClampedSharp, UV1 + float2(0,DeriveOffsetV) ).r;
	float3 Normal = normalize(float3(DU * BumpMapAmplitude, 1.0f, DV * BumpMapAmplitude));
	
	float BlendSum = HeightMap.g + HeightMap.b + HeightMap.a;
	
	// Blend diffuse colors
	float4 Diffuse1 = DiffuseMap1.Sample( samAnisotropicMipmap, UV2 );
	float4 Diffuse2 = DiffuseMap2.Sample( samAnisotropicMipmap, UV2 );
	float4 Diffuse3 = DiffuseMap3.Sample( samAnisotropicMipmap, UV2 );
	float SideToDiffuseBlend = saturate(lerp(-2.0f, 2.0f, Normal.y));
	float3 SideBlend = abs(Normal);
	SideBlend = SideBlend / (SideBlend.x + SideBlend.y + SideBlend.z);
	float4 SideMapX = SideMap.Sample( samAnisotropicMipmap, UV4 );
	float4 SideMapY = SideMap.Sample( samAnisotropicMipmap, UV2 );
	float4 SideMapZ = SideMap.Sample( samAnisotropicMipmap, UV3 );
	float4 SideMap = ((SideBlend.x * SideMapX) + (SideBlend.y * SideMapY) + (SideBlend.z * SideMapZ)) * BlendSum;
	float4 FinalColor = lerp(SideMap,(Diffuse1 * HeightMap.g) + (Diffuse2 * HeightMap.b) + (Diffuse3 * HeightMap.a),SideToDiffuseBlend);
	
	// Get the dimensions of the road map
	uint RoadMap_Width; uint RoadMap_Height; uint RoadMap_Levels;
	RoadMap.GetDimensions(0,RoadMap_Width,RoadMap_Height,RoadMap_Levels);
	
	if (BlendSum > 0.9f) {
		// Draw roads
		float4 RoadTex = RoadTexture.Sample( samAnisotropicMipmap, UV2 );
		if (UV1.x > 0.0f && UV1.x < 1.0f && UV1.y > 0.0f && UV1.y < 1.0f) {
			float2 RoadPixel = float2(UV1.x * RoadMap_Width,UV1.y * RoadMap_Height);
			uint LX = floor(RoadPixel.x);
			uint HX = LX + 1;
			uint LY = floor(RoadPixel.y);
			uint HY = LY + 1;
			float4 Road_LXLY = RoadMap.Load(int3(LX,LY,0));
			float4 Road_LXHY = RoadMap.Load(int3(LX,HY,0));
			float4 Road_HXLY = RoadMap.Load(int3(HX,LY,0));
			float4 Road_HXHY = RoadMap.Load(int3(HX,HY,0));
			
			// Draw large roads
			if (Road_LXLY.b > -0.5f && Road_LXHY.b > -0.5f && Road_HXLY.b > -0.5f && Road_HXHY.b > -0.5f) {
				float4 RoadColor;
				float2 FracRP = frac(RoadPixel);
				float4 Road;
				if ((Road_LXLY.b > 0.4f && Road_LXHY.b > 0.4f && Road_HXLY.b > 0.4f && Road_HXHY.b > 0.4f) || (Road_LXLY.b < 0.6f && Road_LXHY.b < 0.6f && Road_HXLY.b < 0.6f && Road_HXHY.b < 0.6f)) {
					// Interpolated UV
					Road = lerp(lerp(Road_LXLY,Road_HXLY,FracRP.x),lerp(Road_LXHY,Road_HXHY,FracRP.x),FracRP.y);
					float2 RoadUV = lerp(Tile_LargeRoad_MIN_UV,Tile_LargeRoad_MAX_UV,Road.ba);
					RoadColor = TileAtlas.Sample( samAnisotropicMipmap, RoadUV );
				} else {
					// Interpolate color
					float4 RoadColor_LXLY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_LargeRoad_MIN_UV,Tile_LargeRoad_MAX_UV,Road_LXLY.xy) );
					float4 RoadColor_LXHY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_LargeRoad_MIN_UV,Tile_LargeRoad_MAX_UV,Road_LXHY.xy) );
					float4 RoadColor_HXLY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_LargeRoad_MIN_UV,Tile_LargeRoad_MAX_UV,Road_HXLY.xy) );
					float4 RoadColor_HXHY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_LargeRoad_MIN_UV,Tile_LargeRoad_MAX_UV,Road_HXHY.xy) );
					RoadColor = lerp(lerp(RoadColor_LXLY,RoadColor_HXLY,FracRP.x),lerp(RoadColor_LXHY,RoadColor_HXHY,FracRP.x),FracRP.y);
				}
				if (BlendSum < 0.99f) {
					RoadColor.rgb = min(RoadColor.rgb,float3(0.3f,0.3f,0.3f));
				}
				FinalColor = lerp(FinalColor,float4(RoadColor.rgb * RoadTex * BlendSum, 1.0f),RoadColor.a);
			}
			
			// Draw small roads
			if (Road_LXLY.r > -0.5f && Road_LXHY.r > -0.5f && Road_HXLY.r > -0.5f && Road_HXHY.r > -0.5f) {
				float4 RoadColor;
				float2 FracRP = frac(RoadPixel);
				float4 Road;
				if ((Road_LXLY.r > 0.4f && Road_LXHY.r > 0.4f && Road_HXLY.r > 0.4f && Road_HXHY.r > 0.4f) || (Road_LXLY.r < 0.6f && Road_LXHY.r < 0.6f && Road_HXLY.r < 0.6f && Road_HXHY.r < 0.6f)) {
					// Interpolated UV
					Road = lerp(lerp(Road_LXLY,Road_HXLY,FracRP.x),lerp(Road_LXHY,Road_HXHY,FracRP.x),FracRP.y);
					float2 RoadUV = lerp(Tile_SmallRoad_MIN_UV,Tile_SmallRoad_MAX_UV,Road.rg);
					RoadColor = TileAtlas.Sample( samAnisotropicMipmap, RoadUV );
				} else {
					// Interpolate color
					float4 RoadColor_LXLY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_SmallRoad_MIN_UV,Tile_SmallRoad_MAX_UV,Road_LXLY.xy) );
					float4 RoadColor_LXHY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_SmallRoad_MIN_UV,Tile_SmallRoad_MAX_UV,Road_LXHY.xy) );
					float4 RoadColor_HXLY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_SmallRoad_MIN_UV,Tile_SmallRoad_MAX_UV,Road_HXLY.xy) );
					float4 RoadColor_HXHY = TileAtlas.Sample( samAnisotropicMipmap, lerp(Tile_SmallRoad_MIN_UV,Tile_SmallRoad_MAX_UV,Road_HXHY.xy) );
					RoadColor = lerp(lerp(RoadColor_LXLY,RoadColor_HXLY,FracRP.x),lerp(RoadColor_LXHY,RoadColor_HXHY,FracRP.x),FracRP.y);
				}
				if (BlendSum < 0.99f) {
					RoadColor.rgb = min(RoadColor.rgb,float3(0.3f,0.3f,0.3f));
				}
				FinalColor = lerp(FinalColor,float4(RoadColor.rgb * RoadTex * BlendSum, 1.0f),RoadColor.a);
			}
		}
	}
	
	// Use the engine's light system
	float3 DiffuseLight;
	Engine_GetDiffuseLight(input.Pos_WorldSpace.xyz,Normal,DiffuseLight);
	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);
}
