/**
 * Copyright (C) 2013 Stellar Jockeys.
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Stellar Jockeys and its suppliers, if any.  The intellectual and technical
 * concepts contained herein are proprietary to Stellar Jockeys and its suppliers.
 * Dissemination of this information or reproduction of this material is
 * strictly forbidden unless prior written permission is obtained from
 * Stellar Jockeys.
 *
 * @file fragnormal.glsl
 * @author Harry Hsiao
 * @date 07/25/13
 */
#ifndef FRAGNORMAL_GLSL
#define FRAGNORMAL_GLSL

// Requires constants.glsl
#ifndef CONSTANTS_GLSL
#error constants.glsl should also be included
#endif

const float INV_DELTA_DEPTH = 1.0 / MAX_DELTA_DEPTH;
const float INV_CHANGE_DELTA_DEPTH = 1.0 / MAX_CHANGE_DELTA_DEPTH;

/**
 * @brief 0 if |delta| is below a threshold, otherwise 1.
 */
float Indicator(in float delta, in float invThreshold)
{
    return  min(floor(abs(delta) * invThreshold), 1.0);
}

/**
 * @brief Returns one of three values based on an indicator.
 *
 * @param i Must be -1, 0, or 1.
 * @return a if i = -1
 *         b if i = 0
 *         c if i = 1
 */
float Select(in float a, in float b, in float c, in float i)
{
    /*
     * Based on linear interpolation between consecutive values.
     *
     * a is multiplied by (i + |i|) / 2
     * b is multiplied by (1 - |i|)
     * c is multiplied by (i - |i|) / 2
     */
    float absI = abs(i);
    float result = (c - a) * i;
    result += (c + a - 2.0 * b) * absI;
    result = result * 0.5 + b;
    return result;
}

/**
 * @brief Determines a delta depth given three values.
 *
 * Max thresholds are used to conpensate for C0 and C1 discontinuities.
 */
float DeltaDepth(in float center, in float backward, in float forward)
{
    float backwardDelta = center - backward;
    float forwardDelta = forward - center;
    float deltaChange = forwardDelta - backwardDelta;

    float backwardBreak = Indicator(backwardDelta, INV_DELTA_DEPTH);
    float forwardBreak = Indicator(forwardDelta, INV_DELTA_DEPTH);
    float bent = Indicator(deltaChange, INV_CHANGE_DELTA_DEPTH);

    float bentDelta = mix(backwardDelta, forwardDelta, bent * -0.5 + 0.5);
    return Select(backwardDelta, bentDelta, forwardDelta, backwardBreak - forwardBreak);
}

/**
 * @brief Samples the depth texture to derive a surface normal.
 *
 * In general, the function takes the change in depth in two directions
 * and takes their cross product for the normal.
 *
 * @param textureCoord The texture coordinate of the current fragment.
 * @param depth The depth value of the current fragment.
 * @param tapDelta The texture coordinate change per pixel.
 * @param pixelsPerDepth A conversion factor for pixels distances and depth values.
 * @param depthTexture The depth texture sampler.
 */
vec3 DeriveNormal(in vec2 textureCoord,
                  in float depth,
                  in vec2 tapDelta,
                  in float pixelsPerDepth,
                  in sampler2D depthTexture)
{
    const vec2 X_DELTA = vec2(1.0,-1.0);
    const vec2 Y_DELTA = vec2(1.0, 1.0);
    vec2 xDelta = tapDelta * X_DELTA;
    vec2 yDelta = tapDelta * Y_DELTA;

    float x0 = texture2D(depthTexture, textureCoord - xDelta).r;
    float x1 = texture2D(depthTexture, textureCoord + xDelta).r;
    float y0 = texture2D(depthTexture, textureCoord - yDelta).r;
    float y1 = texture2D(depthTexture, textureCoord + yDelta).r;

    float x = DeltaDepth(depth, x0, x1);
    float y = DeltaDepth(depth, y0, y1);
    vec3 normal = cross(vec3(X_DELTA, x * -pixelsPerDepth), vec3(Y_DELTA, y * -pixelsPerDepth));

    return normalize(normal);
}

vec3 DeriveNormalLow(in vec2 textureCoord,
                     in float depth,
                     in vec2 tapDelta,
                     in float pixelsPerDepth,
                     in sampler2D depthTexture)
{
    const vec2 X_DELTA = vec2(1.0,-1.0);
    const vec2 Y_DELTA = vec2(1.0, 1.0);
    vec2 xDelta = tapDelta * X_DELTA;
    vec2 yDelta = tapDelta * Y_DELTA;

    float x1 = texture2D(depthTexture, textureCoord + xDelta).r;
    float y1 = texture2D(depthTexture, textureCoord + yDelta).r;

    float x = x1 - depth;
    float y = y1 - depth;
    vec3 normal = cross(vec3(X_DELTA, x * -pixelsPerDepth), vec3(Y_DELTA, y * -pixelsPerDepth));

    return normalize(normal);
}

vec3 DeriveNormalFromDiffuse(in vec2 textureCoord,
                             in float depth,
                             in vec2 tapDelta,
                             in float pixelsPerDepth,
                             in float perturbation,
                             in sampler2D diffuseTexture)
{
    const vec2 X_DELTA = vec2(1.0, 0.0);
    const vec2 Y_DELTA = vec2(0.0, 1.0);
    vec2 xDelta = tapDelta * X_DELTA;
    vec2 yDelta = tapDelta * Y_DELTA;

    float x1 = dot(texture2D(diffuseTexture, textureCoord + xDelta).rgb, DESATURATE) * perturbation;
    float y1 = dot(texture2D(diffuseTexture, textureCoord + yDelta).rgb, DESATURATE) * perturbation;

    float x = x1 - depth;
    float y = y1 - depth;
    vec3 normal = cross(vec3(X_DELTA, x * -pixelsPerDepth), vec3(Y_DELTA, y * -pixelsPerDepth));

    normal = normalize(normal);
    normal.z -= 1.0;
    normal = clamp(normal, -0.1, 0.1);
    normal += vec3(0.0, 0.866, 0.5);

    return normalize(normal);
}

vec3 NormalToColor(in vec3 normal)
{
    return normal * 0.5 + 0.5;
}

vec3 ColorToNormal(in vec3 color)
{
    // Normalize to ensure unit vector.
    return normalize(color * 2.0 - 1.0);
}

#endif // FRAGNORMAL_GLSL
