// Copyright (c) Wojciech Figat. All rights reserved.

#ifndef __MATERIAL_COMMON__
#define __MATERIAL_COMMON__

// Material input types
#define MATERIAL_DOMAIN_SURFACE      0
#define MATERIAL_DOMAIN_POSTPROCESS  1
#define MATERIAL_DOMAIN_DECAL        2
#define MATERIAL_BLEND_OPAQUE        0
#define MATERIAL_BLEND_TRANSPARENT   1
#define MATERIAL_BLEND_ADDITIVE      2
#define MATERIAL_BLEND_MULTIPLY      3
#define MATERIAL_TESSELLATION_NONE   0
#define MATERIAL_TESSELLATION_FLAT   1
#define MATERIAL_TESSELLATION_PN     2
#define MATERIAL_TESSELLATION_PHONG  3

#define USE_CUSTOM_VERTEX_INTERPOLATORS (CUSTOM_VERTEX_INTERPOLATORS_COUNT > 0)

// Validate inputs
#ifndef MATERIAL
#define MATERIAL 0
#endif
#ifndef MATERIAL_DOMAIN
#define MATERIAL_DOMAIN MATERIAL_DOMAIN_SURFACE
#endif
#ifndef MATERIAL_BLEND
#define MATERIAL_BLEND MATERIAL_BLEND_OPAQUE
#endif
#ifndef MATERIAL_SHADING_MODEL
#define MATERIAL_SHADING_MODEL SHADING_MODEL_LIT
#endif
#ifndef MATERIAL_TEXCOORDS
#define MATERIAL_TEXCOORDS 1
#endif
#ifndef USE_INSTANCING
#define USE_INSTANCING 0
#endif
#ifndef USE_SKINNING
#define USE_SKINNING 0
#endif
#ifndef USE_LIGHTMAP
#define USE_LIGHTMAP 0
#endif
#ifndef USE_POSITION_OFFSET
#define USE_POSITION_OFFSET 0
#endif
#ifndef USE_VERTEX_COLOR
#define USE_VERTEX_COLOR 0
#endif
#ifndef USE_DISPLACEMENT
#define USE_DISPLACEMENT 0
#endif
#ifndef USE_TESSELLATION
#define USE_TESSELLATION 0
#endif
#ifndef USE_DITHERED_LOD_TRANSITION
#define USE_DITHERED_LOD_TRANSITION 0
#endif
#ifndef USE_PER_VIEW_CONSTANTS
#define USE_PER_VIEW_CONSTANTS 0
#endif
#ifndef USE_PER_DRAW_CONSTANTS
#define USE_PER_DRAW_CONSTANTS 0
#endif
#ifndef MATERIAL_TESSELLATION
#define MATERIAL_TESSELLATION MATERIAL_TESSELLATION_NONE
#endif
#ifndef MAX_TESSELLATION_FACTOR
#define MAX_TESSELLATION_FACTOR 15
#endif
#ifndef PER_BONE_MOTION_BLUR
#define PER_BONE_MOTION_BLUR 0
#endif

// Object properties
struct ObjectData
{
    float4x4 WorldMatrix;
    float4x4 PrevWorldMatrix;
    float3 GeometrySize;
    float WorldDeterminantSign;
    float LODDitherFactor;
    float PerInstanceRandom;
    float4 LightmapArea;
};

float2 UnpackHalf2(uint xy)
{
    return float2(f16tof32(xy & 0xffff), f16tof32(xy >> 16));
}

// Loads the object data from the global buffer
ObjectData LoadObject(Buffer<float4> objectsBuffer, uint objectIndex)
{
    // This must match ShaderObjectData::Store
    objectIndex *= 8;
    ObjectData object = (ObjectData)0;
    float4 vector0 = objectsBuffer[objectIndex + 0];
    float4 vector1 = objectsBuffer[objectIndex + 1];
    float4 vector2 = objectsBuffer[objectIndex + 2];
    float4 vector3 = objectsBuffer[objectIndex + 3];
    float4 vector4 = objectsBuffer[objectIndex + 4];
    float4 vector5 = objectsBuffer[objectIndex + 5];
    float4 vector6 = objectsBuffer[objectIndex + 6];
    float4 vector7 = objectsBuffer[objectIndex + 7];
    object.WorldMatrix[0] = float4(vector0.xyz, 0.0f);
    object.WorldMatrix[1] = float4(vector1.xyz, 0.0f);
    object.WorldMatrix[2] = float4(vector2.xyz, 0.0f);
    object.WorldMatrix[3] = float4(vector0.w, vector1.w, vector2.w, 1.0f);
    object.PrevWorldMatrix[0] = float4(vector3.xyz, 0.0f);
    object.PrevWorldMatrix[1] = float4(vector4.xyz, 0.0f);
    object.PrevWorldMatrix[2] = float4(vector5.xyz, 0.0f);
    object.PrevWorldMatrix[3] = float4(vector3.w, vector4.w, vector5.w, 1.0f);
    object.GeometrySize = vector6.xyz;
    object.PerInstanceRandom = vector6.w;
    object.WorldDeterminantSign = vector7.x;
    object.LODDitherFactor = vector7.y;
    object.LightmapArea.xy = UnpackHalf2(asuint(vector7.z));
    object.LightmapArea.zw = UnpackHalf2(asuint(vector7.w));
    return object;
}

#ifndef LoadObjectFromCB
// Loads the object data from the constant buffer into the variable
#define LoadObjectFromCB(var) \
    var = (ObjectData)0; \
    var.WorldMatrix = ToMatrix4x4(WorldMatrix); \
    var.PrevWorldMatrix = ToMatrix4x4(PrevWorldMatrix); \
    var.GeometrySize = GeometrySize; \
    var.PerInstanceRandom = PerInstanceRandom; \
    var.WorldDeterminantSign = WorldDeterminantSign; \
    var.LODDitherFactor = LODDitherFactor; \
    var.LightmapArea = LightmapArea;
#endif

// Material properties
struct Material
{
    float3 Emissive;
    float Roughness;
    float3 Color;
    float AO;
    float3 WorldNormal;
    float Metalness;
    float3 TangentNormal;
    float Specular;
    float3 PositionOffset;
    float Opacity;
    float3 SubsurfaceColor;
    float Refraction;
    float3 WorldDisplacement;
    float Mask;
    float TessellationMultiplier;
#if USE_CUSTOM_VERTEX_INTERPOLATORS
	float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT];
#endif
};

// Secondary constant buffer (with per-view constants at slot 1)
#if USE_PER_VIEW_CONSTANTS
cbuffer ViewData : register(b1)
{
    float4x4 ViewMatrix;
    float4x4 ViewProjectionMatrix;
    float4x4 PrevViewProjectionMatrix;
    float4x4 MainViewProjectionMatrix;
    float4 MainScreenSize;
    float3 ViewPos;
    float ViewFar;
    float3 ViewDir;
    float TimeParam;
    float4 ViewInfo;
    float4 ScreenSize;
    float4 TemporalAAJitter;
    float3 LargeWorldsChunkIndex;
    float LargeWorldsChunkSize;
    float2 ViewPadding0;
    float TestValue;
    float ScaledTimeParam;
};
#endif

// Draw pipeline constant buffer (with per-draw constants at slot 2)
#if USE_PER_DRAW_CONSTANTS
cbuffer DrawData : register(b2)
{
    float3 DrawPadding;
    uint DrawObjectIndex;
};
#endif

struct ModelInput
{
    float3 Position : POSITION;
#if MATERIAL_TEXCOORDS > 0
    float2 TexCoord0 : TEXCOORD0;
#endif
#if MATERIAL_TEXCOORDS > 1
    float2 TexCoord1 : TEXCOORD1;
#endif
#if MATERIAL_TEXCOORDS > 2
    float2 TexCoord2 : TEXCOORD2;
#endif
#if MATERIAL_TEXCOORDS > 3
    float2 TexCoord3 : TEXCOORD3;
#endif
    float2 LightmapUV : LIGHTMAP;
    float4 Normal : NORMAL;
    float4 Tangent : TANGENT;
#if USE_VERTEX_COLOR
	half4 Color : COLOR;
#endif
#if USE_INSTANCING
	uint ObjectIndex : ATTRIBUTE0;
#endif
};

struct ModelInput_PosOnly
{
    float3 Position : POSITION;
#if USE_INSTANCING
	uint ObjectIndex : ATTRIBUTE0;
#endif
};

struct ModelInput_Skinned
{
    float3 Position : POSITION;
#if MATERIAL_TEXCOORDS > 0
    float2 TexCoord0 : TEXCOORD0;
#endif
#if MATERIAL_TEXCOORDS > 1
    float2 TexCoord1 : TEXCOORD1;
#endif
#if MATERIAL_TEXCOORDS > 2
    float2 TexCoord2 : TEXCOORD2;
#endif
#if MATERIAL_TEXCOORDS > 3
    float2 TexCoord3 : TEXCOORD3;
#endif
    float4 Normal : NORMAL;
    float4 Tangent : TANGENT;
#if USE_VERTEX_COLOR
	half4 Color : COLOR;
#endif
    uint4 BlendIndices : BLENDINDICES;
    float4 BlendWeights : BLENDWEIGHTS;
};

struct Model_VS2PS
{
    float4 Position : SV_Position;
    float4 ScreenPos : TEXCOORD0;
};

struct GBufferOutput
{
    float4 Light : SV_Target0;
    float4 RT0 : SV_Target1;
    float4 RT1 : SV_Target2;
    float4 RT2 : SV_Target3;
    float4 RT3 : SV_Target4;
};

float3 UnpackNormalMap(float2 value)
{
    float3 normal;
    normal.xy = value * 2.0 - 1.0;
    normal.z = sqrt(saturate(1.0 - dot(normal.xy, normal.xy)));
    return normal;
}

float3x3 CalcTangentBasis(float3 normal, float3 pos, float2 uv)
{
    // References:
    // http://www.thetenthplanet.de/archives/1180
    // https://zhangdoa.com/posts/normal-and-normal-mapping
    float3 dp1 = ddx(pos);
    float3 dp2 = ddy(pos);
    float2 duv1 = ddx(uv);
    float2 duv2 = ddy(uv);
    float3 dp2perp = cross(dp2, normal);
    float3 dp1perp = cross(normal, dp1);
    float3 tangent = normalize(dp2perp * duv1.x + dp1perp * duv2.x);
    float3 bitangent = normalize(dp2perp * duv1.y + dp1perp * duv2.y);
    return float3x3(tangent, bitangent, normal);
}

float3x3 CalcTangentBasisFromWorldNormal(float3 normal)
{
    float3 tangent = cross(normal, float3(1, 0, 0));
    float3 bitangent = cross(normal, tangent);
    return float3x3(tangent, bitangent, normal);
}

float3x3 CalcTangentBasis(float3 normal, float4 tangent)
{
    float3 bitangent = cross(normal, tangent.xyz) * tangent.w;
    return float3x3(tangent.xyz, bitangent, normal);
}

// [Jimenez et al. 2016, "Practical Realtime Strategies for Accurate Indirect Occlusion"]
float3 AOMultiBounce(float visibility, float3 albedo)
{
    float3 a = 2.0404 * albedo - 0.3324;
    float3 b = -4.7951 * albedo + 0.6417;
    float3 c = 2.7552 * albedo + 0.6903;
    return max(visibility, ((visibility * a + b) * visibility + c) * visibility);
}

float2 Flipbook(float2 uv, float frame, float2 sizeXY, float2 flipXY = 0.0f)
{
    float tile = (float)(int)fmod(frame, sizeXY.x * sizeXY.y);
    float2 tileCount = float2(1.0, 1.0) / sizeXY;
    float tileY = abs(flipXY.y * sizeXY.y - (floor(tile * tileCount.x) + flipXY.y * 1));
    float tileX = abs(flipXY.x * sizeXY.x - ((tile - sizeXY.x * floor(tile * tileCount.x)) + flipXY.x * 1));
    return (uv + float2(tileX, tileY)) * tileCount;
}

// Calculates the world-position offset to stabilize tiling (eg. via triplanar mapping) due to Large Worlds view origin offset.
float3 GetLargeWorldsTileOffset(float tileSize)
{
#if USE_PER_VIEW_CONSTANTS
    return LargeWorldsChunkIndex * fmod(LargeWorldsChunkSize, tileSize);
#else
    return float3(0, 0, 0);
#endif
}

#endif
