// Green Gandalf's revised Blended Terrain Normal Mapping Shader
//
//   Designed for use with "flat" terrains such as Advanced Terrain.
//   Requires four textures plus associated normal maps if available.
//   The first three textures are smoothly blended according to altitude.
//   The resulting texture is then blended with a fourth texture for use on steep slopes.
//
//   The normal maps are then blended in the same way as the first four textures.
//
//   Contains two techniques: with and without bump mapping.
//   Assumes a single directional light such as that corresponding to a distant sun.
//
//   ** This version contains an in-built assumption for the vertex tangents and will not
//      work correctly for all objects.
//
// Created 8 April 2013, edited 13 April 2013.
//

matrix mWVP : WorldViewProjection;
matrix mW : World;
matrix mWI: WorldInverse;

float4 lightDirection   = {-0.707107, -0.707107, 0.0, 1.0};
float4 ambientColour  = {0.2, 0.2, 0.2, 1.0};
float4 lightColour = {1.0, 1.0, 1.0, 1.0};
float4 UVTiling = {4, 4, 4, 4}; // one for each main texture - assumes U and V are tiled equally
float contrast = 4.0;  // higher values increase the contrast for the steep slope texture
float4 border = { 9400, 9500, 9600, 9700 }; // the first two values represent the border between the first two textures
                                            // the second two represent the border between the second and third
float middle = 9550; // this is the middle altitude for the second texture

float3 defaultTangent = {1.0, 0.0, 0.0};

texture detailMap1 < string ResourceName = ""; >; // low altitude/sea level
texture detailMap2 < string ResourceName = ""; >; // medium altitude
texture detailMap3 < string ResourceName = ""; >; // high altitude
texture detailMap4 < string ResourceName = ""; >; // steep slopes/cliffs
texture normalMap1 < string ResourceName = ""; >;
texture normalMap2 < string ResourceName = ""; >;
texture normalMap3 < string ResourceName = ""; >;
texture normalMap4 < string ResourceName = ""; >;

sampler2D detail1 = sampler_state 

{ texture = <detailMap1>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D detail2 = sampler_state 
{ texture = <detailMap2>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D detail3 = sampler_state 
{ texture = <detailMap3>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D detail4 = sampler_state 
{ texture = <detailMap4>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D nMap1 = sampler_state 
{ texture = <normalMap1>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D nMap2 = sampler_state 
{ texture = <normalMap2>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D nMap3 = sampler_state 
{ texture = <normalMap3>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};
sampler2D nMap4 = sampler_state 
{ texture = <normalMap4>;
  minFilter = linear;
  magFilter = linear;
  mipFilter = linear;
  addressU = wrap;
  addressV = wrap;
};

struct VSInput
{ float4 pos          : position;
//  float3 tangent      : tangent;
  float3 normal       : normal;
  float2 UV0          : texcoord0;
};

struct VSOutput1
{ float4 pos          : position;
  float4 UV12         : texcoord0;
  float4 UV34         : texcoord1;
  float  diffuse      : texcoord2;
  float slopeBlend    : texcoord3;
  float altitude      : texcoord4;
};

struct VSOutput2
{ float4 pos          : position;
  float4 UV12         : texcoord0;
  float4 UV34         : texcoord1;
  float3 light        : texcoord2;
  float slopeBlend    : texcoord3;
  float altitude      : texcoord4;
};

VSOutput1 GGBlendVShaderFlat(VSInput In, VSOutput1 Out)
{ float4 tempPos = mul(In.pos, mW);
  Out.pos = mul(In.pos, mWVP);
  Out.UV12 = In.UV0.xyxy * UVTiling.xxyy;
  Out.UV34 = In.UV0.xyxy * UVTiling.zzww;
  float3 wNormal = normalize(mul(In.normal, (float3x3) mW));
  Out.diffuse  = saturate(dot(wNormal, -normalize(lightDirection.xyz)));
  Out.slopeBlend = saturate(0.5 + contrast * (wNormal.y - 0.5));
  Out.altitude = tempPos.y;
  return Out;
}

VSOutput2 GGBlendBumpVShaderFlat(VSInput In, VSOutput2 Out)
{ float4 tempPos = mul(In.pos, mW);
  Out.pos = mul(In.pos, mWVP);
  Out.UV12 = In.UV0.xyxy * UVTiling.xxyy;
  Out.UV34 = In.UV0.xyxy * UVTiling.zzww;

  //smooth tangents/binormals - trick copied from Dark Shader samples
  float3 normal = normalize(In.normal);
  float3 binormal = normalize(cross(normal, defaultTangent));
  float3 tangent = normalize(cross(binormal, normal));

  float3x3 TSM = {tangent, binormal, normal};
  TSM = transpose(TSM);          // tangent space matrix
  float3 temp  = -mul(lightDirection.xyz, (float3x3) mWI);
  Out.light    = normalize(mul(temp, TSM));
  float3 wNormal = normalize(mul(In.normal, (float3x3) mW));
  Out.slopeBlend = saturate(0.5 + contrast * (wNormal.y - 0.5));
  Out.altitude = tempPos.y;
  return Out;
}

struct PSOutput
{ float4 colour : color;
};

PSOutput GGBlendPShader(VSOutput1 In, PSOutput Out)
{ float4 base1 = tex2D(detail1, In.UV12.xy);
  float4 base2 = tex2D(detail2, In.UV12.zw);
  float4 base3 = tex2D(detail3, In.UV34.xy);
  float4 base4 = tex2D(detail4, In.UV34.zw);
  float test = (In.altitude < middle);
  // first do the altitude blending
  float4 base  = lerp(lerp(base3, base2, test), lerp(base2, base1, test),
                      1.0-smoothstep(lerp(border.z, border.x, test), lerp(border.w, border.y, test), In.altitude));
  // now do the slope based blending
  base = lerp(base4, base, In.slopeBlend);
  Out.colour = base * (ambientColour + In.diffuse * lightColour);
  return Out;
}

PSOutput GGBlendBumpPShader(VSOutput2 In, PSOutput Out)
{ float3 tempLightDir = In.light;
  float4 base1 = tex2D(detail1, In.UV12.xy);
  float4 base2 = tex2D(detail2, In.UV12.zw);
  float4 base3 = tex2D(detail3, In.UV34.xy);
  float4 base4 = tex2D(detail4, In.UV34.zw);
  float test = (In.altitude < middle);
  // first do the altitude blending
  float4 base  = lerp(lerp(base3, base2, test), lerp(base2, base1, test),
                      1.0-smoothstep(lerp(border.z, border.x, test), lerp(border.w, border.y, test), In.altitude));
  // now do the slope based blending
  base = lerp(base4, base, In.slopeBlend);
  // now calculate the lighting
  float3 normal1 = 2 * tex2D(nMap1, In.UV12.xy).xyz - 1.0;
  float3 normal2 = 2 * tex2D(nMap2, In.UV12.zw).xyz - 1.0;
  float3 normal3 = 2 * tex2D(nMap3, In.UV34.xy).xyz - 1.0;
  float3 normal4 = 2 * tex2D(nMap4, In.UV34.zw).xyz - 1.0;
  // first do the altitude blending
  float3 normal  = lerp(lerp(normal3, normal2, test), lerp(normal2, normal1, test),
                      1.0-smoothstep(lerp(border.z, border.x, test), lerp(border.w, border.y, test), In.altitude));
  // now do the slope based blending
  normal = normalize(lerp(normal4, normal, In.slopeBlend));
  float diffuse = saturate(dot(normal, tempLightDir));
  Out.colour = base * (ambientColour + diffuse * lightColour);
  return Out;
}

technique t0 // use this for blending with flat terrains 
{ pass p0
  { VertexShader = compile vs_2_0 GGBlendVShaderFlat();
    PixelShader = compile ps_2_0 GGBlendPShader();
  }
}

technique t1 // use this for blending and bump mapping with flat terrains
{ pass p0
  { VertexShader = compile vs_2_0 GGBlendBumpVShaderFlat();
    PixelShader = compile ps_2_0 GGBlendBumpPShader();
  }
}
