
precision lowp float;

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

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 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 DE(vec3 point) {
    /*
    float armada = sphere(point, 1.0);
    vec3 spaceship_location = vec3(-2.0, -1.0, 0.0);
    //vec3 spaceship_location = vec3(-2.0, -1.0 + u_time, 0.0);
    vec3 spaceship2_location = vec3(2.0, 1.0, 0.0);
    float spaceship_radius = 1.2;
    float spaceship = sphere(point + spaceship_location, spaceship_radius);
    for (int i = 0; i < 5; i++) {
        vec3 spaceship_location = vec3(-2.0 + 1.0*i, -1.0 + u_time, 0.0);
        float new_spaceship = sphere(point + spaceship_location, spaceship_radius);
        float armada = opUnion(armada, new_spaceship);
    }
    */

    // scene 1 - spaceships
    float armada = sphere(point, 0.0);
    vec3 spaceship_location = vec3(-2.0, -1.0, 0.0);
    float spaceship_radius = 0.4;

    float new_spaceship;
    vec3 displacement;
    for (int i = 0; i < 5; i++) {
        displacement = vec3(i, 0.0, 0.0);
        new_spaceship = sphere(point + spaceship_location + displacement, spaceship_radius);
        armada = opUnion(armada, new_spaceship);
    }

    // scene 2 - planets, moons, ringed planet
    float scene = sphere(point, 0.0);
    float planet1 = sphere(point, 1.2);
    float planet2 = sphere(point, 0.7);
    float planet3 = sphere(point, 0.4);
    vec3 planet1_location = vec3(-2.0, -1.0, 0.0);
    vec3 planet2_location = vec3(1.0, 1.0, 0.0);
    planet1 = sphere(point + planet1_location, 1.2);
    planet2 = sphere(point + planet2_location, 0.7);
    scene = opUnion(scene, planet1);
    scene = opUnion(scene, planet2);

    return(scene);

    // scene 3 - asteroids

    // scene 4 - destroy the asteroid
    
    /*
    vec3 spaceship_location = vec3(-2.0, -1.0, 0.0);
    //vec3 spaceship_location = vec3(-2.0, -1.0 + u_time, 0.0);
    vec3 spaceship2_location = vec3(2.0, 1.0, 0.0);
    float spaceship_radius = 1.2;
    float spaceship = sphere(point + spaceship_location, spaceship_radius);
    float spaceship2 = sphere(point + spaceship2_location, spaceship_radius);
    float spaceships = opUnion(10000.0, spaceship2);
    */

    return(armada);
}

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
    vec3 camera_pos = u_camera_pos;
    vec3 camera_dir = u_camera_dir;
    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);
}