#version 330 core

in vec2 vUV;
out vec4 FragColor;

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

#define PI 3.1415
#define PHI 1.61803398874989
const int steps = 16;

float smin(float a, float b, float k){
    float h = clamp(0.5+0.5*(b-a)/k,0.0,1.0);
    return mix(b,a,h)-k*h*(1.0-h);
}

float n(vec2 u){
    vec4 d=vec4(.106,5.574,7.728,3.994),q=u.xyxy,p=floor(q);
    ++p.zw; q-=p;
    p=fract(p*d.xyxy);
    d=p+d.wzwz;
    d=p.xxzz*d.ywyw+p.ywyw*d.xxzz;
    p=fract((p.xxzz+d)*(p.ywyw+d));
    p=cos(p*=iTime+d)*q.xxzz+sin(p)*q.ywyw;
    q*=q*(3.-2.*q);
    p=mix(p,p.zwzw,q.x);
    return mix(p.x,p.y,q.y);
}

float fOpUnionRound(float a,float b,float r){
    vec2 u=max(vec2(r-a,r-b),vec2(0));
    return max(r,min(a,b))-length(u);
}

float fOpIntersectionRound(float a,float b,float r){
    vec2 u=max(vec2(r+a,r+b),vec2(0));
    return min(-r,max(a,b))+length(u);
}

float fOpDifferenceRound(float a,float b,float r){
    return fOpIntersectionRound(a,-b,r);
}

float modBlob(inout vec3 p){
    if(p.x<max(p.y,p.z)) p=p.yzx;
    if(p.x<max(p.y,p.z)) p=p.yzx;
    return 0.;
}

float bFunct(vec3 p){
    return max(max(max(
        dot(p,normalize(vec3(1.,1.,1.))),
        dot(p.xz,normalize(vec2(PHI+1.,1.)))),
        dot(p.yx,normalize(vec2(1.,PHI)))),
        dot(p.xz,normalize(vec2(1.,PHI))));
}

float bloby(vec3 p){
    p=abs(p);
    vec3 savedP=p;

    float sz=1.3;
    sz+=modBlob(p);

    float b=bFunct(p);
    float l=length(p);

    float nub=(1.01-b/l)*(PI/.04)-n(savedP.xy*20.);
    float sploops=l-sz-0.09*cos(min(nub,PI));

    return fOpDifferenceRound(sploops,l-1.38,0.15);
}

float virus(vec3 p){
    vec3 savedP=p;
    p=abs(p);

    float sz=1.2;
    sz+=modBlob(p);

    float b=bFunct(p);
    float l=length(p);

    return l-sz-0.3*(3./2.)*
        cos(min(sqrt(1.01-b/l)*(PI/0.15),PI))
        + n(savedP.xy*20.)*0.01
        + n(savedP.zy*17.)*0.03;
}

void pR(inout vec2 p,float a){
    p=cos(a)*p+sin(a)*vec2(p.y,-p.x);
}

vec2 scene(vec3 ray){
    float time=iTime;

    ray-=vec3(0.,0.,2.0);

    vec3 r2=ray;
    vec3 r3=ray;

    pR(r2.yz,time/3.+n(vec2(time/6.))*0.2);
    pR(r3.yz,time/3.);

    vec3 r4=mix(r2,r3,(sin(time)/5.)+1.);

    pR(r4.xz,n(vec2(time/4.)));
    pR(r4.xy,0.2*n(vec2(time)));

    float blob=bloby(r4);
    float vir=virus(r4);

    float d=smin(blob,vir,0.8+0.08*sin(time));

    return vec2(d,length(r4)-0.5);
}

vec3 estimateNormal(vec3 p){
    float e=0.002;
    return normalize(vec3(
        scene(p+vec3(e,0,0)).x - scene(p-vec3(e,0,0)).x,
        scene(p+vec3(0,e,0)).x - scene(p-vec3(0,e,0)).x,
        scene(p+vec3(0,0,e)).x - scene(p-vec3(0,0,e)).x
    ));
}

float lighting(vec3 pos,vec3 normal){
    vec3 lightPos=vec3(12,12,1);
    vec3 L=normalize(lightPos-pos);

    float diff=max(0.,dot(L,normal));
    float spec=pow(max(dot(reflect(-L,normal),L),0.),5.)*0.3;

    return 0.05+diff+spec;
}

vec4 trace(vec3 ro,vec3 rd){
    vec3 p=ro;
    float t=0.;

    for(int i=0;i<steps;i++){
        vec2 h=scene(p);
        float d=h.x;
        float red=h.y;

        if(d<0.04){
            vec3 nrm=estimateNormal(p);
            float light=lighting(p,nrm);

            vec3 dark=vec3(0.02,0.08,0.02);
            vec3 bright=vec3(0.3,1.0,0.4);

            float glow=pow(max(red,0.0),3.0);

            vec3 col=mix(dark,bright,light);
            col+=bright*clamp(glow,0.0,1.0)*1.2;

            col=clamp(col,0.0,1.0);

            return vec4(col,1.0);
        }

        t+=d;
        p+=d*rd;

        if(t>3.) break;
    }

    // Background: opaque black
    return vec4(0.0,0.0,0.0,1.0);
}

vec3 lookAt(vec2 uv,vec3 ro,vec3 target){
    vec3 z=normalize(target-ro);
    vec3 x=normalize(cross(vec3(0,1,0),z));
    vec3 y=cross(z,x);

    return normalize(uv.x*x+uv.y*y+z*2.0);
}

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

    vec3 ro=vec3(uv.x,uv.y,0.);
    vec3 cam=vec3(0,0,-1);

    vec3 target=cam+vec3(sin(iTime/10.),cos(iTime/10.),2.);

    vec3 rd=lookAt(uv,cam,target);

    fragColor=trace(ro,rd);
}

void main()
{
    vec2 fragCoord = vUV * iResolution.xy;
    vec4 outCol;
    mainImage(outCol, fragCoord);
    outCol.a *= uAlpha;
    // Output non-premultiplied color (opaque background)
    FragColor = outCol;
}
