#version 330 core

in vec2 vUV;
out vec4 FragColor;

uniform vec3 iResolution; // x=width, y=height
uniform float iTime;
uniform float uAlpha;

const float TAU = 6.28318530718;

float hash12(vec2 p)
{
    vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
    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(0.1031, 0.1030, 0.0973));
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.xx + p3.yz) * p3.zy);
}

vec3 hash32(vec2 p)
{
    vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
    p3 += dot(p3, p3.yxz + 33.33);
    return fract((p3.xxy + p3.yzz) * p3.zyx);
}

float noise(vec2 p)
{
    vec2 i = floor(p);
    vec2 f = fract(p);
    vec2 u = f * f * (3.0 - 2.0 * f);

    float a = hash12(i);
    float b = hash12(i + vec2(1.0, 0.0));
    float c = hash12(i + vec2(0.0, 1.0));
    float d = hash12(i + vec2(1.0, 1.0));

    return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

float fbm(vec2 p)
{
    float s = 0.0;
    float a = 0.5;

    for(int i = 0; i < 5; i++)
    {
        s += a * noise(p);
        p = mat2(1.6, 1.2, -1.2, 1.6) * p;
        a *= 0.5;
    }

    return s;
}

vec3 sky(vec3 rd)
{
    float h = clamp(rd.z * 0.5 + 0.5, 0.0, 1.0);
    vec3 a = vec3(0.004, 0.006, 0.011);
    vec3 b = vec3(0.025, 0.032, 0.045);
    vec3 c = mix(a, b, pow(h, 2.2));

    float glow = pow(max(dot(rd, normalize(vec3(-0.25, 0.35, 0.72))), 0.0), 30.0);
    c += glow * vec3(0.045, 0.08, 0.12);

    return c;
}

float buildingHeight(vec2 id)
{
    float n = fbm(id * 0.11);
    float h = mix(1.0, 10.0, pow(n, 1.8));
    h *= mix(0.7, 1.4, hash12(id + 19.7));
    return h;
}

void buildingBounds(vec2 id, out vec2 lo, out vec2 hi)
{
    vec2 r1 = hash22(id + 3.1);
    vec2 r2 = hash22(id + 9.4);

    lo = id + 0.08 + 0.16 * r1;
    hi = id + 0.82 - 0.18 * r2;
}

bool boxHit(vec3 ro, vec3 rd, vec3 lo, vec3 hi, out float t, out vec3 n)
{
    vec3 inv = 1.0 / rd;
    vec3 t0 = (lo - ro) * inv;
    vec3 t1 = (hi - ro) * inv;

    vec3 mn = min(t0, t1);
    vec3 mx = max(t0, t1);

    float tn = max(max(mn.x, mn.y), mn.z);
    float tf = min(min(mx.x, mx.y), mx.z);

    if(tf < max(tn, 0.0))
    {
        t = 1e9;
        n = vec3(0.0);
        return false;
    }

    t = tn;

    vec3 p = ro + rd * t;
    vec3 e = min(abs(p - lo), abs(p - hi));

    if(e.x < e.y && e.x < e.z)
    {
        n = vec3(sign(lo.x + hi.x - 2.0 * p.x), 0.0, 0.0);
    }
    else if(e.y < e.z)
    {
        n = vec3(0.0, sign(lo.y + hi.y - 2.0 * p.y), 0.0);
    }
    else
    {
        n = vec3(0.0, 0.0, sign(lo.z + hi.z - 2.0 * p.z));
    }

    return true;
}

vec4 traceCity(vec3 ro, vec3 rd, out vec3 normal, out vec2 bid)
{
    vec2 cell = floor(ro.xy);
    vec2 stepDir = sign(rd.xy);
    vec2 invAbs = abs(1.0 / rd.xy);
    vec2 nextSide = (cell - ro.xy + 0.5 + 0.5 * stepDir) / rd.xy;

    normal = vec3(0.0);
    bid = cell;

    float beacon = 0.0;

    for(int i = 0; i < 128; i++)
    {
        vec2 lo2;
        vec2 hi2;
        buildingBounds(cell, lo2, hi2);

        float h = buildingHeight(cell);
        vec3 lo = vec3(lo2, 0.0);
        vec3 hi = vec3(hi2, h);

        float t;
        vec3 n;

        if(boxHit(ro, rd, lo, hi, t, n))
        {
            normal = n;
            bid = cell;
            return vec4(t, beacon, 1.0, 1.0);
        }

        float bp = 0.0015 * h * h;
        vec2 bh = hash22(cell + 41.0);

        if(bh.x < bp)
        {
            vec3 bc = vec3(cell + 0.5, h + 0.35);
            float bt = dot(bc - ro, rd);

            if(bt > 0.0)
            {
                vec3 q = ro + rd * bt;
                float d = length(q - bc);
                float pulse = smoothstep(0.9, 0.1, d);
                pulse *= 0.5 + 0.5 * cos(TAU * (0.55 * iTime + bh.y));
                beacon += pulse * exp(-0.06 * bt);
            }
        }

        vec2 m = step(nextSide.xy, nextSide.yx);
        cell += m * stepDir;
        nextSide += m * invAbs;
    }

    if(rd.z < -0.001)
    {
        float t = -ro.z / rd.z;
        normal = vec3(0.0, 0.0, 1.0);
        bid = floor((ro + rd * t).xy);
        return vec4(t, beacon, 0.0, 1.0);
    }

    return vec4(1e9, beacon, 0.0, 0.0);
}

float windowMask(vec3 p, vec2 id, vec3 n)
{
    vec2 uv;

    if(abs(n.x) > 0.5)
    {
        uv = p.yz;
    }
    else if(abs(n.y) > 0.5)
    {
        uv = p.xz;
    }
    else
    {
        uv = p.xy;
    }

    vec2 grid = floor(uv / vec2(0.18, 0.22));
    vec2 f = fract(uv / vec2(0.18, 0.22));

    float frame =
        smoothstep(0.16, 0.24, f.x) *
        smoothstep(0.84, 0.76, f.x) *
        smoothstep(0.24, 0.34, f.y) *
        smoothstep(0.90, 0.78, f.y);

    float lit = step(0.63, hash12(id + grid * vec2(7.1, 17.3)));
    float bands = 0.5 + 0.5 * sin(p.z * 5.0 + hash12(id) * TAU);

    return frame * lit * mix(0.45, 1.0, bands);
}

vec3 cityColor(vec3 ro, vec3 rd)
{
    vec3 n;
    vec2 id;
    vec4 hit = traceCity(ro, rd, n, id);

    vec3 p = ro + rd * hit.x;
    vec3 col = sky(rd) * 0.22;

    if(hit.w > 0.5)
    {
        if(hit.z > 0.5)
        {
            vec3 hsh = hash32(id);

            vec3 base = vec3(0.004, 0.006, 0.009);
            base += 0.018 * hsh;

            float win = windowMask(p, id, n);

            vec3 wc = vec3(0.025, 0.075, 0.19) + 0.12 * hsh;
            wc += vec3(0.35, 0.16, 0.055) * step(0.86, hsh.x);

            float edge = pow(1.0 - abs(dot(n, rd)), 2.0);
            col = base + win * wc * 1.45 + edge * vec3(0.025, 0.07, 0.12);
        }
        else
        {
            float lane = pow(1.0 - abs(fract(p.x * 0.5) - 0.5) * 2.0, 22.0);
            lane += pow(1.0 - abs(fract(p.y * 0.5) - 0.5) * 2.0, 22.0);
            col = vec3(0.006, 0.007, 0.009) + lane * vec3(0.045, 0.22, 0.38);
        }

        float street = exp(-p.z / 0.55);
        col += street * vec3(0.035, 0.16, 0.28);

        float fog = exp(-0.105 * hit.x);
        fog *= exp(-0.11 * max(p.z, 0.0));
        col = mix(vec3(0.018, 0.022, 0.028), col, fog);
    }

    col += hit.y * vec3(0.75, 0.08, 0.025);
    col += hit.y * hit.y * 0.35;

    return col;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;

    float a = 0.26 * iTime;
    float r = 17.0 + 2.5 * sin(iTime * 0.13);
    float h = 9.5 + 2.0 * sin(iTime * 0.21 + 1.4);

    vec3 target = vec3(
        7.0 * cos(a * 0.37),
        7.0 * sin(a * 0.37),
        2.4 + 0.7 * sin(iTime * 0.19)
    );

    vec3 ro = target + vec3(
        r * cos(a),
        r * sin(a),
        h
    );

    vec3 ww = normalize(target - ro);
    vec3 uu = normalize(cross(ww, vec3(0.0, 0.0, 1.0)));
    vec3 vv = cross(uu, ww);

    vec3 rd = normalize(uv.x * uu + uv.y * vv + 2.2 * ww);

    vec3 col = cityColor(ro, rd);

    col *= 0.72;
    col = col / (1.0 + col);
    col = pow(col, vec3(0.62));

    fragColor = vec4(col, 1.0);
}

void main()
{
    vec2 fragCoord = vUV * iResolution.xy;
    vec4 outColor;
    mainImage(outColor, fragCoord);
    outColor.a *= uAlpha;
    FragColor = outColor;
}
