#ifndef metalic_roughness_base_h_ixray_included
#define metalic_roughness_base_h_ixray_included

#include "common.hlsli"
#define PI 3.141592653589793f

struct IXrayMaterial
{
	float Metalness;
	float Roughness;

	float3 Normal;
	float3 Point;
	float4 Color;

	float Depth;

	float Hemi;
	float Sun;

    float SSS;
    float AO;
	
	float SnowMask;
};

struct IXrayGbufferPack
{
	float4 Color : SV_Target0;

	float4 Normal : SV_Target1;
	float4 Material : SV_Target2;

	float2 Velocity : SV_Target3;
};

struct IXrayGbuffer
{
	float Metalness;
	float Roughness;

	float3 Normal;
	float3 Color;
	float3 F0;

	float Depth;
	float Hemi;

	float3 Point;
	float3 PointHud;
	float3 PointReal;

	float3 View;
	float ViewDist;

	float SSS;
	float AO;
	float SnowMask;
};

float2 PackNormalVector(float3 Vector) {
	float PackedZ = 0.5f + 0.5f * Vector.z;
	float Scale = rcp(dot(Vector.xy, Vector.xy));
	return Vector.xy * sqrt(PackedZ * Scale);
}

float3 UnPackNormalVector(float2 Packed) {
	float PackedZ = dot(Packed, Packed);
	
	float3 Vector;
	
	Vector.z = PackedZ * 2.0f - 1.0f;
	Vector.xy = Packed * sqrt(1.0f - PackedZ) * 2.0f;
	
	return Vector;
}

float2 NormalEncode(float3 Normal)
{
    Normal *= rcp(abs(Normal.x) + abs(Normal.y) + abs(Normal.z));
    float Shift = saturate(-Normal.z);
    Normal.xy += Normal.xy > 0.0f ? Shift : -Shift;

    return Normal.xy * 0.5f + 0.5f;
}

float3 NormalDecode(float2 InNormal)
{
    InNormal = InNormal * 2.0f - 1.0f;

    float3 Normal = float3(InNormal, 1.0f - abs(InNormal.x) - abs(InNormal.y));
    float Shift = saturate(-Normal.z);

    Normal.xy -= Normal.xy > 0.0f ? Shift : -Shift;

    return normalize(Normal);
}

void GbufferPack(inout IXrayGbufferPack O, inout IXrayMaterial M)
{
    O.Normal.xy = NormalEncode(M.Normal.xyz);
    O.Normal.z = M.Roughness;
    O.Normal.w = M.SnowMask;

    O.Color.xyz = M.Color.xyz;
    O.Color.w = M.SSS;
    
#ifdef USE_R2_STATIC_SUN
    O.Color.w = M.Sun;
#endif

    O.Material.x = M.Metalness;
    O.Material.y = M.Hemi;

    O.Material.z = M.AO;
	
#ifndef USE_PBR
    O.Material.w = 0.0f;
#else
    O.Material.w = 1.0f;
#endif
}

float4 GbufferGetPoint(in float2 HPos)
{
	float Depth = s_position.Load(int3(HPos, 0)).x;
	HPos = HPos - m_taa_jitter.xy * float2(0.5f, -0.5f) * pos_decompression_params2.xy;
	float3 Point = float3(HPos * pos_decompression_params.zw - pos_decompression_params.xy, 1.0f);
    Point *= depth_unpack.x * rcp(Depth - depth_unpack.y);
    return float4(Point, 1.0f);
}

float3 GbufferGetPointRealUnjitter(in float2 TexCoord, in float Depth)
{
	float3 Point = float3(TexCoord * 2.0f - 1.0f, 1.0f);
	
	if(Depth < 0.02f) {
		Point.z = depth_unpack.z * rcp(Depth * 50.0f - depth_unpack.w);
		Point.xy *= pos_decompression_params_hud.xy * Point.z;
	} else {
		Point.z = depth_unpack.x * rcp(Depth - depth_unpack.y);
		Point.xy *= pos_decompression_params.xy * Point.z;
	}
	
	return Point;
}

float3 GbufferGetPointRealUnjitter(in float2 TexCoord)
{
	float Depth = s_position.Load(int3(TexCoord * pos_decompression_params2.xy, 0)).x;
	return GbufferGetPointRealUnjitter(TexCoord, Depth);
}

void GbufferUnpack(in float2 TexCoord, in float2 HPos, inout IXrayGbuffer O)
{
    float4 NormalHemi = s_normal.Load(int3(HPos, 0));
	
    float4 Material = s_surface.Load(int3(HPos, 0));
    float4 ColorSSS = s_diffuse.Load(int3(HPos, 0));

    O.Depth = s_position.Load(int3(HPos, 0)).x;
    
    HPos = HPos - m_taa_jitter.xy * float2(0.5f, -0.5f) * pos_decompression_params2.xy;

    float3 P = float3(HPos * pos_decompression_params.zw - pos_decompression_params.xy, 1.0f);
    float3 P_hud = float3(HPos * pos_decompression_params_hud.zw - pos_decompression_params_hud.xy, 1.0f);

    O.Point = P * depth_unpack.x * rcp(O.Depth - depth_unpack.y);
    O.PointHud = P_hud * depth_unpack.z * rcp(O.Depth * 50.0f - depth_unpack.w);

    O.PointReal = O.Depth < 0.02f ? O.PointHud : O.Point;
	
	O.ViewDist = length(O.PointReal);
	O.View = O.PointReal * rcp(O.ViewDist);

    O.Normal.xyz = NormalDecode(NormalHemi.xy);
    O.Hemi = Material.y;

    O.Color.xyz = PushGamma(ColorSSS.xyz);
    O.SSS = ColorSSS.w;

    O.Metalness = Material.x;
    O.Roughness = NormalHemi.z;
	
	O.AO = PushGamma(Material.z);
	O.F0 = 0.002f + 0.028f * Material.w;

	O.SnowMask = NormalHemi.w;
}

void GbufferUnpack(in float2 TexCoord, inout IXrayGbuffer O)
{
	float2 HPos = TexCoord * pos_decompression_params2.xy;
	
    float4 NormalHemi = s_normal.SampleLevel(smp_rtlinear, TexCoord, 0);
	
    float4 Material = s_surface.SampleLevel(smp_rtlinear, TexCoord, 0);
    float4 ColorSSS = s_diffuse.SampleLevel(smp_rtlinear, TexCoord, 0);

    O.Depth = s_position.Load(int3(HPos, 0)).x;
	
    HPos = HPos - m_taa_jitter.xy * float2(0.5f, -0.5f) * pos_decompression_params2.xy;

    float3 P = float3(HPos * pos_decompression_params.zw - pos_decompression_params.xy, 1.0f);
    float3 P_hud = float3(HPos * pos_decompression_params_hud.zw - pos_decompression_params_hud.xy, 1.0f);

    O.Point = P * depth_unpack.x * rcp(O.Depth - depth_unpack.y);
    O.PointHud = P_hud * depth_unpack.z * rcp(O.Depth * 50.0f - depth_unpack.w);

    O.PointReal = O.Depth < 0.02f ? O.PointHud : O.Point;
	
	O.ViewDist = length(O.PointReal);
	O.View = O.PointReal * rcp(O.ViewDist);

    O.Normal.xyz = NormalDecode(NormalHemi.xy);
    O.Hemi = Material.y;

    O.Color.xyz = PushGamma(ColorSSS.xyz);
    O.SSS = ColorSSS.w;

    O.Metalness = Material.x;
    O.Roughness = NormalHemi.z;
	
	O.AO = PushGamma(Material.z);
	O.F0 = 0.002f + 0.028f * Material.w;

	O.SnowMask = NormalHemi.w;
}

#endif

