const fragmentShaderSource = `
precision lowp float;

#define TAU 6.2831853
#define MAX_STEPS 1000
#define MAX_DIST 100.0
#define SURF_DIST 1e-4
#define ITERATIONS 15

# define SCENE1_DURATION 4.0
# define SCENE2_DURATION 4.0
# define SCENE3_DURATION 14.0
# define SCENE3_CAMERA_TRANSITION 6.0
# define SCENE4_DURATION 8.0
# define SCENE5_DURATION 6.0
# define SCENE6_DURATION 4.91

uniform vec2 u_resolution;
uniform float u_time;
uniform float u_audio[3];
uniform sampler2D u_canvas_texture;
uniform vec3 u_camera_pos;
uniform vec3 u_camera_dir;
uniform float u_camera_zoom;
uniform vec3 u_light_dir;
uniform vec3 u_fold_a;
uniform vec3 u_fold_b;
uniform vec3 u_translate;

// https://iquilezles.org/articles/distfunctions/

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

float sphere(vec3 p, float s) {
  return length(p) - s;
}

float ellipsoid( vec3 p, vec3 r )
{
  float k0 = length(p/r);
  float k1 = length(p/(r*r));
  return k0*(k0-1.0)/k1;
}

float cappedTorus( vec3 p, vec2 sc, float ra, float rb)
{
  p.x = abs(p.x);
  float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy);
  return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
}

float link( vec3 p, float le, float r1, float r2 )
{
  vec3 q = vec3( p.x, max(abs(p.y)-le,0.0), p.z );
  return length(vec2(length(q.xy)-r1,q.z)) - r2;
}

float capsule( vec3 p, vec3 a, vec3 b, float r )
{
  vec3 pa = p - a, ba = b - a;
  float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
  return length( pa - ba*h ) - r;
}

float pyramid( vec3 p, float h )
{
  float m2 = h*h + 0.25;
    
  p.xz = abs(p.xz);
  p.xz = (p.z>p.x) ? p.zx : p.xz;
  p.xz -= 0.5;

  vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y);
   
  float s = max(-q.x,0.0);
  float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 );
    
  float a = m2*(q.x+s)*(q.x+s) + q.y*q.y;
  float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t);
    
  float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b);
    
  return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y));
}

mat4 rotation3d(vec3 axis, float angle) {
    axis = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float oc = 1.0 - c;
  
    return mat4(
      oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
      oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
      oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
      0.0,                                0.0,                                0.0,                                1.0
    );
}

vec3 rotate(vec3 v, vec3 axis, float angle) {
    return (rotation3d(axis, angle) * vec4(v, 1.0)).xyz;
}

mat4 translation3d(float x, float y, float z) {
    return mat4 (
        1,0,0,0, 
        0,1,0,0, 
        0,0,1,0, 
        x,y,z,1
    );
}

vec3 translate(vec3 v, float x, float y, float z) {
    return (translation3d(x, y, z) * vec4(v, 1.0)).xyz;
}

float opIntersection( float d1, float d2 )
{
    return max(d1,d2);
}

float opUnion( float d1, float d2 )
{
    return min(d1,d2);
}

// http://blog.hvidtfeldts.net/index.php/2011/08/distance-estimated-3d-fractals-iii-folding-space/

vec3 fold(vec3 p, vec3 n) {
  return p - 2.0 * min(0.0, dot(p, n)) * n;
}


// https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
float rand(vec2 co){
  return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}

float random(vec4 x) {
  return fract(rand(x.xy) + rand(x.zw));
}


// color functions

vec3 color_add(vec3 a, vec3 b) {
    return vec3(1.0) - (vec3(1.0) - a) * (vec3(1.0) - b);
}

vec3 color_blend(vec3 a, vec3 b) {
    vec3 a_norm = a / (vec3(1.0) - a);
    vec3 b_norm = b / (vec3(1.0) - b);
    // geometric mean of each component
    vec3 c = pow(a_norm * b_norm, vec3(0.5));
    return c / (vec3(1.0) + c);
}

/*
float DE(vec3 point) {
    float scale = 2.0;

    // vec3 a = normalize(vec3(
    //     sin(mouse_t.x * TAU) * cos(mouse_t.y * TAU),
    //     cos(mouse_t.x * TAU) * cos(mouse_t.y * TAU),
    //     cos(mouse_t.x * TAU) * sin(mouse_t.y * TAU)
    // ));
    // vec3 a = normalize(vec3(sin(mouse_t.x), cos(mouse_t.x), 0.0));
    // vec3 b = normalize(vec3(sin(mouse_t.y), cos(mouse_t.y), 0.0));
    // vec3 a = normalize(vec3(0.0, 1.0, 1.0));// + u_audio[0]));
    // vec3 b = normalize(vec3(1.0, 0.0, 1.0));// + u_audio[0]));
    vec3 a = u_fold_a;
    vec3 b = u_fold_b;
    // vec3 o = vec3(-10.0, 3.0, 0.0);
    vec3 o = u_translate;

    for (int i = 0; i < ITERATIONS; i++) {
        // point = fold(point, a);
        point -= 2.0 * min(0.0, dot(point, a)) * a;

        // point = fold(point, b);
        point -= 2.0 * min(0.0, dot(point, b)) * b;

        point *= scale;
        point = point.zxy;
        point += o;
    }
    return box(point * pow(scale, -float(ITERATIONS)), vec3(0.1, 0.2, 2.0) * exp(pow(u_audio[0], 3.0)));
}
*/


float spaceShipDE(float universe, vec3 point, vec3 location) {

    //point = translate(point, location.x, location.y. location.z);
    float s5spaceship = pyramid(point + location, 2.4);
    universe = opUnion(universe, s5spaceship);
    return universe;
}

float DE(vec3 point) {
    vec3 x_axis = vec3(1.0, 0.0, 0.0);
    vec3 y_axis = vec3(0.0, 1.0, 0.0);
    vec3 z_axis = vec3(0.0, 0.0, 1.0);
    float SCENE2_START = SCENE1_DURATION;
    float SCENE3_START = SCENE2_START + SCENE2_DURATION;
    float SCENE4_START = SCENE3_START + SCENE3_DURATION;
    float SCENE5_START = SCENE4_START + SCENE4_DURATION;
    float SCENE6_START = SCENE5_START + SCENE5_DURATION;
    float SCENE7_START = SCENE6_START + SCENE6_DURATION;

    float universe = sphere(point, 0.0);

    vec3 scene1 = vec3(0.0, 0.0, 0.0);
    //if (1.0 > 0.0) {
    if (u_time > SCENE1_DURATION) {
        scene1 = vec3(-300.0, 0.0, 0.0);
    }
    vec3 s1planet1_location = vec3(0.0, 0.0, 0.0);
    float s1planet1 = sphere(point + s1planet1_location + scene1, 0.5);
    universe = opUnion(universe, s1planet1);

    vec3 scene2 = vec3(0.0, 0.0, 0.0);
    //if (1.0 > 0.0) {
    if (u_time < SCENE2_START || u_time > SCENE3_START) {
    //if (u_time > SCENE2_DURATION) {
        scene2 = vec3(-300.0, 0.0, 0.0);
    }
    vec3 s2planet1_location = vec3(0.9, 0.2, 3.3);
    float s2planet1 = sphere(point + s2planet1_location + scene2, 0.5);
    universe = opUnion(universe, s2planet1);

    vec3 scene3 = vec3(0.0, 0.0, 0.0);
    //if (1.0 > 0.0) {
    if (u_time < SCENE3_START || u_time > SCENE4_START) {
    //if (u_time > SCENE3_DURATION) {
        scene3 = vec3(-300.0, 0.0, 0.0);
    }
    vec3 s3planet1_location = vec3(0.0, 0.0, 0.0);
    vec3 s3planet2_location = vec3(0.0, 0.0, 0.0);
    vec3 s3planet2_orbit = vec3(2.7*sin(u_time), 0.0, -2.7*cos(u_time));
    float s3planet1 = sphere(point + s3planet1_location + scene3, 0.5);
    float s3planet2 = sphere(point + s3planet2_location + s3planet2_orbit + scene3, 0.15);
    vec3 s3planet2_moon1_orbit = vec3(0.3*sin(3.7*u_time), 0.0, -0.3*cos(3.3*u_time));
    vec3 s3planet2_moon2_orbit = vec3(0.7*sin(1.9*u_time), 0.03*cos(14.0*u_time), -0.7*cos(1.4*u_time));
    float s3planet2_moon1 = sphere(point + s3planet2_location + s3planet2_orbit + s3planet2_moon1_orbit + scene3, 0.05);
    float s3planet2_moon2 = sphere(point + s3planet2_location + s3planet2_orbit + s3planet2_moon2_orbit + scene3, 0.04);
    universe = opUnion(universe, s3planet1);
    universe = opUnion(universe, s3planet2);
    universe = opUnion(universe, s3planet2_moon1);
    universe = opUnion(universe, s3planet2_moon2);

    vec3 scene4 = vec3(0.0, 0.0, 0.0);
    //if (1.0 > 0.0) {
    if (u_time < SCENE4_START || u_time > SCENE5_START) {
    //if (u_time > SCENE4_DURATION) {
        scene4 = vec3(-300.0, 0.0, 0.0);
    }
    //vec3 s4planet_location = vec3(5.0, -10.0, -80.0);
    //vec3 s4planet_location = vec3(5.0, -10.0, -70.0); // with shadow
    vec3 s4planet_location = vec3(50.0, -10.0, -80.0);
    float s4planet = sphere(point + s4planet_location + scene4, 50.0);
    //vec3 s4spaceship_location = vec3(0.0, 0.0, -14.0 + 2.2*u_time);
    vec3 s4spaceship_location = vec3(0.0, 0.0, -14.0 + 2.2*(u_time - SCENE4_START));
    float s4spaceship_hull = ellipsoid(point + s4spaceship_location + scene4, vec3(0.4,0.4,1.5));
    vec3 s4spaceship_left_winglet_location = vec3(-1.0, 0.0, 0.0);
    vec3 s4spaceship_right_winglet_location = vec3(1.0, 0.0, 0.0);
    float s4spaceship_left_winglet = ellipsoid(point + s4spaceship_location + s4spaceship_left_winglet_location + scene4, vec3(0.2,0.35,1.0));
    float s4spaceship_right_winglet = ellipsoid(point + s4spaceship_location + s4spaceship_right_winglet_location + scene4, vec3(0.2,0.35,1.0));
    vec3 s4spaceship_bridge_location = vec3(0.0, 0.0, -0.5);
    float s4spaceship_bridge = cappedTorus(point + s4spaceship_location + s4spaceship_bridge_location + scene4, vec2(1.0, 1.0), 0.15, 0.4);
    point = rotate(point, vec3(0.0, 0.0, 1.0), 0.1);
    float s4spaceship_left_wing = capsule(point + s4spaceship_location + scene4, vec3(0.9, 0.0, 0.7), vec3(0.0, 0.0, 0.5), 0.05);
    point = rotate(point, vec3(0.0, 0.0, 1.0), -0.1);
    point = rotate(point, vec3(0.0, 0.0, 1.0), -0.1);
    float s4spaceship_right_wing = capsule(point + s4spaceship_location + scene4, vec3(-0.9, 0.0, 0.7), vec3(0.0, 0.0, 0.5), 0.05);
    point = rotate(point, vec3(0.0, 0.0, 1.0), 0.1);
    vec3 s4spaceship_left_landing_gear_location = vec3(-0.4, 0.3, -0.5);
    point = rotate(point, vec3(0.0, 0.0, 1.0), 0.5);
    float s4spaceship_left_landing_gear = link(point + s4spaceship_location + s4spaceship_left_landing_gear_location + scene4, 0.3, 0.07, 0.01);
    point = rotate(point, vec3(0.0, 0.0, 1.0), -0.5);
    vec3 s4spaceship_right_landing_gear_location = vec3(0.4, 0.3, -0.5);
    point = rotate(point, vec3(0.0, 0.0, 1.0), -0.5);
    float s4spaceship_right_landing_gear = link(point + s4spaceship_location + s4spaceship_right_landing_gear_location + scene4, 0.3, 0.07, 0.01);
    point = rotate(point, vec3(0.0, 0.0, 1.0), 0.5);
    universe = opUnion(universe, s4spaceship_hull);
    universe = opUnion(universe, s4spaceship_left_winglet);
    universe = opUnion(universe, s4spaceship_right_winglet);
    universe = opUnion(universe, s4spaceship_bridge);
    universe = opUnion(universe, s4spaceship_left_wing);
    universe = opUnion(universe, s4spaceship_right_wing);
    universe = opUnion(universe, s4spaceship_left_landing_gear);
    universe = opUnion(universe, s4spaceship_right_landing_gear);
    universe = opUnion(universe, s4planet);

    vec3 scene5 = vec3(0.0, 0.0, 0.0);
    //if (1.0 > 0.0) {
    if (u_time < SCENE5_START || u_time > SCENE6_START) {
    //if (u_time > SCENE5_DURATION) {
        scene5 = vec3(-300.0, 0.0, 0.0);
    }
    vec3 s5spaceship_location = vec3(0.0, -2.2*(u_time - SCENE5_START), 0.0);
    // -14.0 + 2.2*(u_time - SCENE4_START)
    point = translate(point, -5.6, 4.0, -7.0);
    point = rotate(point, y_axis, 0.3*(u_time - SCENE5_START));
    float s5spaceship = pyramid(point + s5spaceship_location + scene5, 2.4);
    point = rotate(point, y_axis, -0.3*(u_time - SCENE5_START));
    point = translate(point, 5.6, -4.0, 7.0);
    point = translate(point, 3.0, 0.0, 0.0);
    universe = opUnion(universe, s5spaceship);
    universe = spaceShipDE(universe, point + scene5, vec3(0.0, 5.0 -2.0*(u_time - SCENE5_START), 0.0));
    universe = spaceShipDE(universe, point + scene5, vec3(-7.0, 9.0 -1.0*(u_time - SCENE5_START), -16.0));
    universe = spaceShipDE(universe, point + scene5, vec3(4.0, 3.0 -1.2*(u_time - SCENE5_START), -12.0));
    point = translate(point, -3.0, 0.0, 0.0);

    vec3 scene6 = vec3(0.0, 0.0, 0.0);
    //if (1.0 > 0.0) {
    if (u_time < SCENE6_START || u_time > SCENE7_START) {
    //if (u_time > SCENE6_DURATION) {
        scene6 = vec3(-300.0, 0.0, 0.0);
    }
    vec3 s6asteroid_location = vec3(2.3, 1.5, 0.0);
    float s6asteroid = pyramid(point + s6asteroid_location + scene6, 1.0);
    vec3 s6torpedo_location = vec3(2.3, 1.0, -20.0 + 4.0*(u_time - SCENE6_START));
    float s6torpedo = ellipsoid(point + s6torpedo_location + scene6, vec3(0.2, 0.2, 0.6));
    universe = opUnion(universe, s6torpedo);
    universe = opUnion(universe, s6asteroid);
    
    return(universe);
}

vec3 normal(vec3 point) {
    // https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm
    vec2 e = vec2(1e-4, 0.0);
    float o = DE(point);
    // float dx = DE(point + e.xyy) - o;
    // float dy = DE(point + e.yxy) - o;
    // float dz = DE(point + e.yyx) - o;
    float dx = DE(point + e.xyy) - DE(point - e.xyy);
    float dy = DE(point + e.yxy) - DE(point - e.xyy);
    float dz = DE(point + e.yyx) - DE(point - e.xyy);
    return normalize(vec3(dx, dy, dz));
}

vec3 shadow_raymarch(vec3 origin, vec3 direction) {
    float travel = 0.0;
    for (int i = 0; i < MAX_STEPS; i++) {
        vec3 pos = origin + travel * direction;
        float distance = DE(pos);
        if (distance < SURF_DIST) {
            return vec3(0.0);
        }
        travel += distance;
        if (travel > MAX_DIST) {
            return log(float(i)) / log(float(MAX_STEPS)) * vec3(1.0);
        }
    }
    return vec3(1.0);
}

vec3 raymarch(vec3 origin, vec3 direction) {
    float travel = 0.0;
    for (int i = 0; i < MAX_STEPS; i++) {
        vec3 pos = origin + travel * direction;
        float distance = DE(pos);
        if (distance < SURF_DIST) {
            float fakeAO = 1.0 - log(float(i)) / log(float(MAX_STEPS));
            vec3 fakeAO_light = vec3(fakeAO);
            vec3 n = normal(pos);
            // vec3 light = normalize(vec3(cos(u_time*0.1), 0.2, sin(u_time*0.1)));
            vec3 light = u_light_dir;

            // float directional = 0.5 + 0.5 * dot(n, light);
            float directional = max(0.0, dot(n, light));
            vec3 directional_light = vec3(10.0, 2.5, 0.5) * directional;

            vec3 ambient = vec3(2.5, 6.0, 10.0) * 0.04;

            vec3 shadow = shadow_raymarch(pos + n * SURF_DIST * 2.0, light);
            directional_light *= shadow;

            return fakeAO_light * (directional_light * 2.0 + ambient);
        }
        travel += distance;
        if (travel > MAX_DIST) {
            break;
        }
    }
    return vec3(0.0);
}

void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;

    // bass pump
    float zoom = exp(pow(max(u_audio[0], 0.0), 3.0) * 0.1);
    p /= zoom;


    //Camera Setup

    float SCENE2_START = SCENE1_DURATION;
    float SCENE3_START = SCENE2_START + SCENE2_DURATION;
    float SCENE4_START = SCENE3_START + SCENE3_DURATION;
    float SCENE5_START = SCENE4_START + SCENE4_DURATION;

    /*
    // scene3 camera alone
    vec3 s3camera_move_up = vec3(0.0, 0.5*(u_time - SCENE3_CAMERA_TRANSITION), 0.0);
    vec3 s3camera_move_back = vec3(0.0, 0.0, -0.5*(u_time - SCENE3_CAMERA_TRANSITION));
    vec3 s3camera_pan_down = vec3(0.0, -0.05*(u_time - SCENE3_CAMERA_TRANSITION), 0.0);
    //if (1.0 > 0.0) {
    if (u_time < SCENE3_CAMERA_TRANSITION) {
        s3camera_move_up = vec3(0.0, 0.0, 0.0);
        s3camera_move_back = vec3(0.0, 0.0, 0.0);
        s3camera_pan_down = vec3(0.0, 0.0, 0.0);
    }
    */

    // scene3 camera in context
    vec3 s3camera_move_up = vec3(0.0, 0.5*(u_time - SCENE3_START - SCENE3_CAMERA_TRANSITION), 0.0);
    vec3 s3camera_move_back = vec3(0.0, 0.0, -0.5*(u_time - SCENE3_START - SCENE3_CAMERA_TRANSITION));
    vec3 s3camera_pan_down = vec3(0.0, -0.05*(u_time - SCENE3_START - SCENE3_CAMERA_TRANSITION), 0.0);
    //if (u_time < SCENE3_START || u_time > SCENE4_START || 1.0 > 0.0) {
    if (u_time < SCENE3_START + SCENE3_CAMERA_TRANSITION || u_time > SCENE4_START) {
    //if (u_time < SCENE3_CAMERA_TRANSITION) {
        s3camera_move_up = vec3(0.0, 0.0, 0.0);
        s3camera_move_back = vec3(0.0, 0.0, 0.0);
        s3camera_pan_down = vec3(0.0, 0.0, 0.0);
    }

    vec3 s3camera_move = s3camera_move_up + s3camera_move_back;
    vec3 s3camera_pan = s3camera_pan_down;
    
    /*
    // scene4 camera alone
    vec3 s4camera_move_up = vec3(0.0, -9.5 + 1.8*(u_time), 0.0);
    vec3 s4camera_move_right = vec3(-5.0, 0.0, 0.0);
    vec3 s4camera_move_back = vec3(0.0, 0.0, -0.5*(u_time));
    //if (u_time < SCENE3_START || u_time > SCENE4_START || 1.0 > 0.0) {
    if (u_time < 0.0) {
        s4camera_move_up = vec3(0.0, 0.0, 0.0);
        s4camera_move_right = vec3(0.0, 0.0, 0.0);
        s4camera_move_back = vec3(0.0, 0.0, 0.0);
    }
    */
    
    // scene4 camera in context
    vec3 s4camera_move_up = vec3(0.0, -9.5 + 1.8*(u_time - SCENE4_START), 0.0);
    vec3 s4camera_move_right = vec3(-5.0, 0.0, 0.0);
    vec3 s4camera_move_back = vec3(0.0, 0.0, -0.5*(u_time - SCENE4_START));
    //if (1.0 > 0.0) {
    if (u_time < SCENE4_START || u_time > SCENE5_START) {
        s4camera_move_up = vec3(0.0, 0.0, 0.0);
        s4camera_move_right = vec3(0.0, 0.0, 0.0);
        s4camera_move_back = vec3(0.0, 0.0, 0.0);
    }


    vec3 s4camera_move = s4camera_move_up + s4camera_move_back + s4camera_move_right;  
    
    vec3 camera_move = s3camera_move + s4camera_move;
    vec3 camera_pan = s3camera_pan;

    vec3 camera_pos = u_camera_pos + camera_move;
    vec3 camera_dir = u_camera_dir + camera_pan;
    vec3 camera_up = vec3(0.0, 1.0, 0.0);
    vec3 camera_right = normalize(cross(camera_dir, camera_up));
    camera_up = normalize(cross(camera_right, camera_dir));



    vec3 ray_dir = normalize(u_camera_zoom * camera_dir + p.x * camera_right + p.y * camera_up);

    vec3 exposure = raymarch(camera_pos, ray_dir);

    exposure *= exp(max(u_audio[2], 0.0) * 0.5);
    exposure += pow(u_audio[2], 2.0) * 0.03;

    // exposure *= vec3(1.0) + 5.0 * texture2D(u_canvas_texture, uv).xyz;
    // exposure += 0.2 * texture2D(u_canvas_texture, uv).xyz;

    // HDR Tone Mapping
    vec3 color = exposure / (vec3(1.0) + exposure);

    gl_FragColor = vec4(color,1.0);
}
`;