#version 330 core

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;

out vec4 FragColor;

// rotate helper adapted from macro
vec3 rotateAxis(vec3 v, float idx, float e, float t)
{
    vec3 a = normalize(cos(vec3(2.0*idx, 2.0, 0.0) + e - t*0.05));
    return a*dot(a, v) - cross(a, v);
}

vec3 tanhVec(vec3 x)
{
    vec3 e2x = exp(2.0 * x);
    return (e2x - vec3(1.0)) / (e2x + vec3(1.0));
}

void mainImage(out vec4 O, in vec2 I)
{
    vec3 p;
    vec3 a;
    vec3 r = normalize(vec3(I + I, 0.0) - iResolution.xyy);
    float t = 0.0;
    float v = 0.0;
    float s = 0.0;
    float e = iTime * 0.4;

    // Rotation of ray direction
    r = rotateAxis(r, 2.0, e, t);

    vec3 accum = vec3(0.0);

    for (int iter = 0; iter < 40; ++iter)
    {
        // Define position based on ray direction
        p = t * r;

        // Move camera back
        p.z += 3.0;

        // Rotation of position
        p = rotateAxis(p, 1.0, e, t);

        // Cylindrical coordinates
        p.xy = vec2(length(p.xy), 6.0 * atan(p.y / p.x));

        // Distortions from triangle waves
        for (int si = 1; si <= 4; ++si)
        {
            float sf = float(si);
            p.yz += asin(sin(p.zy * sf)) / sf;
        }

        // Grids of lines based on angle and height
        float sval = mod(p.x + e, 4.0) - 2.0;
        v = min(length(vec2(sval, mod(p.z + e, 0.8) - 0.4)), 2.0 * length(vec2(sval, mod(p.y, 0.2) - 0.1)));

        // Color based on distance travelled
        vec3 add = (1.0 + cos(vec3(t * 0.3, t * 0.3 + 1.0, t * 0.3 + 2.0))) / max(v, 1e-5);
        accum += add;

        t += v * 0.4;
    }

    O = vec4(tanhVec(accum / 300.0), 1.0);
}

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