#version 330 core

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

out vec4 FragColor;

#define T (iTime*2e1 + 12. + 2e1*sin(iTime*.5))
#define P(z) (vec3(cos((z) * .03) * 16.,cos((z) * 0.02) * 32., (z)))
#define R(a) mat2(cos(a+vec4(0, 33, 11, 0)))
#define N normalize

float apollonian(vec3 p) {
    float s,w,l;
    
    p /= 8.;

    p *= vec3(.6, .5, .25);
    p += cos(p.yzx * 6. + p.xzy * 2. + p.zxy *4.)*.1;
    
    for (s=0.,w=.2; s++ < 8.; p *= l, w *= l )
        p  = abs(sin(p))-1.,
        l = 1.35/dot(p,p);

    return length(p*8.)/w-.0002;
}

float gyroid(vec3 p) {
    return 16.*dot(sin(p/64.), cos(p.yzx/12.));
}

float map(vec3 p) {
    return max(gyroid(p),
           max((cos(p.z*.2)+2.) - length((p - P(p.z)).xy), apollonian(p)));
}

void mainImage(out vec4 o, in vec2 u) {

    // initialize accumulators explicitly to avoid undefined behavior
    float s = 0.0;
    float d = 0.0;
    float i = 0.0;
    float a = 0.0;
    vec3 r = iResolution;
    u = (u - r.xy / 2.0) / r.y;

    vec3 p = P(T * 2.0);
    vec3 Z = N( P(T * 2.0 + 7.0) - p);
    vec3 X = N(vec3(Z.z, 0, -Z));
    vec3 D = N(vec3(R(sin(iTime * 0.3) * .6) * u, 1) * mat3(-X, cross(X, Z), Z));

    o = vec4(0.0);
    for (i = 0.0; i < 128.0; i += 1.0) {
        s = map(p) * 0.7;
        d += s;
        p += D * s;
        o += 2.0 * vec4(8,2,1,0) + 0.04 * vec4(1,2,6,4) / (0.001 + abs(s));
    }

    o = tanh(o  / 1e5 * exp(vec4(3, 2, 1, 0) * d / i));
    // ensure opaque alpha so blending with previous frames doesn't produce ghosting
    o.a = 1.0;
}

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