#version 330 core

#define PI 3.14159265

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform sampler2D iChannel0;

out vec4 FragColor;

// The x and y coordinates of the camera while at postion z. Also used as the center of the tunnel at position z
vec2 path(float z){
    return vec2(.5*sin(z), .5*sin(z*.7));
}

// Distance to the edge of the tunnel
float map(vec3 p) {
    float d = -length(p.xy - path(p.z)) + 1.2 + .3 * sin(p.z*.4);
    return d;
}

// Normal of distance function using forward differences
vec3 normal(vec3 p){
    float d = map(p);
    vec2 e = vec2(0.01, 0.0);

    vec3 n = d - vec3(
        map(p - e.xyy),
        map(p - e.yxy),
        map(p - e.yyx)
    );

    return normalize(n);
}

// Smooth maximum function
float sMax(float a, float b, float k) {
    return log(exp(k*a) + exp(k*b)) / k;
}

// Bump map, gives the height of the tiles
float bumpFunction(vec3 p) {
    vec2 c = path(p.z);
    float id = floor(p.z*4.-.25);
    float h = .5 + .5*sin(atan(p.y - c.y, p.x - c.x) * 20. + 1.5 * (2.*mod(id, 2.)-1.) + iTime * 5.);
    h = sMax(h, .5 + .5*sin(p.z *8.*PI), 16.);
    h *= h;
    h *= h*h;
    return 1.-h;
}

// Calculate normal of the bump map using central differences and add this to the normal of the tunnel
vec3 bumpNormal(vec3 p, vec3 n, float bumpFactor) {
    vec3 e = vec3(0.01, 0.0, 0.0);

    float f  = bumpFunction(p);
    float fx = bumpFunction(p - e.xyy);
    float fy = bumpFunction(p - e.yxy);
    float fz = bumpFunction(p - e.yyx);
    // Forward difference gradient (cheaper alternative)
    //vec3 grad = vec3(fx - f, fy - f, fz - f) / e.x;

    float fx2 = bumpFunction(p + e.xyy);
    float fy2 = bumpFunction(p + e.yxy);
    float fz2 = bumpFunction(p + e.yyx);
    // Central difference gradient
    vec3 grad = (vec3(fx - fx2, fy - fy2, fz - fz2)) / (e.x * 2.0);

    // Remove normal component so we only perturb tangentially
    grad -= n * dot(n, grad);

    // Combine with original normal
    return normalize(n + grad * bumpFactor);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord*2. - iResolution.xy)/iResolution.y;

    // Camera position and camera target follow the path
    float vel = iTime * 1.5;
    vec3 ro = vec3(path(vel - 1.), vel - 1.);
    vec3 ta = vec3(path(vel), vel);

    // Set up ray direction
    vec3 fwd = normalize(ta - ro);
    vec3 up = vec3(0, 1., 0);
    vec3 right = cross(fwd, up);;
    up = cross(right, fwd);
    float fl = 1.2;
    vec3 rd = normalize(fwd + fl * (uv.x*right + uv.y*up));

    // Set up golden glow
    float glow = 0.0;
    vec3 glowCol = vec3(9., 7., 4.);

    // Set up raymarching
    float t = 0.0;
    vec3 col = vec3(0.0);

    for (int i=0; i<125; i++){
        vec3 p = ro + t*rd;
        float d = map(p);

        // Add glow each step based on distance to edge tunnel
        glow += exp(-d * 8.) * .005;

        if (d<.01){
            // Get light direction from normal, then distort normal for the tiles
            vec3 n = normal(p);
            vec3 lightDir = n;
            n = bumpNormal(p, n, .02);

            // Get ring and angle of position
            vec2 c = path(p.z);
            vec2 id = vec2(floor(p.z*4.-.25), atan(p.y-c.y, p.x-c.x));


            // Set the color of the tile based on the id
            vec3 tileCol = vec3(.7);
            tileCol += .4*vec3(sin(id.x), cos(id.x), 0);
            tileCol += .3*sin(id.x*.5 + id.y*6. - iTime*4.);
            vec3 tileGray  = vec3(.5);
            float height = bumpFunction(p);
            vec3 baseCol = mix(tileGray, tileCol, height);

            // Lighting: diffuse, specular and reflected light
            float diffuseL = max(dot(n, lightDir), 0.);
            col += diffuseL * baseCol;

            vec3 h = normalize(lightDir - rd);
            float specL = pow(max(dot(n, h), 0.), 64.);
            col += specL * .3;

            vec3 r = reflect(rd, n);
            // convert 3D reflection vector to 2D UV for sampler2D (spherical-ish mapping)
            vec2 refUV = normalize(r).xy * 0.5 + 0.5;
            vec3 reflCol = texture(iChannel0, refUV).rgb;
            col = mix(col, reflCol, .3);

            break;
        }

        t+=d;
    }

    // Add the golden glow
    col += glowCol*glow;

    // Gamma correction
    col=pow(col, vec3(2.2));

    // Output to screen
    fragColor = vec4(col,1.0);
}

void main()
{
    vec4 o;
    mainImage(o, gl_FragCoord.xy);
    o.a *= uAlpha;
    FragColor = o;
}
