precision highp float;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
uniform sampler2D backbuffer;
out vec4 outColor;

#define DEFAULT 0.0
#define BLOOM 1.0

#define MAT_BOX 1.0
#define MAT_BOX_FRAME 2.0
#define MAT_BOX_TORUS 3.0

float pi = acos(-1.0);

struct RayInfo{
    vec3 camPos;
    vec3 rayDir;
    vec3 color;
    bool isHit;
    vec3 reflectionAttenuation;
};

mat2 rotate(float a){
    float c = cos(a);
    float s = sin(a);
    return mat2(c, -s, s, c);
}

float repeat(float p, float repCoef){
    return (fract(p/repCoef - 0.5) - 0.5) * repCoef;
}

float easeInOutExpo(float t)
{
    if (t == 0.0 || t == 1.0) {
        return t;
    }
    if ((t *= 2.0) < 1.0) {
        return 0.5 * pow(2.0, 10.0 * (t - 1.0));
    } else {
        return 0.5 * (-pow(2.0, -10.0 * (t - 1.0)) + 2.0);
    }
}

float linearStep(float start, float end, float t)
{
    return clamp((t - start) / (end - start), 0.0, 1.0);
}

vec3 hsv2rgb(float h, float s, float v){
    vec3 rgb = clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
    rgb = rgb * rgb * (3.0 - 2.0 * rgb);
    return v * mix(vec3(1.0), rgb, s);
}

float random1d2d(vec2 p){
    return fract(sin(dot(p.xy, vec2(12.575, 78.2356)))*43578.2356);
}

vec2 polarMod(vec2 p, float r){
    float a = atan(p.y, p.x) + pi/r;
    float n = 2.0 * pi / r;
    a = floor(a/n)*n;
    return p * rotate(-a);
}

float sdBoxFrame(vec3 p, vec3 b, vec3 e)
{
    vec3 q1 = abs(p) - b;
    vec3 q2 = abs(q1+e) - e;
    return min(min(
       length(max(vec3(q1.x, q2.y, q2.z), 0.0)) + min(max(q1.x, max(q2.y, q2.z)), 0.0),
       length(max(vec3(q2.x, q1.y, q2.z), 0.0)) + min(max(q2.x, max(q1.y, q2.z)), 0.0)),
       length(max(vec3(q2.x, q2.y, q1.z), 0.0)) + min(max(q2.x, max(q2.y, q1.z)), 0.0));
}

float sdBox(vec3 p, vec3 s){
    vec3 q = abs(p) - s;
    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}

vec3 optionMin(vec3 a, vec3 b)
{
    return (a.x < b.x) ? a : b;
}

vec3 sdBoxWithFrame(vec3 p, vec3 s, float w){
    vec3 d = vec3(10e8, 0.0, DEFAULT);

    d = optionMin(d, vec3(sdBox(p, s - vec3(w)), MAT_BOX, DEFAULT));
    d = optionMin(d, vec3(sdBoxFrame(p, s, vec3(w)), MAT_BOX_FRAME, BLOOM));
    return d;
}

float sdBox2d(vec2 p, vec2 s){
    p = abs(p) - s;
    return length(max(p, 0.0))+min(max(p.x, p.y), 0.0);
}

float sdTorusKnots(vec3 p, float inRadius, float outRadius, float divide){
    vec2 cp = vec2(length(p.xz) - outRadius, p.y);
    float a = atan(p.x, p.z);
    cp *= rotate(a*3.0);
    cp.y = abs(cp.y)-0.01;
    
    float d = sdBox2d(cp, vec2(inRadius, inRadius*2.0)*sin(divide*a+time*divide));
    return d;
}

vec3 sdTorusWithFrame(vec3 p){
    vec3 d = vec3(10e8, 0.0, DEFAULT);

    d = optionMin(d, vec3(sdTorusKnots(p, 0.001, 0.021, 3.0), MAT_BOX_TORUS, DEFAULT));
    d = optionMin(d, vec3(sdTorusKnots(p, 0.0012, 0.02, 3.0), MAT_BOX_FRAME, BLOOM));
    return d;
}


vec3 distanceFunction(vec3 p){
    vec3 d = vec3(10e8, 0.0, DEFAULT);

    float it = time*2.0;
    float fTime = mod(it, 16.0);
    float t1 = linearStep(2.0, 2.5, fTime);
    float t2 = linearStep(5.0, 5.5, fTime);
    float t3 = linearStep(8.0, 8.5, fTime);
    float t4 = linearStep(11.0, 11.5, fTime);
    float t5 = linearStep(14.0, 14.5, fTime);
    vec3 p1 = p;
    p1.z -= time*0.3;
    float rotateCoef = 0.0;
    rotateCoef = mix(0.0, 0.8, easeInOutExpo(t1));
    rotateCoef = mix(rotateCoef, 0.4, easeInOutExpo(t2));
    rotateCoef = mix(rotateCoef, 0.9, easeInOutExpo(t3));
    rotateCoef = mix(rotateCoef, 1.4, easeInOutExpo(t4));
    rotateCoef = mix(rotateCoef, 0.0, easeInOutExpo(t5));
    p1.xy *= rotate(rotateCoef*p1.z);
    p1.z = repeat(p1.z, 0.5);
    float offsetParamSub = 0.0;
    offsetParamSub = mix(0.42, 0.34, easeInOutExpo(t1));
    offsetParamSub = mix(offsetParamSub, 0.30, easeInOutExpo(t2));
    offsetParamSub = mix(offsetParamSub, 0.43, easeInOutExpo(t3));
    offsetParamSub = mix(offsetParamSub, 0.35, easeInOutExpo(t4));
    offsetParamSub = mix(offsetParamSub, 0.42, easeInOutExpo(t5));
    for(int i = 0; i < 4; i++){
        p1.xy = polarMod(p1.xy, 4.0);
        p1 = abs(p1) - offsetParamSub;
        p1.xz *= rotate(0.38);
        p1.yz *= rotate(0.26);
    }

    d = optionMin(d, sdBoxWithFrame(p1, vec3(0.03, 0.25, 0.25), 0.005));

    vec3 p2 = p;
    p2.z -= 4.93;
    p2.xy *= rotate(pi/2.0);
    p2.yz *= rotate(time);
    d = optionMin(d, sdTorusWithFrame(p2));
    
    vec3 p3 = p;
    p3.z -= 4.93;
    p3.xy *= rotate(time*0.2);
    p3.yz *= rotate(time);
     d = optionMin(d, sdTorusWithFrame(p3));

    return d;
}

vec3 getNormal(vec3 p){
    vec2 err = vec2(0.001, 0.0);
    return normalize(vec3(
        distanceFunction(p + err.xyy).x - distanceFunction(p - err.xyy).x,
        distanceFunction(p + err.yxy).x - distanceFunction(p - err.yxy).x,
        distanceFunction(p + err.yyx).x - distanceFunction(p - err.yyx).x
    ));
}

float getAO(vec3 p, vec3 n){
    float occ = 0.0;
    float sca = 1.0;

    for(int i = 0; i < 5; i++){
        float h = 0.01 + 0.12 * float(i) / 4.0;
        float d = distanceFunction(p + h * n).x;
        occ += (h - d) * sca;
        if(occ > 0.35){
            break;
        }
    }

    return clamp(1.0 - 3.0 * occ, 0.0, 1.0) * (0.5 + 0.5 * n.y);
}

float getSoftShadow(vec3 camPos, vec3 rayDir, float tMin, float tMax){
    float tp = (0.8 - camPos.y) / rayDir.y;
    if(tp > 0.0){
        tMax = min(tMax, tp);
    }

    float res = 1.0;
    float t = tMin;
    for(int i = 0; i < 24; i++){
        float h = distanceFunction(camPos + rayDir * t).x;
        float s = clamp(8.0 * h / t, 0.0, 1.0);
        res = min(res, s * s * (3.0 - 2.0 * s));
        t += clamp(h, 0.02, 0.2);
        if(res < 0.004 || tMax < t){
            break;
        }
    }

    return clamp(res, 0.0, 1.0);
}

float fresnelSchlick(float f0, float c){
    return f0 + (1.0 - f0) * pow((1.0 - c), 5.0);
}

vec3 acesFilm(vec3 col){
    float a = 2.51;
    float b = 0.03;
    float c = 2.43;
    float d = 0.59;
    float e = 0.14;
    return clamp((col * (a * col + b)) / (col * (c * col + d) + e), 0.0, 1.0);
}

vec3 getBloomAlbedo(vec3 p, float materialId)
{
    if(materialId == MAT_BOX_FRAME){
        return hsv2rgb(sin(p.z*6.2)+time*0.5, 0.7, 0.7);
    }

    return vec3(0.0);
}

RayInfo rayMarch(vec3 camPos, vec3 rayDir, vec3 reflectionAttenuation, float rand){
    RayInfo info;
    info.camPos = camPos;
    info.rayDir = rayDir;
    info.color = vec3(0.0);
    info.isHit = false;
    info.reflectionAttenuation = reflectionAttenuation;

    vec3 p;
    float d = 0.0;
    vec3 df = vec3(0.0);
    for(int i = 0; i < 160; i++){
        p = camPos + rayDir * d;
        df = distanceFunction(p);
        float dist = df.x;
        float gProperty = df.z;
        if(gProperty == DEFAULT){
            if(dist <= 0.001){
                info.isHit = true;
                break;
            }
            d += dist * 0.25;
        }else{
            info.color += 0.001/abs(dist) * getBloomAlbedo(p, df.y);
            d += abs(dist) * 0.25;
        }
    }

    if(info.isHit){
        vec3 normal = getNormal(p);
        float metalic = 0.0;
        vec3 albedo = vec3(0.0);

        vec3 ld = normalize(-p);
        vec3 ref = reflect(rayDir, normal);
        float f0 = 1.0;

        if(df.y == MAT_BOX){
            albedo = vec3(0.4588, 0.3843, 0.3843);
            metalic = 1.0;
        }
        if(df.y == MAT_BOX_TORUS){
            albedo = hsv2rgb(sin(atan(p.y, p.x)*0.2+time*0.2), 0.8, 0.7);
            metalic = 1.0;
        }


        float diffuse = clamp(dot(normal, ld), 0.0, 1.0);
        float specular = pow(clamp(dot(reflect(ld, normal), rayDir) ,0.0, 1.0), 10.0);
        float ao = getAO(p, normal);
        float shadow = getSoftShadow(p, ld, 0.25, 3.0);

        info.color += albedo * diffuse * shadow * (1.0 - metalic);
        info.color += albedo * specular * shadow * metalic;
        info.color += albedo * ao * mix(vec3(0.0), vec3(1.0), 0.7);
        info.reflectionAttenuation *= albedo * fresnelSchlick(f0, dot(ref, normal));

        info.camPos = p + 0.01 * normal;
        info.rayDir = ref;
    }

    info.color *= smoothstep(4.0, 0.0, d);

    return info;
}

vec3 getCutInUv(vec2 uv){
  float timer = time*2.0;
  int index = int(floor(mod(timer, 30.0)));
  float expCoef = -20.0;
  if(index == 0){
    float iUvY = floor((uv.y * 0.5 + 0.5) * 8.0);
    uv.x += (step(mod(iUvY, 2.0), 0.0) - 0.5) * exp(expCoef * fract(timer)) * 5.0;
  }else if(index == 14){
    float iUvX = floor((uv.x * 0.5 + 0.5) * 10.0);
    uv.y += (step(1.0 - mod(iUvX, 2.0), 0.0) - 0.5) * exp(expCoef * fract(timer)) * 5.0;
  }
  
  float reflectFlag = index >= 14 ? 1.0 : 0.0;
  
  return vec3(uv, reflectFlag);
}

vec3 renderingFunc(vec2 uv){
    vec3 uvElement = getCutInUv(uv);
    uv = uvElement.xy;
    float flag = uvElement.z;
    vec3 color = vec3(0.0);
    vec3 camPos = vec3(0.0, 0.0, 5.0);
    vec3 lookPos = vec3(0.0, 0.0, 0.0);
    vec3 forward = normalize(lookPos - camPos);
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 right = normalize(cross(forward, up));
    up = normalize(cross(right, forward));
    float fov = 1.0;
    vec3 rayDir = normalize(uv.x * right + uv.y * up + fov * forward);

    float rand = random1d2d(uv);

    vec3 ra = vec3(1.0);
    float d = 0.0;
    if(flag == 0.0){
      for(int i = 0; i < 1; i++){
        RayInfo info = rayMarch(camPos, rayDir, ra, rand);
        color += info.reflectionAttenuation * info.color * ra;
        if(!info.isHit){
            break;
        }
        ra = info.reflectionAttenuation;
        camPos = info.camPos;
        rayDir = info.rayDir;
      }
    }
    else{
      for(int i = 0; i < 3; i++){
        RayInfo info = rayMarch(camPos, rayDir, ra, rand);
        color += info.reflectionAttenuation * info.color * ra;
        if(!info.isHit){
            break;
        }
        ra = info.reflectionAttenuation;
        camPos = info.camPos;
        rayDir = info.rayDir;
      }
    }

    color = acesFilm(color*0.8);
    color = pow(color, vec3(0.4545));

    return color;
}

void main(){
    vec2 uv = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
    vec2 texUv = vec2(gl_FragCoord.xy/resolution);
    vec3 color = vec3(0.0);

    color += renderingFunc(uv);

    outColor = vec4(color, 1.0);
}