#extension GL_OES_standard_derivatives : enable

/*
    _    _ _              ____                            _     _       
   / \  | (_) ___ _ __   / ___| _ __   __ _  ___ ___  ___| |__ (_)_ __  
  / _ \ | | |/ _ \ '_ \  \___ \| '_ \ / _` |/ __/ _ \/ __| '_ \| | '_ \ 
 / ___ \| | |  __/ | | |  ___) | |_) | (_| | (_|  __/\__ \ | | | | |_) |
/_/   \_\_|_|\___|_| |_| |____/| .__/ \__,_|\___\___||___/_| |_|_| .__/ 
                               |_|                               |_|    

    Alien Spaceship by @gam0022
    Released at GLSL Graphics Compo, Tokyo Demo Fest 2021
*/

// BEGIN: shadertoy porting template
// https://gam0022.net/blog/2019/03/04/porting-from-shadertoy-to-glslsandbox/
precision highp float;

uniform vec2 resolution;
uniform float time;
uniform vec2 mouse;
uniform sampler2D backbuffer;

#define iResolution resolution
#define iTime time
#define iMouse (vec4(mouse, 0.5, 0.5) * resolution.xyxy)
#define iChannel0 backbuffer
#define texture texture2D

void mainImage(out vec4 fragColor, in vec2 fragCoord);

void main(void) {
    vec4 col;
    mainImage(col, gl_FragCoord.xy);
    gl_FragColor = col;
}
// END: shadertoy porting template

#define BPM 114.0
#define TAU 6.28318530718
#define saturate(x) clamp(x, 0., 1.)
#define opRep(p, a) p = mod(p, a) - a * 0.5
#define opRepLim(p, c, l) p = p - c * clamp(floor(p / c + 0.5), -l, l);

vec3 ro, target;
float fov;
vec3 scol;
float beat;
float beatTau;

// For Debugging
vec4 PU;  // [ 0 1]
vec4 PN;  // [-1 1]

// Timeline
float prevEndTime = 0., t = 0.;
#define TL(beat, end) if (t = beat - prevEndTime, beat < (prevEndTime = end))

// Material Types
#define VOL 0.0
#define SOL 1.0

// https://www.shadertoy.com/view/3tX3R4
float remap(float val, float im, float ix, float om, float ox) { return clamp(om + (val - im) * (ox - om) / (ix - im), om, ox); }
float remap01(float val, float im, float ix) { return saturate((val - im) / (ix - im)); }

void opUnion(inout vec4 m, float d, float type, float roughness_or_emissive, float hue) {
    if (d < m.x) m = vec4(d, type, roughness_or_emissive, hue);
}

vec3 pal(vec4 m) {
    // Integer part: Blend ratio with white (0-10)
    // Decimal part: Hue (0-1)
    vec3 col = vec3(0.5) + 0.5 * cos(TAU * (vec3(0.0, 0.33, 0.67) + m.w));
    return mix(col, vec3(1), 0.1 * floor(m.w));
}

// Ref. Energy Lab by kaneta
// https://www.shadertoy.com/view/3dd3WB
float smoothPulse(float start, float end, float period, float smoothness, float t) {
    float h = abs(end - start) * 0.5;
    t = mod(t, period);
    return smoothstep(start, start + h * smoothness, t) - smoothstep(end - h * smoothness, end, t);
}

// Hash without Sine by David Hoskins.
// https://www.shadertoy.com/view/4djSRW
float hash12(vec2 p) {
    vec3 p3 = fract(vec3(p.xyx) * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

vec2 hash23(vec3 p3) {
    p3 = fract(p3 * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.xx + p3.yz) * p3.zy);
}

// hemisphere hash function based on a hash by Slerpy
vec3 hashHs(vec3 n, vec3 seed) {
    vec2 h = hash23(seed);
    float a = h.x * 2. - 1.;
    float b = TAU * h.y * 2. - 1.;
    float c = sqrt(1. - a * a);
    vec3 r = vec3(c * cos(b), a, c * sin(b));
    return r;
}

// https://www.shadertoy.com/view/lssGWn
float sdEgg(vec3 p, float r) {
    p.y *= 0.8;
    p.y += 0.15 * pow(1.5 * dot(p.xz, p.xz), 0.6);
    return length(p) - r;
}

float sdBox(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);
}

void rot(inout vec2 p, float a) { p *= mat2(cos(a), sin(a), -sin(a), cos(a)); }

vec4 map(vec3 pos) {
    vec4 m = vec4(1, VOL, 0, 0);
    // x: Distance
    // y: MaterialType (VOL or SOL)
    // z: Roughness in (0-1), Emissive when z>1
    // w: ColorPalette

    float roughness = 0.05;
    float a = 1.;
    float W = 8.;
    float H = 4.;
    float D = 8.;

    // Final Room
    if (pos.z >= D * 6.) {
        W *= 2.;
        H *= 2.;
        D *= 2.;
        roughness = 0.3;
    }

    vec3 p1 = pos, p;
    p1.x = abs(p1.x);
    opRep(p1.z, 2. * D);

    float roomID = max(floor(pos.z / (2. * D)), 0.0);

    // egg
    p = p1;
    opUnion(m, sdEgg(p - vec3(0, 1, 0), 1.), SOL, 0.01, 0.0);

    // floor
    float pz = smoothPulse(0.0, 0.6, 1.0, 0.5, 0.25 * p.z);
    float emi = smoothPulse(0.2, 0.25, 1.0, 0.5, pz + p.x / 2.0) * saturate(sin(beatTau / 8. + pos.z)) * step(4., beat);
    float h = a + 0.1 * floor(2. * sin(p.x)) + 0.2 * floor(1. * sin(2. * p.z));
    opUnion(m, sdBox(p - vec3(0, -a, 0), vec3(W, h, D)), SOL, roughness + 2. * emi, 0.4);

    // door innner
    emi = 2. * step(beat, 4. * 16.);
    float py = smoothPulse(0.0, 0.6, 1.0, 0.5, 0.25 * p.y);
    emi += 1.8 * smoothPulse(0.0, 0.25, 1.0, 0.5, py + 0.5 * p.x) * saturate(sin(beatTau / 8. + pos.y / 4. - py));
    opUnion(m, sdBox(p - vec3(0, H - a, D - 2. * a), vec3(W, H, 0.5 * a)), VOL, emi, 2.0 + fract(0.9 + 0.325 * roomID));

    // door outer
    p.x = pos.x;
    p.x -= 0.5 * (smoothPulse(0.0, 0.6, 1.0, 0.5, 0.3 * p.y) - 0.5);

    /*
    float doorOpen = 0.05 * saturate(beat / 4.);
    if (roomID < 2.)
        doorOpen += remap(beat, 4. * 16., 4. * 18., 0.0, 0.9);
    else
        doorOpen += remap(beat, 4. * 32., 4. * 36., 0.0, 0.9);
    */

    float openStart = 16. + step(2., roomID) * 18.;
    float doorOpen = 0.05 * saturate(beat / 4.) + 0.9 * (1. - exp(-3. * remap01(beat, 4. * openStart, 4. * (openStart + 2.))));

    float doorPX = (0.5 + 0.5 * doorOpen) * W;
    opUnion(m, sdBox(p - vec3(doorPX, H - a, D - 2. * a), vec3(0.5 * W, H, a)), SOL, 0.1, -0.5);
    opUnion(m, sdBox(p - vec3(-doorPX, H - a, D - 2. * a), vec3(0.5 * W, H, a)), SOL, 0.1, -0.5);

    // wall light
    p = p1;
    opUnion(m, sdBox(p - vec3(W + a, 0.5 * H + a, 0), vec3(a, H, D)), SOL, 0.1, 0.0);
    opRep(p.z, 2.);
    p.y -= H * 0.5 + a;
    p.x -= W - 0.5 * abs(p.y);

    float freq = 0.25 * step(4. * 4., beat) + 0.75 * step(4. * 8., beat) - 0.75 * step(4. * 16., beat);
    float rand = TAU * step(4. * 16., beat) * hash12(floor(pos.xz / vec2(W, 2.0)));
    emi = 1.8 * saturate(sin(beatTau * freq + 0.5 * step(4. * 4., beat) * pos.z * TAU / D + rand));
    opUnion(m, sdBox(p, vec3(a * 2., H, 0.2)), VOL, roughness + emi, 2.0 + fract(0.4 + 0.325 * roomID));
    opUnion(m, sdBox(p, vec3(a * 1.7, H, 0.24)), SOL, roughness, 0.0);

    // wall detail
    p = p1;
    opRep(p.z, 0.5);
    p.y -= H * 0.5 + a;
    p.x -= W - 0.5 * abs(p.y) - 0.3 * floor(0.8 * abs(p.y));
    opUnion(m, sdBox(p, vec3(a * 0.8, H, 0.06)), SOL, 2. * roughness, 0.0);

    // ceil light
    p = p1;
    p.z -= -2. * a;
    opRepLim(p.z, 4., 1.);
    p.x -= W * 0.21;
    opRepLim(p.x, 0.4, 1.);
    emi = 1.8 * step(12., beat);
    opUnion(m, sdBox(p - vec3(0.0, 2.0 * H - 1.1, 0), vec3(0.05, 0.1, 1.)), SOL, emi, 10.0);

    // ceil detail
    p = p1;
    opRep(p.x, 1.0);
    p.y -= 2. * H + 0.1 * smoothPulse(0.0, 0.6, 1.0, 0.5, 0.5 * p.z);
    p.y -= -0.4 * smoothPulse(0.0, 0.6, 1.0, 0.5, p1.x / W * 2. + 0.4);
    p.x -= 0.1 * smoothPulse(0.0, 0.6, 1.0, 0.5, p.z);
    opUnion(m, sdBox(p, vec3(0.4, 1.0, D)), SOL, roughness, 0.0);

    // IFS
    if (pos.z >= D * 3.) {
        vec4 ifs = vec4(875, 482, 197, 545) / vec2(1200, 675).xyxy;
        // ifs = PU;

        p = pos;
        p -= vec3(0, H, 16. * 3.5);

        for (int i = 0; i < 5; i++) {
            p = abs(p) - ifs.w;
            rot(p.xz, -4. * ifs.x);
            p = abs(p) - ifs.z;
            rot(p.xy, -4. * ifs.y);
        }

        opUnion(m, sdEgg(p, 0.1), SOL, 0.0, 0.0);
        opUnion(m, sdBox(p, vec2(1, 0.01).xyy), SOL, roughness, 0.0);
        opUnion(m, sdBox(p - vec2(0.001, 0).yxy, vec2(1, 0.01).xyy), VOL, 2.4 * saturate(cos(beatTau / 2. + 10. * p.x)), 2.4);
    }

    return m;
}

vec3 normal(vec3 p) {
    vec2 e = vec2(0, .05);
    return normalize(map(p).x - vec3(map(p - e.yxx).x, map(p - e.xyx).x, map(p - e.xxy).x));
}

// https://www.shadertoy.com/view/tltGWj
/*vec3 normal(vec3 p) {
    float delta = 0.1;
    vec4 n = vec4(0.0);
    for (int i = min(iFrame, 0); i < 4; i++) {
        vec4 s = vec4(p, 0.0);
        s[i] += delta;
        n[i] = map(s.xyz).x;
    }
    return normalize(n.xyz - n.w);
}*/

// Ref. EOT - Grid scene by Virgill
// https://www.shadertoy.com/view/Xt3cWS
void madtracer(vec3 ro1, vec3 rd1, float seed) {
    scol = vec3(0);
    float t = 0., t2 = 0.;
    vec4 m1, m2;
    vec3 rd2, ro2, nor2;
    for (int i = 0; i < 160; i++) {
        m1 = map(ro1 + rd1 * t);
        // t += m1.y == VOL ? 0.25 * abs(m1.x) + 0.0008 : 0.25 * m1.x;
        t += 0.25 * mix(abs(m1.x) + 0.0032, m1.x, m1.y);
        ro2 = ro1 + rd1 * t;
        nor2 = normal(ro2);
        rd2 = mix(reflect(rd1, nor2), hashHs(nor2, vec3(seed, i, iTime)), saturate(m1.z));
        m2 = map(ro2 + rd2 * t2);
        // t2 += m2.y == VOL ? 0.25 * abs(m2.x) : 0.25 * m2.x;
        t2 += 0.25 * mix(abs(m2.x), m2.x, m2.y);
        scol += .007 * (pal(m2) * step(1., m2.z) + pal(m1) * step(1., m1.z));

        // force disable unroll for WebGL 1.0
        if (t < -1.) break;
    }
}

void raymarching(vec3 ro1, vec3 rd1) {
    scol = vec3(0);
    float t = 0.;
    vec4 m;
    for (int i = 0; i < 160; i++) {
        vec3 p = ro1 + rd1 * t;
        m = map(p);
        t += m.x;

        if (m.x < 0.01) {
            vec3 light = normalize(vec3(1, 1, -1));
            vec3 albedo = vec3(0.3);
            if (m.z > 1.) albedo = pal(m);
            scol = albedo * (0.5 + 0.5 * saturate(dot(normal(p), light)));
            break;
        }
    }
}

void setCamera(vec4 v, float roY) {
    vec4 u = v / vec2(1200, 675).xyxy;
    vec4 n = u * 2. - 1.;
    ro = vec3(8. * n.z, roY, 16. * u.w);
    target = ro + vec3(2. * n.xy, 1);
}

void setCameraRot(vec4 v, float roY) {
    vec4 u = v / vec2(1200, 675).xyxy;
    vec4 n = u * 2. - 1.;
    ro = vec3(8. * n.z, roY, 16. * u.w);
    vec3 fwd = vec3(0, 0, 1);
    rot(fwd.xz, n.x * TAU / 2.);
    rot(fwd.yz, n.y * TAU / 4.);
    target = ro + fwd;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    beat = iTime * BPM / 60.0;
    // beat += 4. * 36.;
    // beat += 32.;
    beat -= 4. * 4.;
    beatTau = beat * TAU;

    PU = abs(iMouse / iResolution.xyxy);  // [0 1] zwはマイナスっぽいのでabsをつける
    PN = PU * 2. - 1.;                    // [-1 1]

    vec2 uv = fragCoord.xy / iResolution.xy;

    // Camera
    vec2 noise = hash23(vec3(iTime, fragCoord)) - 0.5;  // AA
    vec2 uv2 = (2. * (fragCoord.xy + noise) - iResolution.xy) / iResolution.x;

    float FD = 675. * 3.;  // Final Room Depth
    fov = 120.;

// #define DEBUG_CAMERA
#ifdef DEBUG_CAMERA
    setCamera(abs(iMouse) + 1. * vec4(0, 0, 0, FD), 5.0);
    // setCameraRot(abs(iMouse), 3.);
#else

    // Room1
    TL(beat, 4. * 8.) setCamera(vec4(600, 250. + t * 3., 600, 243. - t * 6.), 3.);
    else TL(beat, 4. * 10.) setCamera(vec4(600, 307, 600, 44. + t * 4.), 3.);
    else TL(beat, 4. * 12.) setCamera(vec4(494, 322, 695, 216), 2.4 + 0.2 * t);
    else TL(beat, 4. * 14.) setCamera(vec4(600, 481. + 10. * t, 600, 59), 3.);
    else TL(beat, 4. * 16.) setCamera(vec4(909, 158. - 10.0 * t, 470. + 10.0 * t, 158), 3.);
    else TL(beat, 4. * 18.) setCamera(vec4(541, 335., 609., 384. + 2.0 * t), 3.);

    // Room2-4
    else TL(beat, 4. * 36.) setCamera(vec4(541, 335., 609., 480. + 20.0 * t), 3.);

    // Final
    else TL(beat, 4. * 40.) setCamera(vec4(572., 335. + 5. * t, 617., 106. + FD + t), 2.);                       // 見上げる
    else TL(beat, 4. * 44.) setCamera(vec4(572., 375. + 0. * t, 617., 106. + FD), mix(8., 2., exp(-0.1 * t)));   // 上昇
    else TL(beat, 4. * 46.) setCamera(vec4(585. + 2. * t, 268., 705., 205. + FD + 3. * t), 10.);                 // 高所の前進
    else TL(beat, 4. * 48.) setCamera(vec4(593., 319., 622., 322. + FD - 3. * t), mix(10., 9., exp(-0.1 * t)));  // 高所の後退
    else TL(beat, 4. * 50.) setCamera(vec4(880., 0., 452., 166. + FD), mix(7., 14., exp(-0.5 * t)));             // 落下
    else TL(beat, 4. * 52.) setCamera(vec4(525. + 3. * t, 256., 704., 70. + FD + t), 5.);                        // 入口から俯瞰（右）
    else TL(beat, 4. * 54.) setCamera(vec4(733. + 3. * t, 241., 447., 133. + FD + t), 5.);                       // 入口から俯瞰（左）
    else TL(beat, 4. * 56.) setCamera(vec4(492. + 2. * t, 319., 750., 181. + FD), 4.);                           // 柱（右）
    else TL(beat, 4. * 60.) setCamera(vec4(593., 319., 622., 322. + FD + t), 3.);                                // 中央から前進
    else TL(beat, 4. * 62.) setCamera(vec4(884., 131., 452., 216. + FD), mix(2., 4., exp(-0.1 * t)));            // 落下
    else TL(beat, 4. * 64.) setCamera(vec4(435., 354., 494., 216. + FD + 2. * t), 1.);                           // 地面を前進（左）
    else TL(beat, 4. * 66.) setCamera(vec4(1039., 294., 463. + 5. * t, 247. + FD), 1.);                          // 地面を右移動
    else TL(beat, 4. * 68.) setCamera(vec4(567., 391., 664., 256. + FD + 2. * t), 1.);                           // 地面の中央を前進
    else TL(beat, 4. * 70.) setCamera(vec4(645., 326., 705., 270. + FD), 1.9);                                   // 卵に注目
    else TL(beat, 4. * 72.) {
        setCamera(vec4(645, 326., 705., FD + 272.), 1.9);
        fov = mix(90., 120., exp(-t));
    }

#endif

    vec3 up = vec3(0, 1, 0);
    vec3 fwd = normalize(target - ro);
    vec3 right = normalize(cross(up, fwd));
    up = normalize(cross(fwd, right));
    vec3 rd = normalize(right * uv2.x + up * uv2.y + fwd / tan(fov * TAU / 720.));

// #define DEBUG_SCENE
#ifdef DEBUG_SCENE
    raymarching(ro, rd);
    fragColor = vec4(scol, 1.);
#else
    madtracer(ro, rd, hash12(uv2));
    vec3 bufa = texture(iChannel0, uv).xyz;
    scol = mix(scol, vec3(0), remap01(beat, 4. * 70., 4. * 72.));
    fragColor = saturate(vec4(0.7 * scol + 0.7 * bufa, 0.));
#endif
}