#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform sampler2D backbuffer;
uniform vec2 resolution;

#define iGlobalTime time
#define iResolution resolution

const float PI = 3.14159265359;

const float NEAR = 1e-1;
const float FAR = 1e+3;

const float MAX_TIME = 3.0;

vec3 hsv2rgb(vec3 c) {
 vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
 vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
 return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

float random(in vec2 st) {
 return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}

float easeOut(float x) {
 float a = x - 1.0;
 return 1.0 - (a * a);
}

vec4 opU(vec4 d1, vec4 d2) { return (d1.x < d2.x) ? d1 : d2; }
float pow2(float x) { return x * x; }

mat3 rotX(float angle) {
 float c = cos(angle);
 float s = sin(angle);

 return mat3(vec3(1, 0, 0), vec3(0, c, s), vec3(0.0, -s, c));
}

mat3 rotY(float angle) {
 float c = cos(angle);
 float s = sin(angle);

 return mat3(vec3(c, 0, -s), vec3(0, 1, 0), vec3(s, 0, c));
}

mat3 rotZ(float angle) {
 float c = cos(angle);
 float s = sin(angle);

 return mat3(vec3(c, s, 0), vec3(-s, c, 0), vec3(0, 0, 1));
}

float sdBox(vec3 p, vec3 b) {
 vec3 d = abs(p) - b;
 return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}

void boxFuta(inout vec4 result, vec3 pos, float value, float sz, float mID) {
 result = opU(result, vec4(sdBox(rotX(value) * (pos - vec3(.0, sz, -sz)) -
                                     vec3(0.0, 0.0, sz),
                                 vec3(sz, sz * 0.001, sz)),
                           mID, 0.0, 0.0));
}

void box(inout vec4 result, vec3 pos, float sz, float val, float mID) {
 boxFuta(result, pos, val, sz, mID);

 result = opU(result, vec4(sdBox(pos - vec3(.0, -sz + sz * 0.001, .0),
                                 vec3(sz, sz * 0.001, sz)),
                           mID + 3.0, 0.0, 0.0));

 result = opU(result, vec4(sdBox(pos - vec3(.0, .0, sz - sz * 0.001),
                                 vec3(sz, sz, sz * 0.001)),
                           mID, 0.0, 0.0));

 result = opU(result, vec4(sdBox(pos - vec3(.0, .0, -sz + sz * 0.001),
                                 vec3(sz, sz, sz * 0.001)),
                           mID, 0.0, 0.0));

 result = opU(result, vec4(sdBox(pos - vec3(-sz + sz * 0.001, .0, .0),
                                 vec3(sz * 0.001, sz, sz)),
                           mID, 0.0, 0.0));
 result = opU(result, vec4(sdBox(pos - vec3(sz - sz * 0.001, .0, .0),
                                 vec3(sz * 0.001, sz, sz)),
                           mID, 0.0, 0.0));
}

vec4 map(vec3 pos) {
 float t = mod(iGlobalTime, MAX_TIME);
 vec4 dest = vec4(FAR, -1.0, 0.0, 0.0);
 float u = t / MAX_TIME;
 float s = easeOut(u);
 s = smoothstep(0.5, 0.9, u);
 float r = 2.0 * PI * u;
 vec3 pBox =
     rotX(-PI * 0.5) *
     (pos - ((1.0 - u) * vec3(0.0, 0.0, -49.0) + u * vec3(0.0, 0.0, 49.0)));
 box(dest, pBox, 500.0, PI * 0.5, 0.0);
 box(dest, rotZ(r) * rotY(r) * pBox, 5.0, PI * 0.5 * s, 1.0);
 box(dest, rotY(r) * rotX(r) * pBox, 0.05, 0.0, 2.0);

 return dest;
}

vec3 getNormal(in vec3 p) {
 const float e = 1e-4;
 return normalize(
     vec3(map(vec3(p.x + e, p.y, p.z)).x - map(vec3(p.x - e, p.y, p.z)).x,
          map(vec3(p.x, p.y + e, p.z)).x - map(vec3(p.x, p.y - e, p.z)).x,
          map(vec3(p.x, p.y, p.z + e)).x - map(vec3(p.x, p.y, p.z - e)).x));
}

vec4 castRay(in vec3 eye, in vec3 ray) {
 const int max_steps = 128;
 const float eps = 1e-6;

 float depth = NEAR;
 float material = -1.0;

 for (int i = 0; i < max_steps; i++) {
   vec4 result = map(eye + depth * ray);
   if (result.x < eps || result.x > FAR) {
     break;
   }

   depth += result.x;
   material = result.y;
 }

 if (depth > FAR) {
   material = -1.0;
 }

 return vec4(depth, material, 0.0, 0.0);
}

vec4 render(in vec3 eye, in vec3 ray) {
 vec4 result = castRay(eye, ray);
 float depth = result.x;
 float mID = result.y;
 vec3 pos = eye + depth * ray;

 vec3 colBikaBika1 = hsv2rgb(vec3(8.0 * iGlobalTime / MAX_TIME, 0.5, 1.0));
 vec3 colBikaBika2 = hsv2rgb(vec3(4.0 * iGlobalTime / MAX_TIME, 1.0, 1.0));
 vec3 colBikaBika3 = hsv2rgb(vec3(2.0 * iGlobalTime / MAX_TIME, 1.0, 1.0));

 vec3 col1 =
     hsv2rgb(vec3(0.333 * floor(iGlobalTime / MAX_TIME), 1.0, 1.0));
 vec3 col2 =
     hsv2rgb(vec3(0.333 * (1.0 + floor(iGlobalTime / MAX_TIME)), 1.0, 1.0));
 vec3 col3 =
     hsv2rgb(vec3(0.333 * (2.0 + floor(iGlobalTime / MAX_TIME)), 1.0, 1.0));

 col1 = mix(colBikaBika1, col1, mod(iGlobalTime, MAX_TIME) / MAX_TIME);
 col2 = mix(colBikaBika2, col2, mod(iGlobalTime, MAX_TIME) / MAX_TIME);
 col3 = mix(colBikaBika3, col3, mod(iGlobalTime, MAX_TIME) / MAX_TIME);
 vec3 col = vec3(0.0);

 if (mID > -1.0) {
   vec3 nor = getNormal(pos);
   float diff = 0.5 * pow2(dot(nor, vec3(.0, .0, 1.))) + 0.5;

   if (mID == 0.0) {
     col = col1 * diff;
   }

   if (mID == 1.0) {
     col = col2 * diff;
   }

   if (mID == 2.0) {
     col = col3 * diff;
   }

   if (mID == 3.0) {
     col = col1;
   }

   if (mID == 4.0) {
     col = col2;
   }

   if (mID == 5.0) {
     col = col3;
   }
 }

 return vec4(col, depth);
}

mat3 setCamera(in vec3 ro, in vec3 ta, float cr) {
 vec3 cw = normalize(ta - ro);
 vec3 cp = vec3(sin(cr), cos(cr), 0.0);
 vec3 cu = normalize(cross(cw, cp));
 vec3 cv = normalize(cross(cu, cw));
 return mat3(cu, cv, cw);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
 vec2 p = 2.0 * (fragCoord.xy / iResolution.xy) - 1.0;
 p.x *= iResolution.x / iResolution.y;

 vec2 bure = vec2(-0.5);
 bure.x = random(vec2(0.3250, iGlobalTime));
 bure.y = random(vec2(0.0160, iGlobalTime));

 float t = mod(iGlobalTime, MAX_TIME);
 bure = pow2(exp(-t)) * bure * 10.0;
 vec3 ro = vec3(bure, 50.0);

 vec3 ta = vec3(0.0, 0.0, 0.0);
 mat3 ca = setCamera(ro, ta, .0);

 float fov = 60.0;
 float focal = 1.0 / tan(0.5 * fov * PI / 180.0);
 vec3 rd = ca * normalize(vec3(p.xy, focal));

 vec3 prev = texture2D(backbuffer, fragCoord.xy / iResolution.xy).rgb;


 vec3 col = render(ro, rd).rgb;
 col = mix(col, prev, pow2(exp(-t)));
 col = pow(col, vec3(0.4545));
 fragColor = vec4(col, 1.0);
}

void main() { mainImage(gl_FragColor, gl_FragCoord.xy); }