/*********************************************************************NVMH3****
Copyright NVIDIA Corporation 2002
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS
BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS)
ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF NVIDIA HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
******************************************************************************/

/*******************************************************************************

    Parameters

*******************************************************************************/

// Transform matrix from model space to projection space
float4x4 worldViewProjection : WorldViewProjection;

// Eye position in model space
float3 eyePosition : EyePos;

// Light attributes
float3 lightPosition : LightPos;
float3 lightColor = { 1, 1, 1 };
float3 ambient : Ambient <
    float uimin = 0;
    float uimax = 1;
    float uistep = 0.05;
> = { 0.1, 0.1, 0.1 };

// Material attributes
texture diffuseMap : DiffuseMap;
sampler2D diffuseMapSampler = sampler_state {
    Texture = <diffuseMap>;
    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
};
float3 Ka = { 1, 1, 1 };
float3 Kd = { 0.8, 0.8, 0.8 };
float3 Ks = { 0.9, 0.9, 0.9 };

/*******************************************************************************

    Technique for NV3x 

*******************************************************************************/

/****** Parameters ******/

// Material shininess
float shininess = 8;

/****** Programs ******/

// Vertex program
void DiffuseSpecularVertex(
                           float4 position       : POSITION,  // in model space
                           float2 texCoord       : TEXCOORD0,
                           float3 normal         : NORMAL,    // in model space
                       out float4 oPosition      : POSITION,  // in projection space
                       out float2 oTexCoord      : TEXCOORD0, // in model space
                       out float3 oNormal        : TEXCOORD1, // in model space
                       out float3 oModelPosition : TEXCOORD2, // in model space
                   uniform float4x4 modelViewProj
                          )
{

    // Pass texture coordinates for fetching the diffuse map in the fragment program
    oTexCoord = texCoord;

    // Pass normal
    oNormal = normal;

    // Pass position
    oModelPosition = position.xyz;

    // Transform position to projection space
    oPosition = mul(modelViewProj, position);
}

// Fragment program
float4 DiffuseSpecularFragment(
                             float2 texCoord : TEXCOORD0,
                             float3 normal   : TEXCOORD1, // in model space
                             float4 position : TEXCOORD2, // in model space
                     uniform sampler2D diffuseMap,
                     uniform float3 ambientLight,
                     uniform float3 lightColor,
                     uniform float3 lightPosition,        // in model space
                     uniform float3 eyePosition,
                     uniform float3 Ka,
                     uniform float3 Kd,
                     uniform float3 Ks,
                     uniform float shininess
                             ) : COLOR
{
    // Compute ambient light
    float3 ambient = Ka * ambientLight;

    // Compute diffuse light
    float3 N = normalize(normal);
    float3 L = normalize(lightPosition - position.xyz);
    float diffuseLight = max(dot(L, N), 0);
    float3 diffuse = Kd * lightColor * diffuseLight;

    // Compute specular light
    float3 V = normalize(eyePosition - position.xyz);
    float3 H = normalize(L + V);
    float specularLight = pow(max(dot(H, N), 0), shininess);
    if (diffuseLight <= 0) specularLight = 0;
    float3 specular = Ks * lightColor * specularLight;

    // Compute final color
    float3 color = (ambient + diffuse) * tex2D(diffuseMap, texCoord).xyz + specular;
    return float4(color, 1);
}

// Cg-based technique to get diffuse and specular lighting (with high exponent) in two passes on NV3x hardware
technique DiffuseSpecularNV3x
{
    pass p0
    {
        Zenable = true;
        ZWriteEnable = true;
        CullMode = CW;
        VertexShader = compile vs_2_0 DiffuseSpecularVertex(worldViewProjection);
        PixelShader = compile ps_2_0 DiffuseSpecularFragment(diffuseMapSampler, ambient, lightColor, lightPosition, eyePosition, Ka, Kd, Ks, shininess);
    }
}

/*******************************************************************************

    Technique for NV2x 

*******************************************************************************/

/****** Parameters ******/

texture normalizationMap : NormalizationMap;
samplerCUBE normalizationMapSampler = sampler_state {
    Texture = <normalizationMap>;
    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = None;
};

/****** Programs ******/

// This function approximately normalizes a vector V by using the first step of the Newton-Raphson re-normalization:
//      N = V / |V| is approximated by N = V + (1 - |V|^2) * (V / 2)
// We assume that V is range-compressed (so that its coordinates are all between 0 and 1).
float3 NormalizeApprox(float3 V)
{
    float3 U = 2 * (V - 0.5); // Uncompress the input vector
    return (1 - saturate(dot(U, U))) * (V - 0.5) + U;
}

// Vertex program for first pass (diffuse color)
void DiffuseVertex(
                   float4 position : POSITION,   // in model space
                   float2 texCoord : TEXCOORD0,
                   float3 normal : NORMAL,       // in model space
               out float4 oPosition : POSITION,  // in projection space
               out float2 oTexCoord : TEXCOORD0,
               out float3 oNormal : TEXCOORD1,   // in model space
               out float3 oLightVector : COLOR0, // in model space
           uniform float4x4 worldViewProj,
           uniform float3 lightPosition          // in model space
                  )
{
    // Pass texture coordinates for fetching the diffuse map in the fragment program
    oTexCoord = texCoord;

    // Pass normal
    oNormal = normal;

    // Compute light vector
    float3 lightVector = normalize(lightPosition - position.xyz);

    // Pass light vector as a color (range-compressed)
    oLightVector = 0.5 * lightVector + 0.5;

    // Transform position to projection space
    oPosition = mul(worldViewProj, position);
}

// Fragment program for first pass (diffuse color)
// It uses two interchangeable methods to normalize a vector:
// - Using a normalization cube map,
// - Computing an approximation of the normalized vector
float4 DiffuseFragment(
                     float2 texCoord : TEXCOORD0,
                     float3 normal : TEXCOORD1,   // in model space
                     float3 lightVector : COLOR0, // in model space
             uniform sampler2D diffuseMap,
             uniform samplerCUBE normalizationMap,
             uniform float3 ambientLight,
             uniform float3 lightColor,
             uniform float3 Ka,
             uniform float3 Kd
                     ) : COLOR
{
    // Compute ambient light
    float3 ambient = Ka * ambientLight;

    // Normalize normal using a normalization cube map
    float3 N = 2 * (texCUBE(normalizationMap, normal).xyz - 0.5);

    // Normalize light vector using an approximation
    float3 L = NormalizeApprox(lightVector);

    // Compute diffuse light
    float diffuseLight = saturate(dot(L, N));
    float3 diffuse = Kd * lightColor * diffuseLight;

    // Compute final color
    float3 color = (ambient + diffuse) * tex2D(diffuseMap, texCoord).xyz;

    // Store self-shadow term in the alpha channel
    return float4(color, 4 * diffuseLight);
}

// Vertex program for second pass (specular color)
void SpecularVertex(
                    float4 position : POSITION,       // in model space
                    float3 normal : NORMAL,           // in model space
                out float4 oPosition : POSITION,      // in projection space
                out float3 oHalfAngleVector : COLOR0, // in model space
                out float3 oNormal : TEXCOORD0,       // in model space
            uniform float4x4 worldViewProj,
            uniform float3 eyePosition,
            uniform float3 lightPosition              // in model space
                   )
{
    // Pass normal
    oNormal = normal;

    // Compute light vector
    float3 lightVector = normalize(lightPosition - position.xyz);

    // Compute view vector
    float3 viewVector = normalize(eyePosition - position.xyz);

    // Compute half angle vector
    float3 halfAngleVector = normalize(lightVector + viewVector);

    // Pass half angle vector as a color (range-compressed)
    oHalfAngleVector = 0.5 * halfAngleVector + 0.5;

    // Transform position to projection space
    oPosition = mul(worldViewProj, position);
}


// Fragment program for first pass (diffuse color)
// It uses two interchangeable methods to normalize a vector:
// - Using a normalization cube map,
// - Computing an approximation of the normalized vector
float4 SpecularFragment(
                      float3 halfAngleVector : COLOR0, // in model space
                      float3 normal : TEXCOORD0,       // in model space
                      uniform samplerCUBE normalizationMap,
                      uniform float3 lightColor,
                      uniform float3 Ks
                      )  : COLOR
{
    // Normalize normal using a normalization cube map
    float3 N = 2 * (texCUBE(normalizationMap, normal) - 0.5);

    // Normalize half angle vector using approximation
    float3 H = NormalizeApprox(halfAngleVector);

    // Compute specular color
    float specularLight = saturate(dot(N, H));
    float specularLight2 = specularLight * specularLight;
    float specularLight4 = specularLight2 * specularLight2;
    float specularLight8 = specularLight4 * specularLight4;
    float3 specular = Ks * lightColor * specularLight8;

    // Compute final color
    return float4(specular, 1);
}

/****** Technique ******/

// Cg-based technique to get diffuse and specular lighting (with high exponent) in two passes on NV2x hardware
technique DiffuseSpecularNV2x
{
    pass p0
    {
        Zenable = true;
        ZWriteEnable = true;
        CullMode = CW;
        VertexShader = compile vs_1_1 DiffuseVertex(worldViewProjection, lightPosition);
        PixelShader = compile ps_1_1 DiffuseFragment(diffuseMapSampler, normalizationMapSampler, ambient, lightColor, Ka, Kd);
    }
    pass p1
    {
        Zenable = true;
        ZWriteEnable = false;
        AlphaBlendEnable = true;
        SrcBlend = DestAlpha; // Multiply the specular color by the self-shadow term computed in the first pass
        DestBlend = One;
        CullMode = CW;
        VertexShader = compile vs_1_1 SpecularVertex(worldViewProjection, eyePosition, lightPosition);
        PixelShader = compile ps_1_1 SpecularFragment(normalizationMapSampler, lightColor, Ks);
    }
}
