cbuffer ConstantBuffer
{
    float4x4 completeTransformation;
    float4x4 worldTransformation;    
	float4 cameraPosition;
    float4 lightVector;			// the light's vector
    float4 lightColor;			// the light's color
    float4 ambientColor;		// the ambient light's color
    float4 diffuseCoefficient;	// The diffuse reflection cooefficient
	float4 specularCoefficient;	// The specular reflection cooefficient
	float  shininess;			// The shininess factor
	float  opacity;				// The opacity (transparency) of the material. 0 = fully transparent, 1 = fully opaque

	// Vars to deal with the water, also padding because the cbuffer needs to be a multiple of 16
	float waterHeight;
	float waterShininess;
	float4 waterColor;
	
	// Padded with a float array, floats being 4 bytes each, the above just hits the multiple of 16 but added extra incase this was the issue, will remove later most likely
	float padding[4];
}

Texture2D BlendMap : register(t0);
Texture2DArray TexturesArray : register(t1);
Texture2D WaterNormalMap : register(t2);
Texture2D snowTest : register(t3);

SamplerState ss
{
	Filter = MIN_MAG_MIP_LINEAR;

	AddressU = WRAP;
	AddressV = WRAP;
};

struct VertexShaderInput
{
	float3 Position : POSITION;
	float3 Normal : NORMAL;
	float2 TexCoord : TEXCOORD0;
	float2 BlendMapTexCoord : TEXCOORD1;
};

struct PixelShaderInput
{
	float4 Position : SV_POSITION;
	float4 PositionWS: TEXCOORD2;
	float4 PositionOG: TEXCOORD4;
	float4 NormalWS : TEXCOORD3;
	float2 TexCoord : TEXCOORD0;
	float2 BlendMapTexCoord : TEXCOORD1;
};

float2 UVRotate(float2 uvCoord, float2 pivotPoint, float rotation)
{
	float2x2 rotateMatrix = float2x2(float2(sin(rotation), -cos(rotation)), float2(cos(rotation), sin(rotation)));

	uvCoord -= pivotPoint;
	uvCoord = mul(uvCoord, rotateMatrix);
	uvCoord += pivotPoint;

	return uvCoord;

}

PixelShaderInput VShader(VertexShaderInput vin)
{
    PixelShaderInput output;	
	float3 position = vin.Position;
	output.PositionOG = output.PositionWS = mul(worldTransformation, float4(position, 1.0f));
	output.TexCoord = vin.TexCoord;
	// Check if the y pos is lower than the water point, and set the position to it minus a tiny offset because the textures were clipping and causing a weird effect
	if (position.y < waterHeight)
	{
		position.y = waterHeight;						
	}
	output.Position = mul(completeTransformation, float4(position, 1.0f));
	output.PositionWS = mul(worldTransformation, float4(position, 1.0f));
	output.NormalWS = float4(mul((float3x3)worldTransformation, vin.Normal), 1.0f);
	
	output.BlendMapTexCoord = vin.BlendMapTexCoord;
	return output;
}

float4 hash4(float2 v)
{
	float4 p = mul(float4x2(127.1, 311.7, 269.5, 183.3, 113.5, 271.9, 246.1, 124.6), v);
	return frac(sin(p) * 43758.5453123);
}

float4 PShader(PixelShaderInput input) : SV_TARGET
{
	float4 directionToCamera = normalize(input.PositionWS - cameraPosition);
	float4 directionToLight = normalize(-lightVector);
	float surfaceShininess = shininess;
	float4 adjustedNormal = normalize(input.NormalWS);

	float currentHeightPer = (waterHeight - input.PositionOG.y) / waterHeight;

	if (input.PositionOG.y < waterHeight)
	{
		// Sample the normal from the water normal map and calculate it with the worldtransform
		float3 n0 = 2.0f * (float3)WaterNormalMap.Sample(ss, input.TexCoord) - 1.0f;
		float4 n2 = float4(mul((float3x3)worldTransformation, n0), 1.0f);
		adjustedNormal = normalize(n2);
		surfaceShininess = waterShininess;
	}

	// Calculate diffuse lighting
	float NdotL = max(0, dot(adjustedNormal, directionToLight));
	float4 diffuse = saturate(lightColor * NdotL * diffuseCoefficient);
	diffuse.a = 1.0f;

	// Calculate specular component
	float4 R = 2 * NdotL * adjustedNormal - directionToLight;
	float RdotV = max(0, dot(R, directionToCamera));
	float4 specular = saturate(lightColor * pow(RdotV, surfaceShininess) * specularCoefficient);
	specular.a = 1.0f;

	// Calculate ambient lighting
	float4 ambientLight = ambientColor * diffuseCoefficient;
	float4 color;
	
	// Randomly rotate the UV coordinates using the noise map
	float2 scaleCenter = float2(0.5f, 0.5f);
	// The noise map is greyscale so we only need to check 1 channel since they should all be the same value
		
	float2 scaledUV = frac(UVRotate(input.TexCoord, scaleCenter, input.BlendMapTexCoord.x)); // (input.TexCoord - scaleCenter) * currentScale + scaleCenter;

	float2 xChange = ddx(scaledUV);
	float2 yChange = ddy(scaledUV);

	/*if (xChange.x < 0)
	{
		xChange = float2(0, 0);
	}
	if (yChange.y < 0)
	{
		yChange = float2(0, 0);
	}*/

	// Sample layers in texture array.		
	float4 c0 = TexturesArray.SampleGrad(ss, float3(scaledUV, 0.0f), xChange, yChange);
	float4 c1 = TexturesArray.SampleGrad(ss, float3(scaledUV, 1.0f), xChange, yChange);
	float4 c2 = TexturesArray.SampleGrad(ss, float3(scaledUV, 2.0f), xChange, yChange);
	float4 c3 = TexturesArray.SampleGrad(ss, float3(scaledUV, 3.0f), xChange, yChange);
	//float4 c4 = TexturesArray.SampleGrad(ss, float3(scaledUV, 4.0f), xChange, yChange);
	float4 c4 = snowTest.SampleGrad(ss, float2(scaledUV), xChange, yChange);	

	// Sample the blend map.
	float4 t = BlendMap.Sample(ss, input.BlendMapTexCoord);	

	// Blend the layers on top of each other.
	color = c0;
	color = lerp(color, c1, t.r);
	color = lerp(color, c2, t.g);
	color = lerp(color, c3, t.b);
	color = lerp(color, c4, t.a);

	// Combine all components
	if (input.PositionOG.y < waterHeight)
	{
		// Tint the water		
		color = saturate(color) * waterColor;
	}
	color = (ambientLight + diffuse) * color;
	color = saturate(color + specular);		
	return color;
}