#version 330 core

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform int uIterations;
uniform float uRayDepth;
uniform float uZOffset;
uniform float uInversionScale;
uniform float uRotationSpeed;
uniform float uDriftSpeed;
uniform float uFoldTimeSpeed;
uniform float uDistanceBase;
uniform float uDistanceScale;
uniform float uGlowStrength;
uniform float uPhaseStep;
uniform float uExposure;
uniform float uColorMixStart;
uniform float uColorMixEnd;
uniform float uColorMixRadius;
uniform vec3 uBaseColor;
uniform vec3 uPhaseOffset;

out vec4 FragColor;

// helper tanh for vec4
vec4 tanh4(vec4 x) {
    vec4 e = exp(clamp(2.0 * x, vec4(-20.0), vec4(20.0)));
    return (e - vec4(1.0)) / (e + vec4(1.0));
}

void mainImage(out vec4 o, vec2 u) {
    vec3 p = iResolution;
    u = (u + u - p.xy) / p.y;
    vec3 D = normalize(vec3(u, uRayDepth));
    o = vec4(0.0);

    float i = 0.0;
    float d = 0.0;
    float s = 0.0;

    for (int iter = 0; iter < 128; iter++) {
        if (iter >= uIterations)
            break;

        i += 1.0;
        p = D * d;
        p.z -= uZOffset;

        p = p * uInversionScale / dot(p, p);
        float theta = iTime * uRotationSpeed;
        mat2 rot = mat2(cos(theta), -sin(theta), sin(theta), cos(theta));
        p.xz *= rot;
        p += vec3(sin(iTime * uDriftSpeed), 0.0, cos(iTime * uDriftSpeed));

        for (s = 0.01; s < 2.0; s += s) {
            p -= abs(dot(sin(p.y + iTime * uFoldTimeSpeed + p / s), vec3(s + s)));
        }

        p.xy /= 2.0;

        s = uDistanceBase + uDistanceScale * abs(length(p) - 7.0);
        d += s;

        o += (vec4(uBaseColor, 0.0) / s) * d + uGlowStrength * (1.0 + cos(i * uPhaseStep + vec4(uPhaseOffset, 0.0))) / s;
    }

    o = mix(o, o.zyxw, smoothstep(uColorMixStart, uColorMixEnd, length(u) / max(uColorMixRadius, 0.001)));
    float lenu = max(length(u), 1e-6);
    o = tanh4(o * o / max(d, 1e-6) / max(uExposure, 1.0) / lenu);
    // Ensure the fragment has an opaque alpha so compositing (blit) can
    // correctly apply the final uAlpha. Some effects left alpha at 0 which
    // made them invisible when rendered into an FBO and later composited.
    o.a = 1.0;
}

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