float2 encodeNormal(float3 n)
{
	float2 enc = normalize( n.xy ) * ( sqrt( -n.z * 0.5 + 0.5 ) );
	enc = enc * 0.5 + 0.5;
	return enc;
}

float2 encodeDepth(float d)
{
	float2 enc = frac( float2( 1.f, 255.f ) * d );
	enc -= enc.y * float2( 1.f / 255.f, 1 / 255.f );
	return enc;
}

float decodeDepth(float2 enc)
{
	return dot( enc, float2( 1.f, 1.f / 255.f ) );
}

float3 blendNormals(float3 n1, float3 n2, float n1Weight, float n2Weight)
{
    float3 normal;
	normal.x = n1Weight * n1.x * n2.z + n2Weight * n1.z * n2.x;
    normal.y = n1Weight * n1.y * n2.z + n2Weight * n1.z * n2.y;
    normal.z = n1.z * n2.z;
    return normalize(normal);
}

//----------------------------------------------------------------------------
// Ambient pre-pass
//----------------------------------------------------------------------------
void water_vp(float4   position  : POSITION,
              float2   texCoord  : TEXCOORD0,
      uniform float4x4 worldViewProj,
          out float4   oPosition : POSITION,
          out float2   oTexCoord : TEXCOORD0)
{
    oPosition = mul(worldViewProj, position);
    oTexCoord = texCoord;
}

float4 water_fp(uniform float4 ambient) : COLOR
{
    return ambient;
}

//----------------------------------------------------------------------------
// Depth-fog pass
//----------------------------------------------------------------------------
void waterFog_vp(float4   position  : POSITION,
                 float3   normal    : NORMAL,
                 float2   texCoord  : TEXCOORD0,
         uniform float4x4 worldViewProj,
         uniform float4x4 worldView,
         uniform float    farDistance,
         uniform float    time,
         uniform float    waveSpeed,
         uniform float    waveAmplitude,
         uniform float    waveFrequency,
             out float4   oPos      : POSITION,
             out float2   oTexCoord : TEXCOORD0,
             out float4   oDir      : TEXCOORD1,
             out float4   oPlane    : TEXCOORD2)
{
	float4 postWavePos = position;
	postWavePos.z += sin(((time * waveSpeed) + texCoord.x) * waveFrequency) *
		waveAmplitude;
    oPos = mul(worldViewProj, postWavePos);
    oDir = mul(worldView, postWavePos);
    oPlane = oDir;
    oDir.xyz *= abs(farDistance) / abs(oDir.z);
    oTexCoord = texCoord;
}

float4 waterFogMediumLow_fp(float2    screenPos         : SV_POSITION,
                            float3    dir               : TEXCOORD1,
                            float3    planeVS           : TEXCOORD2,
                    uniform sampler2D depthAndNormalMap : register(s0),
                    uniform float4    viewPortSize,
                    uniform float4    fogColour,
                    uniform float4    surfaceColour,
                    uniform float     fogStart,
                    uniform float     fogDepth)         : COLOR
{
	// Reconstruct position from depth buffer
	float2 texCoord = ( screenPos.xy + 0.5f ) * viewPortSize.zw;
	float depth = decodeDepth( tex2D( depthAndNormalMap, texCoord ).xy );
	float3 pixelPos = dir * depth;
	float3 relativePos = pixelPos - planeVS;

	float distanceToFogStart = fogStart - length(relativePos);
	float distanceToFogDepth = fogDepth - length(relativePos);
	float fogWidth = fogStart - fogDepth;
	float fogFactor = saturate(distanceToFogStart / fogWidth);

	return lerp(surfaceColour, fogColour, fogFactor);
}

float4 waterFogHigh_fp(float2    screenPos         : SV_POSITION,
                       float2    texCoord          : TEXCOORD0,
                       float3    dir               : TEXCOORD1,
                       float3    planeVS           : TEXCOORD2,
               uniform sampler2D depthAndNormalMap : register(s0),
               uniform sampler2D refractionMap	   : register(s1),
               uniform sampler2D normalMap1        : register(s2),
               uniform sampler2D normalMap2        : register(s3),
               uniform sampler2D normalMap3        : register(s4),
               uniform sampler2D normalMap4        : register(s5),
               uniform float     normalMap1UScroll,
               uniform float     normalMap1VScroll,
               uniform float     normalMap2UScroll,
               uniform float     normalMap2VScroll,
               uniform float     normalMap3UScroll,
               uniform float     normalMap3VScroll,
               uniform float     normalMap4UScroll,
               uniform float     normalMap4VScroll,
               uniform float     time,
               uniform float4    viewPortSize,
               uniform float4    fogColour,
               uniform float4    surfaceColour,
               uniform float     fogStart,
               uniform float     fogDepth)         : COLOR
{
	// Reconstruct position from depth buffer
	float2 depthTexCoord = ( screenPos.xy + 0.5f ) * viewPortSize.zw;
	float depth = decodeDepth( tex2D( depthAndNormalMap, depthTexCoord ).xy );
	float3 pixelPos = dir * depth;

	float3 relativePos = pixelPos - planeVS;

	// Determine fog colour
	float distanceToFogStart = fogStart - length( relativePos );
	float fogWidth = fogStart - fogDepth;
	float fogFactor = saturate( distanceToFogStart / fogWidth );

	float4 fog = lerp( surfaceColour, fogColour, fogFactor );

	// Determine ambient colour from refraction map
	// Sample normal from normal maps
	float2 uv1 = texCoord + float2( normalMap1UScroll, normalMap1VScroll) ;
	float2 uv2 = texCoord + float2( normalMap2UScroll, normalMap2VScroll) ;
	float2 uv3 = texCoord + float2( normalMap3UScroll, normalMap3VScroll) ;
	float2 uv4 = texCoord + float2( normalMap4UScroll, normalMap4VScroll );
	float3 nMap1 = tex2D( normalMap1, uv1 ).rgb * 2 - 1;
	float3 nMap2 = tex2D( normalMap2, uv2 ).rgb * 2 - 1;
	float3 nMap3 = tex2D( normalMap3, uv3 ).rgb * 2 - 1;
	float3 nMap4 = tex2D( normalMap4, uv4 ).rgb * 2 - 1;

	float3 normal1 = blendNormals( nMap1, nMap2, 0.5, 0.5 );
	float3 normal2 = blendNormals( nMap3, nMap4, 0.5, 0.5 );
	float3 nMap = blendNormals( normal1, normal2, 0.5, 0.5 );

	// Calculate refractions BY MEANS OF CHEESY HACKS!
	float2 refractUV = depthTexCoord;
	refractUV.x += sin( ( time * 3.2 ) ) * 0.001 + (nMap.x * 0.01);
	refractUV.y += sin( ( time * 3.3 ) ) * 0.005 + (nMap.y * 0.05);
	
	float newDepth = decodeDepth( tex2D( depthAndNormalMap, refractUV ).xy );
	float3 newPos = newDepth * dir;
	float3 newRelativePos = newPos - planeVS;

	float4 refract;
	if (newRelativePos.z < 0)
	{
		refract = tex2D( refractionMap, refractUV );
		if ( refract.x < 0.15 && refract.y < 0.15 && refract.z < 0.15 )
			refract = tex2D( refractionMap, depthTexCoord );
	}
	else
		refract = tex2D( refractionMap, depthTexCoord );

	return float4( ( refract.rgb * ( 1.0f - fog.a ) ) +
		( fog.rgb * fog.a ), 1 );
}

//----------------------------------------------------------------------------
// Pre-deferred lighting pass
//----------------------------------------------------------------------------
void waterDeferred_vp(float4   position  : POSITION,
                      float3   normal    : NORMAL,
                      float4   tangent   : TANGENT0,
                      float2   texCoord  : TEXCOORD0,
              uniform float    time,
              uniform float    waveAmplitude,
              uniform float    waveFrequency,
              uniform float    waveSpeed,
              uniform float    farDistance,
              uniform float4x4 worldViewProj,
              uniform float4x4 world,
                  out float4   oPosition : POSITION,
                  out float2   oTexCoord : TEXCOORD0,
                  out float4   oNormal   : TEXCOORD1,
                  out float3   oTangent  : TEXCOORD2,
                  out float3   oBinormal : TEXCOORD3)
{
    float4 postWavePos = position;
    postWavePos.z += sin(((time * waveSpeed) + texCoord.x) * waveFrequency) *
		waveAmplitude;
    oPosition = mul( worldViewProj, postWavePos );
    oTexCoord = texCoord;

    // Calculate binormal and tangent from the derivate of the wave function
    float derivate = cos(((time * waveSpeed) + texCoord.x) * waveFrequency) *
		waveAmplitude;
    oBinormal = float3(1, 0, derivate);
	oTangent = float3(0, 1, derivate);

	// Normal is the cross product of the binormal and the tangent
	oNormal.xyz = normalize(float3(-derivate, -derivate, 1));
	oNormal.w = oPosition.w / farDistance;
}

void waterDeferred_fp(float2    texCoord        : TEXCOORD0,
                      float4    normal          : TEXCOORD1,
                      float3    tangent         : TEXCOORD2,
                      float3    binormal        : TEXCOORD3,
              uniform float3    materialSpecular,
              uniform float     shininess,
              uniform sampler2D normalMap1      : register(s0),
              uniform sampler2D normalMap2      : register(s1),
              uniform sampler2D normalMap3      : register(s2),
              uniform sampler2D normalMap4      : register(s3),
              uniform float     normalMap1UScroll,
              uniform float     normalMap1VScroll,
              uniform float     normalMap2UScroll,
              uniform float     normalMap2VScroll,
              uniform float     normalMap3UScroll,
              uniform float     normalMap3VScroll,
              uniform float     normalMap4UScroll,
              uniform float     normalMap4VScroll,
                  out float4    oDepthAndNormalBuffer : COLOR0,
                  out float4    oDiffuseBuffer  : COLOR1,
                  out float4    oSpecularBuffer : COLOR2)
{
    oDiffuseBuffer = float4(0.0f, 0.3f, 0.5f, 1.0f);

    float2 uv1 = texCoord + float2(normalMap1UScroll, normalMap1VScroll);
    float2 uv2 = texCoord + float2(normalMap2UScroll, normalMap2VScroll);
    float2 uv3 = texCoord + float2(normalMap3UScroll, normalMap3VScroll);
    float2 uv4 = texCoord + float2(normalMap4UScroll, normalMap4VScroll);
    float3 nMap1 = tex2D(normalMap1, uv1).rgb * 2 - 1;
    float3 nMap2 = tex2D(normalMap2, uv2).rgb * 2 - 1;
    float3 nMap3 = tex2D(normalMap3, uv3).rgb * 2 - 1;
    float3 nMap4 = tex2D(normalMap4, uv4).rgb * 2 - 1;
    
    float3 normal1 = blendNormals(nMap1, nMap2, 0.5, 0.5);
    float3 normal2 = blendNormals(nMap3, nMap4, 0.5, 0.5);
    float3 nMap = blendNormals(normal1, normal2, 0.5, 0.5);

    oDepthAndNormalBuffer.zw = encodeNormal( normalize( tangent * nMap.x +
        binormal * nMap.y + normal.xyz * nMap.z ) );
    oDepthAndNormalBuffer.xy = encodeDepth( normal.w );
    
    oSpecularBuffer.xyz = materialSpecular;
    oSpecularBuffer.w = shininess / 256;
}