const shaderWidth = 1920;
const shaderHeight = 1080;

const shaderSource = `
#define PI 3.1415926538
#define EPSILON 0.001
#define CUTOFF 20.
#define NLIGHTS 2

vec3 light = normalize(vec3(-0.5, 0.5, 0.5));
vec3 lights[NLIGHTS * 2];
vec3 lightColor = vec3(1.0,1.0,1.0);
vec3 up = vec3(0.,1.,0.);
vec3 cam;
float amplitude;

// utilities
mat3 getXRotMat(float a) {
    return mat3(
         1.0,  0.0,     0.0,
         0.0,  cos(a), -sin(a),
         0.0,  sin(a),  cos(a)
    );
}

mat3 getYRotMat(float a) {
    return mat3(
         cos(a),  0.0,  sin(a),
         0.0,     1.0,  0.0,
        -sin(a),  0.0,  cos(a)
    );
}

mat3 getZRotMat(float a) {
    return mat3(
         cos(a), -sin(a),  0.0,
         sin(a),  cos(a),  0.0,
         0.0,     0.0,     1.0
    );
}

float smoothUnion( float a, float b, float k )
{
    k *= 4.0;
    float h = max(k-abs(a-b),0.0);
    return min(a, b) - h*h*0.25/k;
}

// objects
float sphere(vec3 pos, float r) {
    return length(pos) - r;
}

float box( vec3 p, vec3 b )
{
  return length(max(abs(p)-b,0.0));
}

float plane(vec3 p) {
    return p.y;
}

float wave(vec3 p, float time, float scale, float mag) {
    return plane(p) + mag * sin(scale * length(p) - iTime * time) + 0.5;
}

float sphereField(vec3 p, float r) {
    p.xz = mod((p.xz), 1.0) - vec2(0.5);
    return sphere(p, r);
}

float boxFlow(vec3 pos) {
    vec3 box1_pos = vec3(0.1,-0.7,0.3);
    vec3 box1_offset = vec3(0.0,mod(iTime*0.5, 4.0)-1.0,0.0);
    vec3 q1 = (pos-box1_pos-box1_offset)*getXRotMat(iTime*0.5)*getYRotMat(cos(iTime)*1.5);
    vec3 box2_pos = vec3(-0.4,-0.7,-0.4);
    vec3 box2_offset = vec3(0.0,mod(iTime*0.5-1.5, 4.0)-1.0,0.0);
    vec3 q2 = (pos-box2_pos-box2_offset)*getXRotMat((iTime-7.0)*0.7)*getYRotMat(cos(iTime-5.0)*1.0);
    vec3 box3_pos = vec3(-0.4,-0.7,0.4);
    vec3 box3_offset = vec3(0.0,mod(iTime*0.5-3.0, 4.0)-1.0,0.0);
    vec3 q3 = (pos-box3_pos-box3_offset)*getXRotMat((iTime-3.0)*1.5)*getYRotMat(cos(iTime-7.5)*2.0);
    return
    min(
        box(q1, vec3(0.15,0.15,0.15)),
        min(
            box(q2, vec3(0.15,0.15,0.15)),
            box(q3, vec3(0.15,0.15,0.15))
        )
    );
}

float scene(vec3 pos) {
    return
    max(
        smoothUnion(
            wave(pos-vec3(0.0,-0.5,0.0), 5.0, 7.0, 0.05),
            boxFlow(pos),
            0.2
        ),
        sphere(pos,100.0)
    );
}

// drawing
vec3 grad(vec3 p) {
    vec2 e = vec2(EPSILON, 0.0);
    return (vec3(scene(p+e.xyy), scene(p+e.yxy), scene(p+e.yyx)) - scene(p)) / e.x;
}

mat3 lookat(vec3 cam, vec3 target) {
    vec3 w = normalize(target-cam);
    vec3 u = normalize(cross(w, up));
    vec3 v = normalize(cross(u, w));
    return mat3(u, v, w);
}

float softShadow(vec3 ro, vec3 rd, float mint, float maxt, float k)
{
    // calculates shadows with penumbra included
    float res = 0.7;
    for (float t = mint; t < maxt;) {
        float h = scene(ro + rd*t);
        if (h < EPSILON)
            return 0.0;
        // record the closest distance to an object on the way
        res = min(res, k*h/t);
        t += h;
    }
    return res;
}

vec3 lightSource(vec3 pos, vec3 light, vec3 color, vec3 origin) {
    vec3 normal = normalize(grad(pos));
    vec3 v = normalize(origin - pos);
    vec3 l = normalize(light - pos);
    vec3 h = normalize(v + l);
    vec3 reflection = reflect(-l, normal);
    float diffuse = max(dot(-l, -normal), 0.0);
    float lightDistance = length(pos-light) * 3.;
    float attenuation = 1./(lightDistance*lightDistance);
    float depth = length(pos-cam);
    float specular = pow(max(dot(v, reflection), 0.), 32.);
    if (depth > CUTOFF) {
        specular = 0.;
    }
    return (diffuse
           * attenuation
           + specular)
           * softShadow(pos, l, 0.1, length(light-pos), 18.0)
           * color;
}

vec3 lighting(vec3 pos, vec3 dir, vec3 origin) {
    vec3 ret = vec3(0.);
    for (int i = 0; i < NLIGHTS; i++) {
        ret += lightSource(pos, lights[i*2], lights[i*2+1], origin);
    }
    return ret;
}

vec3 march(vec3 origin, vec3 dir) {
    float t;
    float dist;
    for (int i = 0; i < 80; i++) {
        dist = scene(origin + dir*t);
        t += dist;
        if (t > 30.0)
            break;
    }
    return origin + dir*t;
}

vec3 draw(vec2 uv) {
    vec3 dir = lookat(cam, vec3(0., -0.3, 0.))*normalize(vec3(uv * 0.5, 1.0));
    vec3 pos = march(cam, dir);
    vec3 normal = normalize(grad(pos));
    vec3 reflectDir = normalize(reflect(dir, normal));
    vec3 reflectPos = march(pos + reflectDir*EPSILON, reflectDir);
    return mix(lighting(pos, dir, cam) + lighting(reflectPos, reflectDir, pos), vec3(0.), length(pos-cam)*.1);
}

vec3 tonemap(vec3 hdrColor) {
    float gamma = 2.2;
    float exposure = 2.;
    return pow((vec3(1.0) - exp(-hdrColor * exposure)), vec3(1.0 / gamma));
}

// main function
void mainImage(out vec4 fragColor, in vec2 fragCoord )
{
    lights[0] = vec3(5.0*sin(iTime/5.0), 2.5, 2.5*cos(iTime/5.0));
    lights[1] = 20.*normalize(vec3(1., 0.5, 0.5));
    lights[2] = getYRotMat(2.0) * lights[0];
    lights[3] = 20.*normalize(vec3(.2, .1, .7));
//    lights[4] = vec3(1., 5.0, 0.);
//    lights[5] = 20.*normalize(vec3(0.3, 0.8, 0.2));

    // camera position
    vec2 uv = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
    cam = getYRotMat(0.2*iTime)*vec3(2.0,0.5,0.);

    // Output to screen
    fragColor = vec4(tonemap(draw(uv)),1.0);
}
`;
