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);
}