//---------------------------------------------------------------------------
// Prepar3d - Shader Effect Files
// Copyright (c) 2014, Lockheed Martin Corporation
//---------------------------------------------------------------------------

#ifndef _GPU_TERRAIN_FXH_
#define _GPU_TERRAIN_FXH_

#include "ShaderMacros.h"
#include "ConstantBuffers.fxh"
#include <FuncLibrary.fxh>
#include "Shadow.fxh"
#include "IRSim.fxh"
#include "InstanceIndexing.fxh"
#include "Water.fxh"
#include "GPUTerrainStaticData.fxh"

//---------------------DEFINES-------------------------//
#define TESS_MAX 32.0
 
#define GetParentEdgeTessOffset(p1,p2)  0.5 * clamp(exp2(ceil(log2(2*lerp(abs(p[p1].PixelsPerTexel.x),abs(p[p2].PixelsPerTexel.x),0.5)) )),4,TESS_MAX*saturate(2*size))
#define GetEdgeTessOffset(p1,p2)  clamp(exp2(ceil(log2(lerp(abs(p[p1].PixelsPerTexel.x),abs(p[p2].PixelsPerTexel.x),0.5)) )),4,TESS_MAX*size)
#define GetTess(p1,p2)  clamp(exp2(ceil(log2(lerp(abs(p[p1].PixelsPerTexel.x),abs(p[p2].PixelsPerTexel.x),0.5)) )),0,TESS_MAX*size)

//#define GetParentEdgeTessOffset(p1,p2) 64
//#define GetEdgeTessOffset(p1,p2)  64
//#define GetTess(p1,p2)  64

//---------------------Constant Buffers-------------------------//

shared cbuffer cbPerObjectMaterialData : REGISTER(b,PER_MATERIAL_CB_REGISTER)
{
    float fLandDetailScale;
    float fSinWindDirection;
    float fCosWindDirection;
    float fMaxWaveHeight;
    float fWindSpeed;
    int iSeason;
    int iWaterElevationBucket;
    float fTessFactor;
};

//---------------------Structs-------------------------//
struct Qmic64
{
    int3   Meters;      // Offset in meters from earth center.      See RS_RenderableTransform.h   
    int    QmicU;          // U QMIC Coordiate in 7cm steps.
    float3 SubMeters;   // Submeter remainder of the meter offset.  See RS_RenderableTransform.h
    int    QmicV;          // V QMIC Coordiate in 7cm steps.
    float3 Normal;      // need this to account for wgs84 ellipsoid model
    float  Altitude;    // not used for now but could help with accuracy in the future
};

struct PosNorm
{
    float4 Position;
    float3 Normal;
};

struct DemLoadData
{
    float3 Normal;
    float  Alt;
    #if defined (SHD_HAS_LAND)
    float3 Specular;
    #endif
    float  BathyAlt;
    float  LandClassTemp;
    float  IsWater;
    //float  Radiosity;
};

/// class that holds the instance data for an terrain quad packed for the gpu
struct GTCItem
{     
    Qmic64 Points[9];          // Top left corner of the cell           
                                  //    0  1  2
                                  //    3  4  5
                                  //    6  7  8

    float  QmidInvSizeU;       // size in qmic units ~7cm
    float  QmidInvSizeV;       // 1/size.  used for uv scaling
    int    LOD;                // QMID level for this tile
    int    ParentID;           // Cache Index of parent
    
    int4    ActiveChildren;  // If non zero, this tile should not be drawn unless we did a distance check
                                  // to determine that a higher lod wa snot needed.  this will be read in as a
                                  // uint by the shader since only a zero check is required.
    

    int    AdjacentLods[12];       // LOD for adjacent tiles.  These are
                                  // Needed to determine blending and Tess Factors:
                                  //    0  1  2
                                  //    7  x  3
                                  //    6  5  4

    float4  BaseTextureCoords;			// u, v, slice, size 
    float4  EmissiveTextureCoords;      // u, v, slice, size 
    float4  DemTextureCoords;			// u, v, slice, size 
    float4  ParentDemTextureCoords;     // u, v, slice, size 

    float   MinElevation;
    float   MaxElevation;
    float   MinBathyElevation;
    float   MaxBathyElevation;
};

// Declare outputs
struct VS_OUTPUT_HS_INPUT
{
    float3 vWorldPos            : WORLDPOS;
    float  IsVisible            : CLIP;
    float2 PixelsPerTexel       : PPT;
    float3 LevelNorm            : NORMAL;
    float3 NorthVecWS           : NORTH;
    int4 DemStitch              : STITCH;
    int4 DemStitchParent        : STITCHP;
    float4 TexBase              : TEXCOORD0;
    float3 DemTex               : DEMTEX;
#if defined(SHD_HAS_WATER) && defined(SHD_WATER_DETAIL_4)
	float2 DisTex               : DISTEX;
#endif
    float needsBlend            : NEEDSBLEND;
    float3 DemTexMip            : MIP;
    float MetersPerTexel        : METERSPT;
    int4 ActiveChildren         : CHILDREN;
};

struct HS_CONSTANT_DATA_OUTPUT
{
    float Edges[4]              : SV_TessFactor;
    float Inside[2]             : SV_InsideTessFactor;
};

struct HS_CONTROL_POINT_OUTPUT
{
    float3 vWorldPos            : WORLDPOS;
    float  IsLODTenPlus         : LODTEN;
    float3 LevelNorm            : NORMAL;
    float3 NorthVecWS           : NORTH;
    int4 DemStitch              : STITCH;
    int4 DemStitchParent        : STITCHP;
    float4 TexBase              : TEXCOORD0;
    float3 DemTex               : DEMTEX;
#if defined(SHD_HAS_WATER) && defined(SHD_WATER_DETAIL_4)
	float2 DisTex               : DISTEX;
#endif
    float dropScalar            : DROP;
    float3 DemTexMip            : MIP;
    float needsBlend            : NEEDSBLEND;
};

#if defined(SHD_PER_SAMPLE_PS_EXECUTE) && !defined(SHD_TERRAIN_SHADOWS)
#define PS_INPUT_MODIFIERS centroid sample
#else
#define PS_INPUT_MODIFIERS
#endif

struct DS_OUTPUT
{
    PS_INPUT_MODIFIERS float4 vPosition            : SV_Position;
#if !defined(SHD_TERRAIN_SHADOWS)
    PS_INPUT_MODIFIERS float3 vNormal              : NORMAL;
    PS_INPUT_MODIFIERS float  vFoamScale           : FOAM;
    PS_INPUT_MODIFIERS float4 TexBase              : TEXCOORD0;
#if defined (SHD_HAS_LAND)
    #if defined(SHD_PER_SAMPLE_PS_EXECUTE) && !defined(SHD_TERRAIN_SHADOWS)
        centroid sample float3 Specular : SPEC;
    #else
        centroid linear float3 Specular : SPEC;
    #endif
#endif
    PS_INPUT_MODIFIERS float3 vPosWS               : TEXCOORD2;
    PS_INPUT_MODIFIERS float  LandClassTemp        : LANDCLASS;

#if defined(SHD_HAS_WATER)
    PS_INPUT_MODIFIERS float3 LevelNorm            : LEVELMAT;
    PS_INPUT_MODIFIERS float  IsWater              : ISWATER;
    PS_INPUT_MODIFIERS float3 LevelBiNorm          : LEVELMAT1;
    PS_INPUT_MODIFIERS float  Shoreline            : SHORE;
	#if defined(SHD_WATER_DETAIL_4)
    PS_INPUT_MODIFIERS float2 DisTex               : DISTEX;
	#endif
#endif    

#if defined(SHD_BATHYMETRY)
    float3 vBathyPosWS          : BATHY_POS;
#endif

#if defined(SHD_GPU_DEM_SKIP_WATER)   
    float  fClipDistance        : SV_ClipDistance0;
    float  fCullDistance        : SV_CullDistance0;
#endif
#endif
};

struct PS_INPUT
{
    PS_INPUT_MODIFIERS float4 vPosition            : SV_Position;
#if !defined(SHD_TERRAIN_SHADOWS)
    PS_INPUT_MODIFIERS float3 vNormal              : NORMAL;
    PS_INPUT_MODIFIERS float  vFoamScale           : FOAM;
    PS_INPUT_MODIFIERS float4 TexBase              : TEXCOORD0;
#if defined (SHD_HAS_LAND)
    #if defined(SHD_PER_SAMPLE_PS_EXECUTE) && !defined(SHD_TERRAIN_SHADOWS)
        centroid sample float3 Specular : SPEC;
    #else
        centroid linear float3 Specular : SPEC;
    #endif
#endif
    PS_INPUT_MODIFIERS float3 vPosWS               : TEXCOORD2;
    PS_INPUT_MODIFIERS float  LandClassTemp        : LANDCLASS;

#if defined(SHD_HAS_WATER)
    PS_INPUT_MODIFIERS float3 LevelNorm            : LEVELMAT;
    PS_INPUT_MODIFIERS float  IsWater              : ISWATER;
    PS_INPUT_MODIFIERS float3 LevelBiNorm          : LEVELMAT1;
    PS_INPUT_MODIFIERS float  Shoreline            : SHORE;
	#if defined(SHD_WATER_DETAIL_4)
        PS_INPUT_MODIFIERS float2 DisTex           : DISTEX;
	#endif
#endif    
#endif
};

//---------------------Bound Resource Registers--------------------------------//
shared Texture2DArray txBase : REGISTER(t,TERRAIN_TEXTUREARRAY_REGISTER);
shared Texture2DArray txEmissive : REGISTER(t,TERRAIN_TEXTUREARRAY_EMISSIVE_REGISTER);
shared Texture2DArray txDem   : REGISTER(t,DEM_TEXTURE_REGISTER);
shared Texture2DArray txEnv : REGISTER(t,0);
shared Texture2DArray txLandDetail : REGISTER(t,DETAIL_TEXTURE_REGISTER);
shared Texture2DArray txBumpOne : REGISTER(t,2);
shared Texture2DArray txBumpTwo : REGISTER(t,3);
shared Texture2DArray txBumpThree : REGISTER(t,4);
shared Texture2DArray txBumpFour : REGISTER(t,5);
shared Texture2DArray txBumpFive : REGISTER(t,6);
shared Texture2DArray txRefraction : REGISTER(t,WATER_TEXTURE_REFRACTION);
shared Texture2DArray txFoam : REGISTER(t,WATER_FOAM_REGISTER);
shared Texture2DArray txNoise : REGISTER(t,WATER_NOISE_REGISTER);

shared Texture2D txWaterDisplacement : REGISTER(t, WATER_DISPLACEMENT_REGISTER);
shared Texture2D txWaterSloapFoam    : REGISTER(t, WATER_SLOAP_FOAM_REGISTER);

shared StructuredBuffer<PointLight> pointLightBuffer : REGISTER(t, POINTLIGHT_REGISTER);
shared StructuredBuffer<SpotLight> spotLightBuffer : REGISTER(t, SPOTLIGHT_REGISTER);
shared Buffer<uint> lightTileIndexBuffer : REGISTER(t, LIGHT_TILE_REGISTER);

//Texture2DArray <unsigned int> txClip   : REGISTER(t,CLIP_MAP_REGISTER);

// TODO replace these with a single vec of indexes into a coord cache

shared StructuredBuffer<GTCItem> mCache   :  REGISTER(t,TERRAIN_CACHE_DATA_REGISTER);

//---------------------Functions-------------------------//
// 1/4096
#define BASE_WATER_DETAIL_SCALE 0.00024414062

#if defined(SHD_WATER_DETAIL_2)
#define BLEND_AND_OFFSET 0.333333333
#define INV_AND_OFFSET 3
#else
#define BLEND_AND_OFFSET 0.5
#define INV_AND_OFFSET 2
#endif

float4 ComputeWaterTextureCoordinates(float3 WorldPos, float index, float eyeDist, float fLogEyeDist)
{
    // first get the cb values that set the artist defined scale/range/scroll
    float scale = cb_WaterTextureData[index].TextureScale;
    float windScaler = cb_WaterTextureData[index].WindScalar;
    float distBlend = saturate(abs(eyeDist - cb_WaterTextureData[index].MaxWaveDistance) / (cb_WaterTextureData[index].MaxWaveDistance * BLEND_AND_OFFSET));
    float fLogDistWithOffset = 0;

    // rather than fade out at max dist, fade out and fade it back in with the scale
    // and scroll rate increased based on log distance.
    // could optimize to remove branch but the compiler may do this better anyway
    if (eyeDist > cb_WaterTextureData[index].MaxWaveDistance)
    {
        // fade at log distance bands based on percentage of the current log range
        // offset log distance per texture so that only one fade is happening for any given pixel
        // there are still some visibile artifacts but this works fairly well
        fLogDistWithOffset = fLogEyeDist + BLEND_AND_OFFSET*index;
        uint logDist = fLogDistWithOffset;
        // get proximity to nearest band edge by rounding log dist 
        distBlend = min(distBlend, saturate(abs(fLogDistWithOffset - uint(fLogDistWithOffset + 0.5)) * INV_AND_OFFSET));

        // Double scale at each log dist band
        // note that log eyedist is precalculated in the pixel shader
        //
        //       const float fLogEyeDist = min(log2(eyeDist) - 7, 7);
        //
        // The bands start at 128m and end at 4096m (the largest texture scale we can support given the 
        // the texture coordinate origin system for mapping detail texture into global space. 
        scale = max(1.0f, 128.0f / float(1 << logDist));
        
        // increase scroll rate by log dist.  (note default windScale is 1.0)
        // It was tweaked subjectivly by view water at various altitudes. 
        // Eventually It would be good to add another param or two
        // to the water constants to let artists tweak these scalars.
        windScaler += windScaler * logDist;
    }

    const float2 sc = float2(cb_WaterTextureData[index].SinWaveWindOffset, cb_WaterTextureData[index].CosWaveWindOffset);
    const float2 scrollOffset = windScaler * cb_mSimTime * float2(sc.x, sc.y);
    const float2 bmp = (WorldPos.xz + cb_DetailTextureOrigin + scrollOffset) * scale * BASE_WATER_DETAIL_SCALE;

    // return scale factor in the w so that ps can use it to scale the gradient for sampling.
    // add a bit of bias for the log distance textures to help fight aliasing in the distance.
    return float4(bmp.x, bmp.y, distBlend, scale + 0.05 * fLogDistWithOffset);
}

float2 ComputeBaseWaterDetailTextureCoords(float3 WorldPos)
{
    return (WorldPos.xz + cb_DetailTextureOrigin) * BASE_WATER_DETAIL_SCALE;
}


float2 ComputeDetailTextureCoordinates(float3 WorldPos, float scale)
{
    float scaleUV = scale * BASE_WATER_DETAIL_SCALE; 
    return (WorldPos.xz + cb_DetailTextureOrigin) * scaleUV;
}

PosNorm GetPositionAndNormal(int3 PositionMeters, float3 SubMeterOffset, float3 normal)
{
    PosNorm pn;
    float3 offsetMeters = (PositionMeters - cb_ViewOrigin.PositionMeters) +
                            (SubMeterOffset - cb_ViewOrigin.SubMeterOffset);
    pn.Position = mul(float4(offsetMeters,1), cb_ViewOrigin.TangentMatrix);
    pn.Normal = normalize(mul(float4(normal,1), cb_ViewOrigin.TangentMatrix).xyz);
    return pn;
}

int3 GetLoadIndex(float3 DemTex, int4 stitch)
{
    float2 loadIndexUV = floor(DemTex.xy * 64);
    const float2 isEdge   =  saturate(loadIndexUV - 63);
    const int   stitchIndex   =  stitch[2 * isEdge.y + isEdge.x];
    const float hasStitch     =  saturate(abs(DemTex.z - stitchIndex));
    loadIndexUV *= (1-(isEdge*hasStitch));
    loadIndexUV -= isEdge*(1 - hasStitch);
    return int3(loadIndexUV,stitchIndex);
}

DemLoadData singleLoadDem1(float3 DemTex, int4 stitch,
                          float3 DemTexParent, int4 stitchParent,
                          float blend)
{
    DemLoadData output = (DemLoadData)0;
    output.Normal    = float3(0,1,0);
    output.Alt = 1000;
    return output;
}

DemLoadData singleLoadDem(float3 DemTex, int4 stitch,
                          float3 DemTexParent, int4 stitchParent,
                          float blend)
{
    DemLoadData output = (DemLoadData)0;
    
    int3 loadIdx = lerp(GetLoadIndex(DemTex, stitch),
                        GetLoadIndex(DemTexParent, stitchParent), saturate(floor(blend)));

    float3 demLoad   = float3(0,0,0);
    demLoad.xyz   = txDem.Load(int4(loadIdx, 0)).xyz;
    unsigned int color0 = asuint(demLoad.z);
    output.Normal    = PalNormal[(color0 & 0x00FF0000)>>16];
    //float wc = float(color0 & 0x000000FF)-150;
    //float wc1 = fmod(wc-1,6);
    //if(wc > 12) wc1 += 6; 
    //output.LandClassTemp = saturate(((float)wc1)/12.0f);//LandClassTemperature[color0 & 0x000000FF];
    //
    output.LandClassTemp = LandClassTemperature[color0 & 0x000000FF];
    //output.LandClassTemp = (color0 & 0x000000FF)/255.0;
    //output.Radiosity = ((float)((color0 & 0xFF000000)>>24))/128.0f;
    //if(output.Radiosity > 1.0f)
    //{
    //    output.Radiosity = 1.0f*(output.Radiosity - 1.0f);
    //}
    output.IsWater    =  (float)((color0 & 0x0000FF00)>>8);
     //output.LandClassTemp = output.IsWater / 12.0f;
    output.Alt        =  demLoad.x;
    output.BathyAlt   =  demLoad.y;
    #if defined (SHD_HAS_LAND)
    output.Specular   =  LandClassSpecular[color0 & 0x000000FF];
    #endif
    //output.Normal    = float4(1000,0,1,0);
    return output;
}

#endif