#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

#define kPI05    1.5707963267949
#define kPI      3.1415926535898
#define kPI2     6.2831853071796

//
vec3 rotZ(vec3 p, float a) {
    vec2 sc = sin(vec2(a, a + kPI05));
    return vec3(
        p.x * sc.y + p.y * -sc.x,
        p.x * sc.x + p.y * sc.y,
        p.z
    );
}

vec3 lookAt(vec3 o, vec3 t, vec3 u, vec3 d) {
    vec3 f = normalize(o - t);
    vec3 s = normalize(cross(u, f));
    vec3 h = normalize(cross(f, s));
    return vec3(
        dot(vec3(s.x, h.x, f.x), d),
        dot(vec3(s.y, h.y, f.y), d),
        dot(vec3(s.z, h.z, f.z), d));
}

// Hash without Sine https://www.shadertoy.com/view/4djSRW
float hash11(float p) {
    p = fract(p * .1031);
    p *= p + 33.33;
    p *= p + p;
    return fract(p);
}
float hash13(vec3 p3) {
    p3  = fract(p3 * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}
vec2 hash22(vec2 p) {
    vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx+33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}
vec3 hash33(vec3 p3) {
    p3 = fract(p3 * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yxz+33.33);
    return fract((p3.xxy + p3.yxx)*p3.zyx);
}

//
vec3 voro3(vec3 p, vec3 r) {
    vec3 ret;
    vec3 ip = floor(p);
    const int N = 1;
    float lmax = 10.0;
    vec3 minip;
    vec3 mintp;
    
    for(int iz = -N; iz <= N; iz++) {
        for(int iy = -N; iy <= N; iy++) {
            for(int ix = -N; ix <= N; ix++) {
                vec3 tp = ip + vec3(ivec3(ix, iy, iz));
                tp += (hash33(tp) - vec3(0.5)) * r;
                float l = length(tp - p);
                if(l < lmax) {
                    lmax = l;
                    minip = ip + vec3(ivec3(ix, iy, iz));
                    mintp = tp;
                }
            }
        }
    }
    ret.x = lmax;
    ret.z = hash13(minip);
    
    lmax = 10.0;
    vec3 minvp;
    for(int iz = -N; iz <= N; iz++) {
        for(int iy = -N; iy <= N; iy++) {
            for(int ix = -N; ix <= N; ix++) {
                vec3 tp = minip + vec3(ivec3(ix, iy, iz));
                tp += (hash33(tp) - vec3(0.5)) * r;
                vec3 vp = tp - mintp;
                float l = length(vp);
                if(l > 0.0) {
                    vec3 mp = (tp + mintp) * 0.5;
                    l = abs(dot(p - mp, vp / l));
                    if(l < lmax) {
                        lmax = l;
                        minvp = vp;
                    }
                }
            }
        }
    }
    ret.y = lmax;
    return ret;
}

float star(vec2 p) {
    float a = atan(p.y, p.x) / kPI2 + 0.5;
    a = abs(fract(a * 5.0) * 2.0 - 1.0) * kPI2 / 10.0;
    float r = length(p);
    vec2 q = cos(vec2(a, a - kPI05)) * r;
    float s = kPI / 180.0 * 55.0;
    vec2 n = cos(vec2(s, s - kPI05));
    return 0.5 - dot(q, n);
}

float flower(vec2 p, float w) {
    vec2 tt = vec2(1e2);
    for(float i = 0.0; i < 5.0; i+=1.0) {
        float a = kPI2 * i / 5.0;
        vec2 o = cos(vec2(a, a - kPI05));
        float to = length(p - o);
        tt = min(tt, vec2(to, abs(to - 1.1756)));
    }
    return w - (length(p) > 1.0 ? tt.x : tt.y);
}

//
float smin(float a, float b, float k) {
    k *= 4.0;
    float h = max(k - abs(a - b), 0.0) / k;
    return min(a, b) - h * h * k * 0.25;
}

float sphere(vec3 p, vec3 o, float r) {
    return length(p - o) - r;
}

vec2 tsphere(vec3 ro, vec3 rd, vec3 sc, float sr, out vec3 ret0, out vec3 ret1) {
    vec3 oc = ro - sc;
    float a = dot(rd, rd);
    float b = 2.0 * dot(rd, oc);
    float c = dot(oc, oc) - sr * sr;
    float d = b * b - 4.0 * a * c;
    if(d < 0.0) return vec2(-1.0);
    vec2 t = (vec2(-b) + vec2(-1.0, 1.0) * sqrt(d)) * 0.5 / a;
    ret0 = rd * t.x + ro;
    ret1 = rd * t.y + ro;
    return t;
}

float tplane(vec3 o, vec3 d, vec3 n, vec3 p, out vec3 ret) {
    vec3 po = p - o;
    float nd = dot(n, d);
    float t = dot(n, po) / nd;
    if(t < 0.0 || abs(nd) <= 1e-8) return -1.0;
    ret = d * t + o;
    return t;
}

// 
vec3 bg(vec3 p) {
    vec3 d = normalize(p);
    vec3 c = vec3(0.0, 0.0, 0.02);

    vec3 vr = voro3(d * 120.0, vec3(1.0));
    float f = 10.0 + abs(sin((vr.z * 24.0 + time) * 2.0)) * 20.0;
    vec3 s = mix(vec3(1.0, 0.6, 0.4), vec3(0.4, 0.9, 1.0), fract(vr.z * vr.z * 1e3));
    c += pow(vec3(1.0 - sqrt(vr.x)), vec3(10.0)) * f * s;

    vec4 hc = mix(vec4(0.1, 0.15, 0.05, 25.0),
                    vec4(0.0, 0.2, 0.8, 50.0),
                    smoothstep(-0.03, 0.03, d.y));
    c += pow(1.0-abs(d.y), hc.w) * hc.rgb;
    c += exp(d.y * -d.y * 3e4) * pow(-d.z, 12.0) * vec3(1.0);

    return c;
}

// scene
const float kDz = 4.0;
const float kIntro = 8.0;
const float kSeq = 16.0;

vec2 i2p(vec2 p, float i, inout float y) {
    if(i > kIntro) {
        float iseq = mod(i - kIntro, kSeq);
        if(iseq == 0.0) {
            p = vec2(0.0, -1.0);
            y *= 2.0;
        } else if(iseq > kSeq - 4.0) {
            p = vec2(p.x * 0.1, 2.0 - (kSeq - iseq));
        }
    }
    return p;
}

vec2 pos2(float t) {
    float i = floor(t);
    float f = fract(t);

    vec2 xx = vec2(-1.0, 1.0) * (fract(i * 0.5) * 4.0 - 1.0);
    float y = 4.0 * f * (1.0 - f);

    vec2 o0 = hash22(vec2(i) + vec2(0.0, 0.3456789));
    vec2 o1 = hash22(vec2(i + 1.0) + vec2(0.0, 0.3456789));

    o0 = o0 * 2.0 - vec2(1.0);
    o1 = o1 * 2.0 - vec2(1.0);

    vec2 p0 = vec2(xx.x, 0.0) + o0;
    vec2 p1 = vec2(xx.y, 0.0) + o1;

    p0 = i2p(p0, i, y);
    p1 = i2p(p1, i + 1.0, y);

    return mix(p0, p1, f) + vec2(0.0, y);
}

float map(vec3 p, vec3 tbif) {
    float tb = tbif.x;
    float ti = tbif.y;
    float tf = tbif.z;

    const float N = 16.0;
    const float r0 = 0.01;
    float d = sphere(p, vec3(pos2(tb), 0.0), r0);
    float dt = 0.0;
    for(float i = 1.0; i < N; i+=1.0) {
        float f = sqrt(i / (N - 1.0));
        f = 4.0 * f * (1.0 - f);
        dt += mix(0.02, 0.05, f);
        vec2 sp = pos2(tb - dt);
        float r = 0.1 * f + r0;
        float s = mix(0.03, 0.05, f);
        d = smin(d, sphere(p, vec3(sp, dt * kDz), r), s);
    }
    return d;
}

vec3 normal(vec3 p, vec3 t) {
    int id;
    vec2 e = vec2(1.0, -1.0) * 0.5773;
    const float eps = 0.00025;
    return normalize( e.xyy * map(p + e.xyy * eps, t) + 
					  e.yyx * map(p + e.yyx * eps, t) + 
					  e.yxy * map(p + e.yxy * eps, t) + 
					  e.xxx * map(p + e.xxx * eps, t) );
}

vec3 fxs(vec3 ro, vec3 rd, float maxt, vec3 tbif) {
    float tb = tbif.x;
    float ti = tbif.y;
    float tf = tbif.z;

    vec3 ret = vec3(0.0, 0.0, 0.0);

    for(float i = 0.0; i < 4.0; i+=1.0) {
        float pti = ti - i;
        if(pti < 0.0) break;

        float ft = (tb - pti);
        vec3 fp = vec3(pos2(pti), ft * kDz);
        if(pti < kIntro) maxt = 100.0;

        {
            vec3 hp;
            float ht = tplane(ro, rd, vec3(0.0, 1.0, 0.0), fp, hp);
            if(ht >= 0.0 && ht < maxt) {
                float r = ft * 1.2;
                float t = length(fp - hp);
                float f = smoothstep(0.95, 1.0, 1.0 - abs(t - r));
                f *= max(0.0, 1.0 - t * t * 1.0);
                ret += vec3(1.0, 1.0, 1.0) * f;
            }
        }

        if(pti < kIntro) continue;
        float scl = (mod(pti - kIntro, kSeq) == 0.0) ? 4.0 : 1.0;
        scl = (pti == kIntro) ? 1.0 : scl;

        for(float k = 0.0; k < 2.0; k+=1.0) {
            float fd = 1.0 - smoothstep(1.0, 1.5, ft);
            float sr = (0.5 * ft * ft + 1e-1) * (1.0 + k * 2.0) * scl;
            vec3 sp = fp + vec3(0.0, sr, 0.0);
            vec3 shp0, shp1;
            vec2 sht = tsphere(ro, rd, sp, sr, shp0, shp1);
            for(int j = 0; j < 2; j++) {
                float st = (j == 0) ? sht.x : sht.y;
                if(st < 0.0 || st > maxt) continue;
                vec3 lp = rd * st + ro - sp;

                float a = atan(lp.z, lp.x) + pti * 0.7 + k * 0.6;
                float r = acos(clamp(-lp.y / sr, -1.0, 1.0)) / kPI;

                r *= max(1.0, sr * 5.0 / scl);
                vec2 uv = cos(vec2(a, a - kPI05)) * r;
                // ret += vec3(fract(length(uv)));
                float tx = flower(uv, 0.04);
                ret += vec3(4.0, 3.0, 2.0) * smoothstep(-0.02, 0.04, tx) * fd;
            }
        }

        float strrng = fract(sin(pti + 1.7654321));
        for(float k = 0.0; k < 20.0; k+= 1.0) {
            strrng = fract(strrng * (1.23456 + strrng) * 100.0);
            float rnd = strrng;
            float a = (k + pti) * 2.4 + rnd;
            vec3 pp = fp;
            pp.xz += cos(vec2(a, a - kPI05)) * ft * (0.4 + rnd * 0.8) * scl;
            pp.y += ft * (0.9 + rnd * 0.5 - ft) * 2.0 * scl;

            vec3 hp;
            float ht = tplane(ro, rd, vec3(0.0, 0.0, -1.0), pp, hp);
            if(ht >= 0.0 && ht < maxt) {
                vec2 uv = (hp.xy - pp.xy) * 12.0 / scl;
                uv = rotZ(uv.xyy, k * 2.4).xy;
                float tx = star(uv);
                float fd = 1.0 - smoothstep(1.0, 1.5, ft);
                ret += vec3(1.5, 1.0, 0.6) * fd * smoothstep(-0.1, 0.1, tx);
            }
        }
    }

    return ret;
}

void main() {
    vec3 t;
    t.x = time * 120.0 / 60.0;
    t.y = floor(t.x);
    t.z = fract(t.x);

    vec3 color = vec3(0.0);
    vec2 sc = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);

    vec3 ro = vec3(0.0, 0.0, 10.0);
    vec3 rd = normalize(vec3(sc.xy, -4.0));
    ro.xy += vec2(0.0, 0.4 + sin(time * 1.3) * 0.03) + sin(vec2(time) * vec2(0.7, 1.15)) * 0.04;
    vec3 lp = vec3(pos2(t.x - 0.6) * vec2(0.02, 0.002), sin(time * 0.2) * 0.05);
    rd = lookAt(ro, lp, vec3(0.0, 1.0, 0.0), rd);

    color = bg(rd);

    float td = 0.0;
    float blm = 0.0;
    for(int i = 0; i < 64; i++) {
        vec3 rp = rd * td + ro;
        float d = map(rp, t);
        if(d < 1e-2) {
            vec3 nv = normal(rp, t);
            color = mix(vec3(0.4, 0.4, 0.55), vec3(0.8), (nv.y * 0.5 + 0.5));
            color += vec3(0.2, 0.3, 0.0) * (1.0 - abs(nv.y));
            break;
        }
        blm += pow(max(0.0, 1.0 - d * 8.0), 10.0); 
        td += d;
    }
    color += vec3(0.1, 0.2, 0.2) * blm;

    if(t.y < kIntro) {
        color = vec3(0.0);
    } else {
        color += max(0.0, 1.0 - (t.x - kIntro) * 2.0);
    }

    color += fxs(ro, rd, td, t);
    gl_FragColor = vec4(pow(color, vec3(1.0/2.2)), 1.0);
}
