

#define TIME mod(iTime, float(20.425))

precision highp float;

const float MIN_DEPTH = float(1);
const float MAX_DEPTH = float(40);
const int MAX_ITER = 600;
const float EPS = float(0.01);
const float SHADOW = float(0);
const float SHADOW_EPS = float(0.01);
const float SHADOW_DEPTH = float(2);
const float SHADOW_SMOOTH = float(15);
const float PI = 3.14159265359;
const float KEYFRAME_0 = float(0);
const float KEYFRAME_1 = float(5);
const float KEYFRAME_2 = float(6);
const float KEYFRAME_3 = float(15);
const float KEYFRAME_4 = float(20);
struct Ray {
    vec3 origin;
    vec3 direction;
};
struct RayResult {
    float depth;
    int object;
    vec3 norm;
};
struct SdfResult {
    float distance;
    vec3 pos;
    vec2 uv;
    int object;
};
struct Camera {
    vec3 position;
    vec3 target;
    vec3 up;
    float zoom;
};
float rand(float n){
    return fract(sin(n) * 43758.5453123);
}
vec2 norm_coord(in vec2 coord, in vec2 res) {
    vec2 uv = coord.xy/res.xy;
    uv = 2.*uv - 1.;
    if (res.x > res.y) {
        uv.x *= res.x/res.y;
    } else {
        uv.y *= res.y/res.x;
    }
    return uv;
}
vec3 ray_direction(in Camera cam, in vec2 coord, in vec2 res) {
    vec2 uv = norm_coord(coord, res);
    vec3 dir = normalize(cam.target - cam.position);
    vec3 right = normalize(cross(dir, cam.up));
    vec3 u = normalize(cross(right, dir));
    vec3 target = cam.position + normalize(dir)/cam.zoom + uv.x*right + uv.y*u;
    return normalize(target - cam.position);
}
Ray get_ray(in Camera cam, in vec2 coord, in vec2 res) {
    return Ray(cam.position, ray_direction(cam, coord, res));
}
vec2 sphere_map(in vec3 pos) {
    // TODO
    return vec2(0.);
}
float box(vec3 p, vec3 b) {
  vec3 d = abs(p) - b;
  return length(max(d,0.0)) + min(max(d.x,max(d.y,d.z)),0.0);
}
SdfResult sdf_union(in SdfResult a, in SdfResult b) {
    if (a.distance < b.distance) {
        return a;
    } else {
        return b;
    }
}
mat3 rotx(in float a) {
    return mat3(
            1., 0., 0.,
            0., cos(a), -sin(a),
            0., sin(a), cos(a)
            );
}
mat3 roty(in float a) {
    return mat3(
            cos(a), 0., -sin(a),
            0., 1., 0.,
            sin(a), 0., cos(a)
            );
}
mat3 rotz(in float a) {
    return mat3(
            cos(a), -sin(a), 0.,
            sin(a), cos(a), 0.,
            0., 0., 1.
            );
}
SdfResult sdf(in vec3 pos) {
    SdfResult res = SdfResult(MAX_DEPTH, vec3(0.), vec2(0.), -1);
    vec3 p = pos;
    
    p.xz = vec2(atan(p.x, p.z), length(p.xz) - 2.7 + 0.5*(exp(-TIME*0.1) + exp((TIME - 20.425)*0.1))*sin(0.5*p.y));
    p.y -= 5.*(TIME - 11.);
    p.xy = mod(p.xy, vec2(0.3, 3.));
    p.y += p.x;
    res = SdfResult(box(p, vec3(float(0.35), float(0.2), float(2))), p, vec2(0., 0.), 0);
    return res;
}
vec3 norm(in vec3 p) {
    return normalize(vec3(
        sdf(vec3(p.x + EPS, p.y, p.z)).distance - sdf(vec3(p.x - EPS, p.y, p.z)).distance,
        sdf(vec3(p.x, p.y + EPS, p.z)).distance - sdf(vec3(p.x, p.y - EPS, p.z)).distance,
        sdf(vec3(p.x, p.y, p.z + EPS)).distance - sdf(vec3(p.x, p.y, p.z - EPS)).distance
    ));
}
RayResult raymarcher(in Ray ray) {
    float depth = MIN_DEPTH;
    for (int i=0; i<MAX_ITER; i++) {
        vec3 pos = ray.origin + depth*ray.direction;
        SdfResult res = sdf(pos);
        if (res.distance < EPS) {
            return RayResult(depth, res.object, norm(pos));
        }
        depth += res.distance;
        if (depth > MAX_DEPTH) {
            return RayResult(MAX_DEPTH, -1, vec3(0.));
        }
    }
    return RayResult(MAX_DEPTH, -1, vec3(0.));
}
float lightmarch(in Ray ray, in float maxd, in float k) {
    float depth = 0.;
    float r = 1.;
    for (int i=0; i<MAX_ITER; i++) {
        vec3 pos = ray.origin + depth*ray.direction;
        SdfResult res = sdf(pos);
        if (res.distance < SHADOW_EPS) {
            return 0.;
        }
        r = min(r, k*res.distance/depth);
        depth += res.distance;
        if (depth >= (maxd - SHADOW_DEPTH)) {
            return r;
        }
    }
    return r;
}
vec4 color(in vec2 coord, in vec2 resolution) {
    Camera cam = Camera(mix(mix(mix(mix(vec3(float(0), float(0), float(40)), vec3(float(0), float(5), float(0)), smoothstep(KEYFRAME_0, KEYFRAME_1, TIME)), vec3(float(0), float(5), float(0)), smoothstep(KEYFRAME_1, KEYFRAME_2, TIME)), vec3(float(0), float(5), float(0)), smoothstep(KEYFRAME_2, KEYFRAME_3, TIME)), vec3(float(0), float(0), float(40)), smoothstep(KEYFRAME_3, KEYFRAME_4, TIME)), vec3(float(0.1), float(0), float(0)), mix(mix(mix(mix(vec3(float(0), float(1), float(0)), vec3(float(1), float(5), float(0)), smoothstep(KEYFRAME_0, KEYFRAME_1, TIME)), vec3(cos(0.75*sin(TIME - 11.)), 0., sin(0.75*sin(TIME - 11.))), smoothstep(KEYFRAME_1, KEYFRAME_2, TIME)), vec3(cos(0.75*sin(TIME - 11.)), 0., sin(0.75*sin(TIME - 11.))), smoothstep(KEYFRAME_2, KEYFRAME_3, TIME)), vec3(float(0), float(1), float(0)), smoothstep(KEYFRAME_3, KEYFRAME_4, TIME)), float(0.4));
    Ray ray = get_ray(cam, coord, resolution);
    RayResult res = raymarcher(ray);
    vec3 pos = ray.origin + res.depth*ray.direction;
    vec3 col;
    if (res.object == 0) {
        vec3 amb = vec3((sin(pos) + 1.)/2.);
        vec3 spec = vec3(0.);
        vec3 diff = vec3(0.);
        vec3 h;
        vec3 l;
        vec3 lpos;
        vec3 ldir;
        float bright = 1.;
        lpos = vec3(float(0), float(5), float(0));
        l = normalize(lpos - pos);
        h = normalize(-ray.direction + l);
        spec += vec3(float(1), float(1), float(1))*pow(dot(res.norm, h), float(1));
        diff += amb*dot(l, res.norm);
        if (SHADOW > 0.) {
            Ray light_ray = Ray(lpos, -l);
            float s = SHADOW*lightmarch(light_ray, length(lpos - pos), SHADOW_SMOOTH) + 1. - SHADOW;
            spec *= s;
            diff *= s;
        }
        col = float(0)*amb + float(1)*diff + float(0)*spec;
    }
    if (res.object == -1) {
        col = vec3(float(0.1), float(0.1), float(0.2));
    }vec2 nc = abs(2.*coord/resolution - 1.); col = mix(vec3(0.), col, smoothstep(1.1, 1.05, max(nc.x, nc.y) + 0.1*length(nc)));
    col = clamp(col, 0., 1.);
    return vec4(col, 1.);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    fragColor = color(fragCoord, iResolution.xy);
}
