
// A diffuse map at tex_0
// A tangent space normal map at tex_1.rgb
// A bump map as tex_1.a

// 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 Color : COLOR;
	float3 Normal_WorldSpace : NORMAL;
	float4 Pos_WorldSpace : TEXCOORD1;
	float4 Pos_CameraSpace : TEXCOORD2;
	float3 UAxis_WorldSpace : TEXCOORD3;
	float3 VAxis_WorldSpace : TEXCOORD4;
	float3 CUAxis_WorldSpace : TEXCOORD5;
	float3 CVAxis_WorldSpace : TEXCOORD6;
	float2 TangentLengths : TEXCOORD7;
	float Depth : TEXCOORD8;
};

//--------------------------------------------------------------------------------------
// 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 );
	// 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 );
	
	// Transform normals and tangent space from object space to world space
	output.Normal_WorldSpace = mul(input.Normal,(float3x3)ObjectToWorld);
	output.UAxis_WorldSpace = mul(input.A.xyz,(float3x3)ObjectToWorld);
	output.VAxis_WorldSpace = mul(input.B.xyz,(float3x3)ObjectToWorld);
	
	output.TangentLengths = float2(length(input.A.xyz),length(input.B.xyz));
	
	output.CUAxis_WorldSpace = normalize(cross(output.VAxis_WorldSpace,output.Normal_WorldSpace));
	if (dot(output.CUAxis_WorldSpace,output.UAxis_WorldSpace) < 0) {
		output.CUAxis_WorldSpace = -output.CUAxis_WorldSpace;
	}
	
	output.CVAxis_WorldSpace = normalize(cross(output.Normal_WorldSpace,output.UAxis_WorldSpace));
	if (dot(output.CVAxis_WorldSpace,output.VAxis_WorldSpace) < 0) {
		output.CVAxis_WorldSpace = -output.CVAxis_WorldSpace;
	}
	
	output.Depth = input.A.w;
	
	// Give the texture coordinates to the pixel shader
	output.Tex = input.Tex;
	
	// Give the vertex color multiplied with the instance color to the pixel shader
	output.Color = lerp(input.Color * InstanceColor, float4(0.0f,0.0f,1.0f,1.0f), input.Selected);
	
	return output;
}

#define LinearSteps 64 // How many steps will we at most spend on the linear raytracing
#define BisectionSteps 5 // How many steps will be spend on the bisection method

#define MaxRealOffset 0.2f // Where should the offset start to become flat
#define MaxOffset 0.3f // What is the maximum amount of offset

//--------------------------------------------------------------------------------------
// 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)
	
	float3 RelativeCameraPosition = (CameraPos_WorldSpace - input.Pos_WorldSpace.xyz);
	
	float FrontOfNormal = max(0.001f,dot(normalize(input.Normal_WorldSpace),RelativeCameraPosition));
	
	float2 ParallaxTextureOffset = float2(
		dot(input.CUAxis_WorldSpace,RelativeCameraPosition) / input.TangentLengths.x,
		dot(input.CVAxis_WorldSpace,RelativeCameraPosition) / input.TangentLengths.y
		) * max(0.000001f,input.Depth) / -FrontOfNormal;
	
	// Get the length of the offset
	float OffsetLength = length(ParallaxTextureOffset);
	
	// Limit the length of the offset using a linear, quadratic and constant section
	if (OffsetLength < MaxRealOffset) {
		// Let the real length of the offset remain
		// OffsetLength = OffsetLength;
	} else if (OffsetLength > (MaxOffset * 2) - MaxRealOffset){
		// Limit to MaxOffset
		OffsetLength = MaxOffset;
	} else {
		// Fade from MaxRealOffset to MaxOffset using a quadratic curve
		OffsetLength = (OffsetLength - MaxRealOffset) - pow((OffsetLength - MaxRealOffset),2.0f) / ((MaxOffset - MaxRealOffset) * 4) + MaxRealOffset;
	}
	
	// Assign the limited length
	float2 LimitedOffset = normalize(ParallaxTextureOffset) * OffsetLength;
	
	float2 DeepUVCoordinate = float2(0,0);
	float LastDepth = 0.0f;
	float Depth = 0.0f;
	int Contact = 0;
	int I = 1;
	[fastopt]while (Contact == 0 && I <= LinearSteps) {
		LastDepth = Depth;
		Depth = ((float)I) / ((float)LinearSteps);
		DeepUVCoordinate = input.Tex.xy + (LimitedOffset * Depth);
		Contact = ((1.0f - tex_1.SampleLevel(samSharp,DeepUVCoordinate,0).a) < Depth);
		I++;
	}
	float MinDepth = LastDepth;
	float MaxDepth = Depth;
	I = 0;
	while (I < BisectionSteps) {
		Depth = lerp(MinDepth,MaxDepth,0.5f);
		DeepUVCoordinate = input.Tex.xy + (LimitedOffset * Depth);
		if ((1.0f - tex_1.SampleLevel(samSharp,DeepUVCoordinate,0).a) < Depth) {
			// Contact
			MaxDepth = Depth;
		} else {
			// No contact
			MinDepth = Depth;
		}
		I++;
	}
	Depth = lerp(MinDepth,MaxDepth,0.5f);
	
	float4 finalColor = tex_0.Sample( samAnisotropicMipmap, DeepUVCoordinate ) * input.Color;
	
	float3x3 TangentSpace = float3x3(normalize(input.UAxis_WorldSpace),normalize(input.VAxis_WorldSpace),normalize(input.Normal_WorldSpace));
	float3 FinalNormal = normalize(mul((tex_1.Sample( samAnisotropicMipmap, DeepUVCoordinate ).xyz * 2.0f) - 1.0f,TangentSpace));
	
	float3 DiffuseLight;
	float3 SpecularLight;
	Engine_GetDiffuseAndSpecularLight(input.Pos_WorldSpace,FinalNormal,30,DiffuseLight,SpecularLight);
	// finalColor = (finalColor * float4(DiffuseLight,1.0f)) + float4(SpecularLight,1.0f);
	finalColor = (finalColor * float4(DiffuseLight + SpecularLight,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);
}
