/**
 * 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 shieldwisps.frag
 * @date 07/13/15
 */
#version 120
uniform sampler2D uDepth;
uniform sampler2D uMaterial;
uniform vec3 uProjectionScale;
uniform float uProjectionBias;
uniform vec2 uBlend;

uniform vec4 uColor0;
uniform vec4 uColor1;
uniform vec4 uColor2;

varying vec2 vVertex;
varying vec3 vPosition;
varying vec3 vEllipsoid;
varying vec2 vOrientation;
varying float vColor;
varying float vOpacity;
varying float vProgress;
varying float vPriority;
varying mat4 vHits;

//#include <constants.glsl>
//#include <fragnormal.glsl>
//#include <fragblend.glsl>

const float Z_UNSET = -1000.0;

vec2 WispRing(in vec3 n, in vec3 p, in vec3 ellipsoid, in mat3 transform, in float time)
{
    vec2 result = vec2(0.0);
    result.y = dot(p.xy, n.xy) / -n.z;
    vec3 q = transform * vec3(p.xy, result.y);
    q /= ellipsoid;

    {
        float thickness = 0.15;
        float radius = (ellipsoid.x + ellipsoid.y + ellipsoid.z) / 3.0;
        float lower = 1.0 - (thickness / radius);
        float upper = 1.0 + (thickness / radius);
        lower *= lower;
        upper *= upper;
        float middle = (lower + upper) * 0.5;
        float scale = upper - middle;

        result.x = clamp(1.0 - (abs(dot(q, q) - middle) / scale), 0.0, 1.0);
    }

    {
        vec3 axis = transform * n;
        float k = 1.0 / (axis.z + 1.0);

        vec3 perp = transform * normalize(vec3(n.y * n.z, n.x * n.z, -2.0 * n.x * n.y));
        vec3 binormal = cross(axis, perp);
        vec3 norm_q = normalize(q);
        float cos_w = dot(perp, norm_q);

        float w = ((-0.090845 * cos_w * cos_w) - 0.159155) * cos_w + 0.25;
        w = mix(w, -w + 1.0, step(dot(binormal, norm_q), 0.0));
        w = mod(w + time, 1.0);

        result.x *= clamp(-6.0 * w + 1.0, 0.0, 1.0);
    }

    result.y = mix(Z_UNSET, result.y, ceil(result.x));

    return result;
}

vec4 GetColor(in float t)
{
    float c0 = clamp(1.0 - t, 0.0, 1.0);
    float c2 = clamp(t - 1.0, 0.0, 1.0);
    float c1 = 1.0 - c0 - c2;

    return uColor0 * c0 + uColor1 * c1 + uColor2 * c2;
}

vec4 HitWave(in vec4 hit, in vec3 direction, in float visibility)
{
    // TODO: Many computations will be recomputed for the front and back.
    vec3 hit_direction = hit.xyz;
    float hit_time = hit.w;
    float hit_intensity = length(hit_direction);
    hit_direction /= mix(hit_intensity, 1.0, step(hit_intensity, 0.0));

    float distance = dot(hit_direction, direction);
    float theta = ((-0.090845 * distance * distance) - 0.159155) * distance + 0.25;

    float front = mix(0.0, 0.33, hit_time);
    float tail_width = 0.125;
    float shade_tail = step(theta, front) * (theta + tail_width - front) / tail_width;

    float shade = visibility * mix(1.0, 0.0, hit_time) * hit_intensity;
    shade *= shade_tail;

    float color_select = mix(1.25, -0.25, hit_time);
    color_select *= shade_tail * shade_tail;

    vec4 color = GetColor(color_select);
    color.a *= shade;
    color.rgb *= color.a;

    return color;
}

vec4 CombineColors(in vec4 top, in vec4 bottom)
{
    vec4 combined = vec4(top + bottom * (1.0 - top.a));
    return combined;
}

void main()
{
    vec3 p = vec3(vVertex.xy, Z_UNSET);
    p.xy /= uProjectionScale.xy;
    p.xy -= vPosition.xy;

    mat3 iso = mat3( 0.70710678118,  0.70710678118, 0.0,
                    -0.35355339059,  0.35355339059, 0.86602540378,
                     0.61237243569, -0.61237243569, 0.5);
    mat3 iso_inv = transpose(iso);
    mat3 transform = mat3(vOrientation.x, -vOrientation.y, 0.0,
                          vOrientation.y,  vOrientation.x, 0.0,
                          0.0,             0.0,            1.0) * iso;
    mat3 transform_inv = transpose(transform);

    float t = abs(vProgress * 4.0 - 2.0) - 1.0;
    float sin_t = (t * t - 1.0) * (t * t * 0.2146 - 1.0);
    sin_t *= step(vProgress, 0.5) * 2.0 - 1.0;
    float cos_t = sqrt(1.0 - sin_t * sin_t);
    cos_t *= step(0.0, t) * 2.0 - 1.0;
    mat3 rotate = mat3( cos_t, sin_t, 0.0,
                       -sin_t, cos_t, 0.0,
                        0.0,   0.0,   1.0);

    float wisps = 0.0;

    float cycle = mod(vProgress * 16.0, 1.0);

#if 1
    {
        vec3 n = rotate * iso_inv * vec3(1.0, 0.0, 0.0);
        vec2 result = WispRing(n, p, vEllipsoid, transform, cycle);
        wisps = max(wisps, result.x);
        p.z = max(p.z, result.y);
    }
    {
        vec3 n = rotate * iso_inv * vec3(0.0, 1.0, 0.0);
        vec2 result = WispRing(n, p, vEllipsoid, transform, cycle);
        wisps = max(wisps, result.x);
        p.z = max(p.z, result.y);
    }
    {
        vec3 n = rotate * iso_inv * vec3(0.0, 0.0, 1.0);
        vec2 result = WispRing(n, p, vEllipsoid, transform, cycle);
        wisps = max(wisps, result.x);
        p.z = max(p.z, result.y);
    }
#endif

    vec2 texture_coord = vVertex.xy * 0.5 + 0.5;
    float scene_depth = texture2D(uDepth, texture_coord).r;
    vec4 hit_color = vec4(0.0);

#if 1
    {
        //solve for ellipsoid points
        vec3 l = (transform * vec3(p.xy, 0.0)) / vEllipsoid;
        vec3 r = transform[2] / vEllipsoid;
        float a = dot(r, r);
        float half_b = dot(l, r);
        float c = dot(l, l) - 1.0;

        float discrim = (half_b * half_b) - (a * c);
        discrim = sqrt(max(discrim, 0.0));

        vec3 p_plus = vec3(p.xy, (-half_b + discrim) / a);
        vec3 p_minus = vec3(p.xy, (-half_b - discrim) / a);
        vec3 q_plus = normalize(p_plus);
        vec3 q_minus = normalize(p_minus);

        float depth_plus = (p_plus.z + vPosition.z) * uProjectionScale.z + uProjectionBias;
        depth_plus = depth_plus * 0.5 + 0.5;
        float depth_minus = (p_minus.z + vPosition.z) * uProjectionScale.z + uProjectionBias;
        depth_minus = depth_minus * 0.5 + 0.5;

        float visibility = 1.0 - step(discrim, 0.0);
        float visibility_plus = visibility * step(depth_plus, scene_depth);
        float visibility_minus = visibility * step(depth_minus, scene_depth);

        {
            vec4 color = HitWave(vHits[0], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[0], q_plus, visibility_plus);
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        #if 1
        {
            vec4 color = HitWave(vHits[1], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[1], q_plus, visibility_plus);
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        {
            vec4 color = HitWave(vHits[2], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[2], q_plus, visibility_plus);
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        {
            vec4 color = HitWave(vHits[3], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[3], q_plus, visibility_plus);
            hit_color = CombineColors(color, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        #else
        {
            vec4 color = HitWave(vHits[1], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color.bgra, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[1], q_plus, visibility_plus);
            hit_color = CombineColors(color.bgra, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        {
            vec4 color = HitWave(vHits[2], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color.rbra, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[2], q_plus, visibility_plus);
            hit_color = CombineColors(color.rbra, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        {
            vec4 color = HitWave(vHits[3], q_minus, visibility_minus);
            color.a *= 0.75;
            hit_color = CombineColors(color.brga, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_minus.z, ceil(color.a)));

            color = HitWave(vHits[3], q_plus, visibility_plus);
            hit_color = CombineColors(color.brga, hit_color);
            p.z = max(p.z, mix(Z_UNSET, p_plus.z, ceil(color.a)));
        }
        #endif
    }
#endif

    if (p.z <= Z_UNSET)
    {
        discard;
    }

    float depth = (p.z + vPosition.z) * uProjectionScale.z + uProjectionBias;
    depth = depth * 0.5 + 0.5;

    float depth_fail = 1.0 - step(depth, scene_depth);
    float scene_priority = texture2D(uMaterial, texture_coord).b + (1.0 / 255.0);
    float priority_fail = step(vPriority, scene_priority);

    wisps *= 1.0 - (depth_fail * priority_fail);
    float occluded = depth_fail * (1.0 - priority_fail);

    vec4 color = GetColor(vColor);
    color.a *= vOpacity;
    vec3 occluded_color = vec3(mix(dot(color.rgb, DESATURATE), 1.0, 0.5));

    color.rgb = mix(color.rgb, occluded_color, occluded);
    color.a *= wisps;
    color = hit_color + color * (1.0 - hit_color.a);

    gl_FragColor = BlendedColor(color.rgb, color.a, uBlend);
}
