#version 330 core
out vec4 FragColor;
in vec2 vUV;

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

const int L = 120;

#define T (iTime * 0.85)
#define N normalize
#define P(z) (vec3(tanh(cos((z) * 0.45) * 0.35) * 8.0, tanh(cos((z) * 0.32) * 0.4) * 8.0, (z)))

mat2 rot(float a) {
    float c = cos(a);
    float s = sin(a);
    return mat2(c, s, -s, c);
}

// distance to tunnel
float tunnel(vec3 p) {
    return 0.5 - length(p.xy - P(p.z).xy);
}

// distance to fractal
float fractal(vec3 p) {
    float w = 0.6;
    float l = 1.0;

    p *= 2.0;
    p.xz += tanh(sin(T * 0.25) * 5.0) * 4.0;

    for (int ii = 0; ii < 8; ++ii)
    {
        p = abs(sin(p)) - 1.0;
        l = 1.6 / dot(p, p);
        p *= l;
        w *= l;
    }
    return length(p) / w;
}

void mainImage(out vec4 o, in vec2 u) {
    float s = 0.002;
    float d = 0.0;
    vec3 r = iResolution;
    vec2 uv = (u - r.xy * 0.5) / r.y;

    vec3 p = P(T);
    vec3 ro = p;

    vec3 Z = normalize(P(T + 1.0) - p);
    vec3 up = abs(Z.y) > 0.99 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);
    vec3 X = normalize(cross(up, Z));
    vec3 Y = normalize(cross(Z, X));
    mat3 basis = mat3(X, Y, Z);

    vec2 dir2 = rot(sin(T * 0.3) * 0.5) * (rot(tanh(sin(p.z * 0.2) * 2.0) * 3.0) * uv);
    vec3 D = normalize(basis * vec3(dir2, 1.0));

    o = vec4(0.0);

    // raymarch tunnel
    for (int ii = 0; ii < L && s > 0.001; ++ii)
    {
        p = ro + D * d;
        s = tunnel(p) * 0.5;
        d += s;
    }

    float tunnelDist = d;
    vec3 q = p;

    // reset, origin now starts at p
    ro = p;
    s = 0.002;
    d = 0.0;

    for (int ii = 0; ii < 120 && s > 0.001; ++ii)
    {
        p = ro + D * d;
        s = fractal(p);
        d += s;
        o.rgb += 0.001 - sin(p * 0.6) * 0.05 + 0.03;
    }

    o.rgb = 1.0 - o.rgb;

    float f = mod((0.25 * T + p.z * 0.1), 4.0);
    if (f > 3.0)
        o.rgb = mix(o.rgb, abs(sin(3.5 * T + p * 0.4) / dot(sin(T + q * 32.0), vec3(0.6))), 0.5);
    else if (f > 2.0)
        o.rgb = mix(o.rgb, abs(sin(2.0 * T + p * 0.3) / dot(sin(T + q * 16.0), vec3(0.9))), 0.5);
    else if (f > 1.0)
        o.rgb = mix(o.rgb, abs(sin(1.0 * T + q * 0.2) / dot(sin(q * 8.0), vec3(0.9))), 0.5);
    else
        o.rgb = mix(o.rgb, abs(sin(0.5 * T + p * 0.1) / dot(sin(T + p * 32.0), vec3(1.6))), 0.5);

    // noise texture
    for (float i = 0.4; i < 4.0; i *= 1.4142)
    {
        o.rgb += abs(dot(sin(p * i * 512.0), vec3(0.05))) / i;
    }

    // distance fade
    o.rgb *= exp(-d / 1.5);
    o.rgb *= exp(-tunnelDist / 3.0);

    // vignette & gamma
    float vignette = 1.0 - dot(uv, uv) * 0.8;
    vignette = clamp(vignette, 0.0, 1.0);
    o.rgb *= vignette;
    o.rgb = pow(max(o.rgb, 0.0), vec3(0.45));

    o.a = 1.0;
}

void main() {
    vec2 fragCoord = vUV * iResolution.xy;
    mainImage(FragColor, fragCoord);
    FragColor.a = uAlpha;
}