#version 330 core
out vec4 FragColor;
in vec2 vUV;
uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform vec4 iMouse;
uniform sampler2D iChannel1;

#define PASS_COUNT 1
#define SPIRAL_NOISE_ITER 6

float fBrightness = 2.0;
float fSteps = 181.0;
float fParticleSize = 0.005;
float fMinDist = 0.8;
float fMaxDist = 5.0;
float fRepeatMin = 1.0;
float fRepeatMax = 2.0;
float fDepthFade = 0.6;
float nebulaBrightness = 1.3;
vec3 gCamRight;
vec3 gCamUp;

float Random(float x) { return fract(sin(x * 123.456) * 23.4567 + sin(x * 345.678) * 45.6789 + sin(x * 456.789) * 56.789); }

vec3 GetParticleColour(vec3 vParticlePos, float size, vec3 vRayDir)
{
    float d = dot(vParticlePos, vRayDir);
    vec3 vDeltaPos = vParticlePos - vRayDir * d;
    float dist = length(vDeltaPos);
    float fps2 = size * size;
    float core = clamp(1.0 - dist / size, 0.0, 1.0);
    float halo = (size * 0.3) / (sqrt(dist) + size * 0.2);
    float glow = (fps2 * 0.5) / (dist * dist + fps2 * 0.25);
    float dx = dot(vDeltaPos, gCamRight);
    float dy = dot(vDeltaPos, gCamUp);
    float starSeed = fract(sin(dot(vParticlePos.xy, vec2(12.9898, 78.233))) * 43758.5453);
    float starAngle = starSeed * 6.2831 + iTime * (0.2 + starSeed * 0.6);
    float cs = cos(starAngle);
    float sn = sin(starAngle);
    vec2 r = vec2(cs * dx + sn * dy, -sn * dx + cs * dy);
    float rayWidthK = 1.0 / (fps2 * 0.09);
    float rayLenK = 1.0 / (size * 4.0);
    float rays = (exp(-r.y * r.y * rayWidthK) * exp(-abs(r.x) * rayLenK) + exp(-r.x * r.x * rayWidthK) * exp(-abs(r.y) * rayLenK)) * 0.35;
    float fShade = (core + halo + glow + rays) * exp2(-d * fDepthFade) * fBrightness;
    float h = fract(starSeed * 7.13 + 0.137);
    vec3 tint = mix(vec3(0.6, 0.8, 1.0), vec3(1.0), smoothstep(0.0, 0.33, h));
    tint = mix(tint, vec3(1.0, 0.95, 0.75), smoothstep(0.33, 0.66, h));
    tint = mix(tint, vec3(1.0, 0.7, 0.5), smoothstep(0.66, 1.0, h));
    return vec3(fShade) * mix(vec3(1.0), tint, 0.8);
}

vec3 GetParticlePos(vec3 vRayDir, float fZPos, float fSeed)
{
    float fAngle = atan(vRayDir.x, vRayDir.y);
    float fAngleFraction = fract(fAngle / 6.28);
    float fSegment = floor(fAngleFraction * fSteps + fSeed) + 0.5 - fSeed;
    float fParticleAngle = fSegment / fSteps * 6.28;
    float fSegmentPos = fSegment / fSteps;
    float fRadius = fMinDist + Random(fSegmentPos + fSeed) * (fMaxDist - fMinDist);
    float tunnelZ = vRayDir.z / length(vRayDir.xy / fRadius) + fZPos;
    float fRepeat = fRepeatMin + Random(fSegmentPos + 0.1 + fSeed) * (fRepeatMax - fRepeatMin);
    float fParticleZ = (ceil(tunnelZ / fRepeat) - 0.5) * fRepeat - fZPos;
    return vec3(sin(fParticleAngle) * fRadius, cos(fParticleAngle) * fRadius, fParticleZ);
}

vec3 Starfield(vec3 vRayDir, float fZPos, float fSeed) { return GetParticleColour(GetParticlePos(vRayDir, fZPos, fSeed), fParticleSize, vRayDir); }
vec3 RotateX(vec3 p, float a) { float s = sin(a), c = cos(a); return vec3(p.x, c * p.y + s * p.z, -s * p.y + c * p.z); }
vec3 RotateY(vec3 p, float a) { float s = sin(a), c = cos(a); return vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z); }
vec3 RotateZ(vec3 p, float a) { float s = sin(a), c = cos(a); return vec3(c * p.x + s * p.y, -s * p.x + c * p.y, p.z); }

float h1(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.yzx + 19.19); return fract((p.x + p.y) * p.z); }
float vn(vec3 p)
{
    vec3 i = floor(p), f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    return mix(mix(mix(h1(i), h1(i + vec3(1.0, 0.0, 0.0)), f.x), mix(h1(i + vec3(0.0, 1.0, 0.0)), h1(i + vec3(1.0, 1.0, 0.0)), f.x), f.y),
               mix(mix(h1(i + vec3(0.0, 0.0, 1.0)), h1(i + vec3(1.0, 0.0, 1.0)), f.x), mix(h1(i + vec3(0.0, 1.0, 1.0)), h1(i + vec3(1.0, 1.0, 1.0)), f.x), f.y), f.z);
}
float pn(vec3 p) { return 2.4 * vn(p) - 1.0; }
float hash(float p) { vec3 p3 = fract(vec3(p) * 0.1031); p3 += dot(p3, p3.yzx + 19.19); return fract((p3.x + p3.y) * p3.z); }

const float nudge = 20.0;
float normalizer = 1.0 / sqrt(1.0 + nudge * nudge);

float SpiralNoiseC(vec3 p, vec4 id)
{
    float iter = 2.0;
    float n = 2.0 - id.x;
    for (int i = 0; i < SPIRAL_NOISE_ITER; i++)
    {
        n += -abs(sin(p.y * iter) + cos(p.x * iter)) / iter;
        p.xy += vec2(p.y, -p.x) * nudge;
        p.xy *= normalizer;
        p.xz += vec2(p.z, -p.x) * nudge;
        p.xz *= normalizer;
        iter *= id.y + 0.733733;
    }
    return n;
}

float mapIntergalacticCloud(vec3 p, vec4 id)
{
    float k = 2.0 * id.w + 0.1;
    return k * (0.5 + SpiralNoiseC(p.zxy * 0.4132 + 333.0, id) * 3.0 + pn(p * 8.5) * 0.12);
}

vec3 hsv2rgb(float x, float y, float z)
{
    return z + z * y * (clamp(abs(mod(x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0) - 1.0);
}

vec4 renderIntergalacticClouds(vec3 ro, vec3 rd, float tmax, vec4 id)
{
    float max_dist = min(tmax, 22.0), td = 0.0, d, t, noi, lDist, a, sp = 9.0, rRef = 2.0 * id.x, h = 0.05 + 0.25 * id.z;
    vec3 pos, lightColor;
    vec4 sum = vec4(0.0);
    t = 0.1 * hash(hash(rd.x + rd.y * 7.0 + rd.z * 13.0));
    for (int i = 0; i < 100; i++)
    {
        if (td > 0.9 || sum.a > 0.99 || t > max_dist) break;
        a = smoothstep(max_dist, 0.0, t);
        pos = ro + t * rd;
        d = abs(mapIntergalacticCloud(pos, id)) + 0.07;
        lDist = max(length(mod(pos + sp * 0.5, sp) - sp * 0.5), 0.001);
        noi = pn(0.05 * pos);
        lightColor = mix(hsv2rgb(noi, 0.5, 0.6), hsv2rgb(noi + 0.3, 0.5, 0.6), smoothstep(rRef * 0.5, rRef * 2.0, lDist));
        sum.rgb += a * lightColor / exp(lDist * lDist * lDist * 0.08) / 30.0;
        if (d < h)
        {
            td += (1.0 - td) * (h - d) + 0.005;
            sum.rgb += sum.a * sum.rgb * 0.25 / lDist;
            sum += (1.0 - sum.a) * 0.02 * td * a;
        }
        td += 0.015;
        t += max(d * 0.08 * max(min(lDist, d), 2.0), 0.01);
    }
    sum = clamp(sum, 0.0, 1.0);
    sum.xyz *= sum.xyz * (3.0 - sum.xyz - sum.xyz);
    return sum;
}

mat2 rot(float a) { float s = sin(a), c = cos(a); return mat2(c, -s, s, c); }

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 vScreenPos = (fragCoord / iResolution.xy) * 2.0 - 1.0;
    vScreenPos.x *= iResolution.x / iResolution.y;
    vec3 vRayDir = normalize(vec3(vScreenPos, 1.0));
    vec3 vEuler = vec3(sin(iTime * 0.2) * 0.125, sin(iTime * 0.1) * 0.125, 0.5 + sin(iTime * 0.3) * 0.5);
    if (iMouse.z > 0.0)
    {
        vEuler.x = -((iMouse.y / iResolution.y) * 2.0 - 1.0);
        vEuler.y = -((iMouse.x / iResolution.x) * 2.0 - 1.0);
        vEuler.z = 0.0;
    }
    vRayDir = RotateZ(RotateY(RotateX(vRayDir, vEuler.x), vEuler.y), vEuler.z);
    gCamRight = RotateZ(RotateY(RotateX(vec3(1.0, 0.0, 0.0), vEuler.x), vEuler.y), vEuler.z);
    gCamUp = RotateZ(RotateY(RotateX(vec3(0.0, 1.0, 0.0), vEuler.x), vEuler.y), vEuler.z);

    float speed = 1.0;
    float fZPos = 2.0 + iTime * speed;
    vec3 col = mix(vec3(0.005, 0.0, 0.01), vec3(0.01, 0.005, 0.0), vRayDir.y * 0.5 + 0.5);
    float fSeed = 0.0;
    for (int i = 0; i < PASS_COUNT; i++)
    {
        col += Starfield(vRayDir, fZPos, fSeed);
        fSeed += 1.234;
    }

    vec3 ro = vec3(sin(iTime * 0.04) * 3.0, cos(iTime * 0.03) * 2.0, iTime * speed);
    vec4 clouds = renderIntergalacticClouds(ro, vRayDir, 22.0, vec4(0.5, 0.4, 0.16, 0.7));
    col += clouds.rgb * nebulaBrightness * (1.0 - clamp(length(col) * 2.0, 0.0, 1.0));

    vec3 vRayDirT = normalize(vec3(vScreenPos, 1.0));
    vRayDirT = RotateZ(RotateY(RotateX(vRayDirT, vEuler.x), vEuler.y), -0.1 + vEuler.z * 0.15);
    vec4 terrainAccum = vec4(0.0);
    float hitY = 0.0;
    float minSt = 999.0;
    float ii_t = 0.0;
    float dd_t = 0.0;
    float s_t = 0.0;
    vec2 curve_t = vec2(sin(iTime * 0.35) * 0.004, sin(iTime * 0.4) * 0.003);

    for (; ii_t++ < 40.0;)
    {
        vec3 p = dd_t * vRayDirT;
        p.xy += dd_t * dd_t * curve_t;
        p.z += iTime * 8.0;
        float audioElev = 0.0;
        for (s_t = 0.2; s_t < 0.9; s_t += s_t)
        {
            audioElev += 1.2 * texture(iChannel1, vec2(mod(p.x * 0.02 + 0.5, 1.0), mod(p.z * 0.03, 1.0))).r;
        }
        p.y += -0.2 + audioElev;
        float silenceBoost = (1.0 - smoothstep(0.0, 0.4, audioElev)) * 2.5;
        s_t = 0.03 + max(abs(p.x) - 2.5, abs(p.y)) + silenceBoost;
        dd_t += s_t;
        if (s_t < minSt) { minSt = s_t; hitY = audioElev; }
        float elevationFactor = smoothstep(-1.5, 2.0, p.y * p.y);
        float freqAtThisHeight = clamp(audioElev * 0.2, 0.0, 1.0);
        float amp = texture(iChannel1, vec2(freqAtThisHeight, mod(p.z * 0.03, 1.0))).r;
        float stepT = audioElev * 0.3 + p.z * 0.012 + p.x * 0.04;
        vec3 stepHue = hsv2rgb(stepT, 1.0, 1.0);
        vec4 terrainColor = vec4(stepHue, 1.0) / max(s_t, 0.01);
        terrainColor.rgb *= 0.1 + 0.8 * elevationFactor;
        terrainColor.rgb *= max(0.15, amp * audioElev * 1.5);
        float zDim = exp(-dd_t * 0.04);
        float zBands = 0.2 + 0.8 * cos(p.z * 0.01);
        terrainColor.rgb *= (0.2 + 0.7 * zDim) * (0.6 + 0.4 * zBands);
        float fade = smoothstep(80.0, 10.0, dd_t);
        terrainColor *= fade;
        if (fade < 0.0001) break;
        terrainAccum += terrainColor;
    }

    float t = clamp(0.85 - hitY * 0.35, 0.0, 0.85);
    vec3 heightHue = hsv2rgb(t, 1.0, 1.0);
    col += heightHue * terrainAccum.r / 100.0;
    float luma = dot(col, vec3(0.299, 0.587, 0.114));
    col = mix(vec3(luma), col, 1.6);
    fragColor = vec4(tanh(col * 1.5), 1.0);
}

void main()
{
    mainImage(FragColor, gl_FragCoord.xy);
    FragColor.a *= uAlpha;
}
