#version 410 core
in vec2 vUV;
uniform vec3 uCamPos;
uniform vec3 uCamForward;
uniform vec3 uCamRight;
uniform vec3 uCamUp;
uniform vec3 uPlayerPos;
uniform float uTime;
uniform float uAspect;
uniform int uEchoMask;
uniform int uExitOpen;
out vec4 FragColor;

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);
}

vec2 scene(vec3 p) {
    // The cube is the player. Everything else is built from endlessly repeated
    // sphere families, with smaller orbiting copies nested into each cell.
    float cube = sdBox(p - uPlayerPos, vec3(0.55));
    vec3 cell = floor((p + 5.0) / 10.0);
    vec3 q = mod(p + 5.0, 10.0) - 5.0;
    float phase = dot(cell, vec3(1.73, 2.41, 3.17)) + uTime * 0.22;
    float big = length(q) - (1.45 + 0.48 * sin(phase));

    vec3 orbit = vec3(sin(phase * 1.3), cos(phase * 0.9), sin(phase * 1.7)) * 2.65;
    float smallA = length(q - orbit) - 0.62;
    float smallB = length(q + orbit.zxy) - 0.38;
    float spheres = min(big, min(smallA, smallB));
    vec2 result = cube < spheres ? vec2(cube, 1.0) : vec2(spheres, 2.0 + mod(cell.x + cell.y * 2.0 + cell.z * 3.0, 5.0));

    vec3 echoes[5] = vec3[](vec3(8.0,34.0,0.0), vec3(-7.0,29.0,6.0), vec3(3.0,39.0,-8.0),
                            vec3(-9.0,36.0,-7.0), vec3(10.0,28.0,8.0));
    for (int i = 0; i < 5; ++i) {
        if ((uEchoMask & (1 << i)) == 0) {
            float pulse = 0.72 + 0.12 * sin(uTime * 2.1 + float(i));
            float echo = length(p - echoes[i]) - pulse;
            if (echo < result.x) result = vec2(echo, 10.0 + float(i));
        }
    }
    if (uExitOpen != 0) {
        float portal = length(p - vec3(0.5,32.0,0.5)) - (1.35 + 0.18 * sin(uTime * 1.8));
        if (portal < result.x) result = vec2(portal, 20.0);
    }
    return result;
}

vec3 normalAt(vec3 p) {
    const float e = 0.0025;
    float d = scene(p).x;
    return normalize(vec3(scene(p + vec3(e,0,0)).x - d,
                          scene(p + vec3(0,e,0)).x - d,
                          scene(p + vec3(0,0,e)).x - d));
}

vec3 palette(float t) {
    return 0.56 + 0.44 * cos(6.28318 * (vec3(0.02, 0.28, 0.56) + t));
}

void main() {
    vec2 s = vUV * 2.0 - 1.0;
    s.x *= uAspect;
    float r = length(s);
    float a = atan(s.y, s.x) + 0.035 * sin(uTime * 0.7 + r * 9.0);
    s = vec2(cos(a), sin(a)) * r;
    vec3 ro = uCamPos;
    vec3 rd = normalize(uCamForward * 1.65 + uCamRight * s.x + uCamUp * s.y);

    float t = 0.0;
    float mat = 0.0;
    float glow = 0.0;
    for (int i = 0; i < 112; ++i) {
        vec3 p = ro + rd * t;
        vec2 hit = scene(p);
        glow += 0.004 / (0.04 + abs(hit.x));
        if (hit.x < 0.0015 || t > 120.0) { mat = hit.y; break; }
        t += hit.x * 0.68;
    }

    vec3 col;
    if (t < 120.0) {
        vec3 p = ro + rd * t;
        vec3 n = normalAt(p);
        vec3 lightDir = normalize(vec3(sin(uTime * 0.23), 0.7, cos(uTime * 0.19)));
        float diff = 0.18 + 0.82 * max(dot(n, lightDir), 0.0);
        float rim = pow(1.0 - max(dot(n, -rd), 0.0), 2.5);
        if (mat < 1.5) {
            // The lone cube stays recognizably geometric and bright against the spheres.
            col = vec3(0.92, 0.98, 1.0) * diff + palette(uTime * 0.06) * rim * 1.8;
        } else if (mat >= 20.0) {
            col = vec3(1.0, 0.96, 0.72) * (1.4 + rim * 2.4) + palette(uTime * 0.12) * 0.8;
        } else if (mat >= 10.0) {
            col = palette(mat * 0.17 + uTime * 0.08) * (1.8 + diff + rim * 2.2);
        } else {
            col = palette(mat * 0.11 + t * 0.018 + uTime * 0.025) * (diff + rim * 0.8);
        }
        col *= exp(-t * 0.006);
    } else {
        float stars = pow(max(0.0, sin(a * 12.0 + log(r + 0.03) * 17.0 - uTime)), 18.0);
        col = palette(a / 6.28318 + uTime * 0.015) * (0.035 + stars * 0.75);
    }
    col += palette(uTime * 0.035 + r * 0.1) * min(glow, 0.32);
    col *= 1.0 - 0.22 * smoothstep(0.3, 1.4, r);
    col = pow(max(col, 0.0), vec3(0.82));
    FragColor = vec4(col, 1.0);
}
