#include "common.h"

#if defined(DX11) || (!defined(GLSLES) && defined(GL3))
#define ATLAS_ARRAY 1
#define ATLAS_DECLARE(name, reg) TEX_DECLARE2DARRAY(name, reg)
#define TEXTURE_IN_ATLAS(name) TEXTURE_IN_2DARRAY(name)
#else
#define ATLAS_ARRAY 0
#define ATLAS_DECLARE(name, reg) TEX_DECLARE2D(name, reg)
#define TEXTURE_IN_ATLAS(name) TEXTURE_IN_2D(name)
#endif

struct Appdata
{
    ATTR_INT4 Position	: POSITION;

    ATTR_INT4 Normal	: NORMAL;

    ATTR_INT4 Material0	: TEXCOORD0;
    ATTR_INT4 Material1	: TEXCOORD1;
};

struct VertexOutput
{
    float4 HPosition    : POSITION;

    float3 Weights: COLOR0;

    float4 Uv0: TEXCOORD0;
    float4 Uv1: TEXCOORD1;
    float4 Uv2: TEXCOORD2;

    float4 LightPosition_Fog : TEXCOORD3;
    float3 PosLightSpace     : TEXCOORD4;

#ifdef PIN_HQ
	float3 Normal: TEXCOORD5;
    float4 View_Depth: TEXCOORD6;

    float3 Tangents: COLOR1;
#else
    float3 Diffuse: COLOR1;
#endif
};

WORLD_MATRIX_ARRAY(WorldMatrixArray, 75);

uniform float4 LayerScale;

float4 getUV(float3 position, ATTR_INT material, ATTR_INT projection, float seed)
{
	float3 u = WorldMatrixArray[1 + int(projection)].xyz;
	float3 v = WorldMatrixArray[19 + int(projection)].xyz;

    float4 m = WorldMatrixArray[37 + int(material)];

    float2 uv = float2(dot(position, u), dot(position, v)) * m.x + m.y * float2(seed, floor(seed * 2.6651441f));

#if ATLAS_ARRAY
    return float4(uv, material, 0);
#else
    return float4(uv, m.zw);
#endif
}

VertexOutput TerrainVS(Appdata IN)
{
    VertexOutput OUT = (VertexOutput)0;
    
	float3 posWorld = IN.Position.xyz * WorldMatrixArray[0].w + WorldMatrixArray[0].xyz;
    float3 normalWorld = IN.Normal.xyz * (1.0 / 127.0) - 1.0;

	OUT.HPosition = mul(G(ViewProjection), float4(posWorld, 1));

    OUT.LightPosition_Fog = float4(lgridPrepareSample(lgridOffset(posWorld, normalWorld)), (G(FogParams).z - OUT.HPosition.w) * G(FogParams).w);

    OUT.PosLightSpace = shadowPrepareSample(posWorld);

    OUT.Uv0 = getUV(posWorld, IN.Material0.x, IN.Material1.x, IN.Normal.w);
    OUT.Uv1 = getUV(posWorld, IN.Material0.y, IN.Material1.y, IN.Material0.w);
    OUT.Uv2 = getUV(posWorld, IN.Material0.z, IN.Material1.z, IN.Material1.w);

#if defined(GLSLES) && !defined(GL3) // iPad2 workaround
    OUT.Weights = abs(IN.Position.www - float3(0, 1, 2)) < 0.1;
#else
    OUT.Weights = IN.Position.www == float3(0, 1, 2);
#endif

#ifdef PIN_HQ
	OUT.Normal = normalWorld;
    OUT.View_Depth = float4(G(CameraPosition) - posWorld, OUT.HPosition.w);
	OUT.Tangents = float3(IN.Material1.xyz) > 7.5; // side vs top
#else
    float ndotl = dot(normalWorld, -G(Lamp0Dir));
    float3 diffuse = max(ndotl, 0) * G(Lamp0Color) + max(-ndotl, 0) * G(Lamp1Color);

    OUT.Diffuse = diffuse;
#endif

	return OUT;
}

ATLAS_DECLARE(AlbedoMap, 0);
ATLAS_DECLARE(NormalMap, 1);
ATLAS_DECLARE(SpecularMap, 2);

TEX_DECLARECUBE(EnvMap, 3);
LGRID_SAMPLER(LightMap, 4);
TEX_DECLARE2D(LightMapLookup, 5);
TEX_DECLARE2D(ShadowMap, 6);

float4 sampleMap(TEXTURE_IN_ATLAS(s), float4 uv)
{
#if ATLAS_ARRAY
	return tex2DArray(s, uv.xyz);
#elif defined(PIN_HQ)
    float2 uvs = uv.xy * LayerScale.xy;

    return tex2Dgrad(s, frac(uv.xy) * LayerScale.xy + uv.zw, ddx(uvs), ddy(uvs));
#else
    return tex2D(s, frac(uv.xy) * LayerScale.xy + uv.zw);
#endif
}

float4 sampleBlend(TEXTURE_IN_ATLAS(s), float4 uv0, float4 uv1, float4 uv2, float3 w)
{
	return
        sampleMap(TEXTURE(s), uv0) * w.x + 
        sampleMap(TEXTURE(s), uv1) * w.y +
        sampleMap(TEXTURE(s), uv2) * w.z;
}

float3 sampleNormal(TEXTURE_IN_ATLAS(s), float4 uv0, float4 uv1, float4 uv2, float3 w, float3 normal, float3 tsel)
{
	return terrainNormal(sampleMap(TEXTURE(s), uv0), sampleMap(TEXTURE(s), uv1), sampleMap(TEXTURE(s), uv2), w, normal, tsel);
}

void TerrainPS(VertexOutput IN,
#ifdef PIN_GBUFFER
    out float4 oColor1: COLOR1,
#endif
    out float4 oColor0: COLOR0)
{
    float4 light = lgridSample(TEXTURE(LightMap), TEXTURE(LightMapLookup), IN.LightPosition_Fog.xyz);
    float shadow = shadowSample(TEXTURE(ShadowMap), IN.PosLightSpace, light.a);

	float3 w = IN.Weights.xyz;

    float4 albedo = sampleBlend(TEXTURE(AlbedoMap), IN.Uv0, IN.Uv1, IN.Uv2, w);

#ifdef PIN_HQ
	float fade = saturate0(1 - IN.View_Depth.w * G(FadeDistance_GlowFactor).y);

#ifndef PIN_GBUFFER
	float3 normal = IN.Normal;
#else
	float3 normal = sampleNormal(TEXTURE(NormalMap), IN.Uv0, IN.Uv1, IN.Uv2, w, IN.Normal, IN.Tangents);
#endif

	float4 params = sampleBlend(TEXTURE(SpecularMap), IN.Uv0, IN.Uv1, IN.Uv2, w);

    float ndotl = dot(normal, -G(Lamp0Dir));

    float incandescence = params.b * 2 * fade;

    // Compute diffuse term
    float3 diffuse = (G(AmbientColor) + (saturate(ndotl) * G(Lamp0Color) + max(-ndotl, 0) * G(Lamp1Color)) * shadow + light.rgb + incandescence) * albedo.rgb;

	// Compute specular term (note: guards against params.g becoming negative which can happen for pixels outside of the triangle if MSAA is on)
    float specularIntensity = step(0, ndotl) * params.r * fade;
    float specularPower = saturate0(params.g) * 128 + 0.01;

	float3 specular = G(Lamp0Color) * (specularIntensity * shadow * (float)(half)pow(saturate(dot(normal, normalize(-G(Lamp0Dir) + normalize(IN.View_Depth.xyz)))), specularPower));
#else
    // Compute diffuse term
    float3 diffuse = (G(AmbientColor) + IN.Diffuse * shadow + light.rgb) * albedo.rgb;

	// Compute specular term
	float3 specular = 0;
#endif

    // Combine
    oColor0.rgb = diffuse + specular;
    oColor0.a = 1;

    float fogAlpha = saturate(IN.LightPosition_Fog.w);

    oColor0.rgb = lerp(G(FogColor), oColor0.rgb, fogAlpha);

#ifdef PIN_GBUFFER
    oColor1 = gbufferPack(IN.View_Depth.w, diffuse.rgb, specular.rgb, fogAlpha);
#endif
}
