vec3 get_stars(vec3 PlayerPosN) {
    vec3 StarCoord = PlayerPosN / (PlayerPosN.y + length(PlayerPosN.xz));
    //StarCoord.x += frameTimeCounter * 0.001;
    const float ACTUAL_STAR_SIZE = 386;
    vec3 StarCoordFloor = floor(StarCoord * ACTUAL_STAR_SIZE) / ACTUAL_STAR_SIZE;
    vec3 StarCoordMid = StarCoordFloor + 0.5 / ACTUAL_STAR_SIZE;

    float Visibility = smoothstep(0, 0.1, StarCoord.y); // Smoothly fade out stars near the bottom of the sky
    Visibility *= max(0.5 - distance(StarCoordMid.xz, StarCoord.xz) * ACTUAL_STAR_SIZE, 0) * 2;

    float StarNoise = random(StarCoordFloor.xz);
    #ifdef RAINBOW_STARS
    vec3 StarColor = vec3(hash2(StarCoordFloor.xz), StarNoise);
    #else
    vec3 StarColor = vec3(0.5);
    #endif
    return vec3(max(0, StarNoise - 0.995) * 50 * Visibility * StarColor);
}

vec3 get_aurora(vec3 PlayerPosN, float Dither) {
    if(precipitationSmooth <= 1) return vec3(0);

    const vec3 COLOR_TOP = srgb_linear(vec3(28, 255, 218) / 255.0);
    const vec3 COLOR_BOTTOM = srgb_linear(vec3(122, 255, 28) / 255.0);
    const float AURORA_STRENGTH = 3;

    // Calculate intersection with aurora plane
    const float PLANE_TOP = 17.0;
    const float PLANE_BOTTOM = 10.0;
    const int SAMPLE_COUNT = 4;

    vec3 StartPos = PLANE_BOTTOM / PlayerPosN.y * PlayerPosN; 
    vec3 EndPos = PLANE_TOP / PlayerPosN.y * PlayerPosN;
    vec3 Step = (EndPos - StartPos) / SAMPLE_COUNT;
    vec3 Pos = Step * Dither + StartPos;

    vec2 Wind = frameTimeCounter * vec2(2, 3.25);
    vec3 AuroraColor = vec3(0);
    for(int i = 1; i <= SAMPLE_COUNT; i++) {
        vec2 D = vec2(textureNice(noisetex, (Pos.xz + Wind) * 0.0001).rg);
        vec2 Displacement = D * 10.5;

        float Noise = texture(waterNoise, (Pos.xz - Displacement) / vec2(100, 100)).r;

        Noise = pow2(pow4(Noise));
        Noise *= smoothstep(0.0, 0.2, PlayerPosN.y);
        AuroraColor += Noise * mix(COLOR_BOTTOM, COLOR_TOP, smoothstep(StartPos.y, EndPos.y, Pos.y));

        Pos += Step;
    }

    return AuroraColor * AURORA_STRENGTH * (precipitationSmooth - 1) / SAMPLE_COUNT;
}

vec3 get_sky_overworld(vec3 ViewPosN, const bool DrawSun, float PlayerPosY) {
    float VangUp = acosf(dot(gbufferModelView[1].xyz, ViewPosN)) - PI / 2;
    float v = 0.5 + 0.5 * sign(-VangUp) * sqrt(abs(-VangUp) / (PI / 2));

    float FakeSkyGround = SmoothMax(v, 0.53, 0.01);
    v = mix(FakeSkyGround, v, clamp(cameraPosition.y / 1000 - 1, 0, 1));

    float u = dot(sunPosN, ViewPosN) * 0.5 + 0.5;

    vec3 SkyColor = texture(atm_skyview_sampler, vec2(u, v)).rgb;

    float Fade = smoothstep(-0.02, 0.1, PlayerPosY);

    if (DrawSun) {
        SkyColor += step(0.9997, dot( sunPosN, ViewPosN)) * dataBuf.SunColor * Fade;
        SkyColor += step(0.9998, dot(-sunPosN, ViewPosN)) * dataBuf.MoonColor * Fade;
    }

    return SkyColor;
}

vec3 get_sky_nether() {
    vec3 fogColorL = srgb_linear(fogColor.rgb);
    vec3 SkyColor = fogColorL / get_luminance(fogColorL);
    return SkyColor / 100;
}

const vec3 MainColor = vec3(3.0, 2.5, 2.0);
#define ITERATIONS 50

float noise(vec3 x) {
    vec3 p = floor(x);
    vec3 f = fract(x);
    f = f*f*(3.0-2.0*f);
    vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
    vec2 rg = textureLod(noisetex, (uv+ 0.5)/64.0, 0.0 ).yx;
    return -1.0+2.0*mix( rg.x, rg.y, f.z );
}

float pcurve( float x, float a, float b ) {
    float k = pow(a+b,a+b) / (pow(a,a)*pow(b,b));
    return k * pow( x, a ) * pow( 1.0-x, b );
}

float atan2(float y, float x) {
    if (x > 0.0) return atan(y / x);
    else if (x == 0.0) {
        if (y > 0.0) return PI / 2.0;
        else if (y < 0.0) return -(PI / 2.0);
        else return 0.0;
    } else {
        if (y >= 0.0) return atan(y / x) + PI;
        else return atan(y / x) - PI;
    }
}

float sdTorus(vec3 p, vec2 t) {
    vec2 q = vec2(length(p.xz) - t.x, p.y);
    return length(q)-t.y;
}

void Haze(inout vec3 color, vec3 pos, float alpha) {
    vec2 t = vec2(1.0, 0.01);
    float torusDist = length(sdTorus(pos + vec3(0.0, -0.05, 0.0), t));
    float bloomDisc = 1.0 / (pow(torusDist, 2.0) + 0.001);
    vec3 col = MainColor;
    bloomDisc *= length(pos) < 0.5 ? 0.0 : 1.0;
    color += col * bloomDisc * (2.9 / float(ITERATIONS)) * (1.0 - alpha * 1.0);
}

void GasDisc(inout vec3 color, inout float alpha, vec3 pos) {
    float discRadius = 3.2;
    float discWidth = 5.3;
    float discInner = discRadius - discWidth * 0.5;
    float discOuter = discRadius + discWidth * 0.5;
    vec3 origin = vec3(0.0, 0.0, 0.0);
    vec3 discNormal = normalize(vec3(0.0, 1.0, 0.0));
    float discThickness = 0.1;
    float distFromCenter = distance(pos, origin);
    float distFromDisc = dot(discNormal, pos - origin);
    float radialGradient = 1.0 - saturate((distFromCenter - discInner) / discWidth * 0.5);
    float coverage = pcurve(radialGradient, 4.0, 0.9);
    discThickness *= radialGradient;
    coverage *= saturate(1.0 - abs(distFromDisc) / discThickness);
    vec3 dustColorLit = MainColor;
    float dustGlow = 1.0 / (pow(1.0 - radialGradient, 2.0) * 290.0 + 0.002);
    vec3 dustColor = dustColorLit * dustGlow * 8.2;
    coverage = saturate(coverage * 0.7);
    dustColor = mix(dustColor, dustColorLit * 150.0 * dustGlow, saturate(1.0 - coverage * 1.0));
    coverage *= pcurve(radialGradient, 4.0, 0.9);
    if (coverage < 0.01) return;
    vec3 radialCoords;
    radialCoords.x = distFromCenter * 1.5 + 0.55;
    radialCoords.y = atan2(-pos.x, -pos.z) * 1.5;
    radialCoords.z = distFromDisc * 1.5;
    radialCoords *= 0.95;
    float speed = 0.06;
    float noise1 = 1.0;
    vec3 rc = radialCoords + 0.0;
    rc.y += frameTimeCounter * speed;
    noise1 *= noise(rc * 3.0) * 0.5 + 0.5;
    rc.y -= frameTimeCounter * speed;
    noise1 *= noise(rc * 6.0) * 0.5 + 0.5;
    rc.y += frameTimeCounter * speed;
    noise1 *= noise(rc * 12.0) * 0.5 + 0.5;
    rc.y -= frameTimeCounter * speed;
    noise1 *= noise(rc * 24.0) * 0.5 + 0.5;
    rc.y += frameTimeCounter * speed;
    float noise2 = 2.0;
    rc = radialCoords + 30.0;
    noise2 *= noise(rc * 3.0) * 0.5 + 0.5;
    rc.y += frameTimeCounter * speed;
    noise2 *= noise(rc * 6.0) * 0.5 + 0.5;
    rc.y -= frameTimeCounter * speed;
    noise2 *= noise(rc * 12.0) * 0.5 + 0.5;
    rc.y += frameTimeCounter * speed;
    noise2 *= noise(rc * 24.0) * 0.5 + 0.5;
    rc.y -= frameTimeCounter * speed;
    noise2 *= noise(rc * 48.0) * 0.5 + 0.5;
    rc.y += frameTimeCounter * speed;
    noise2 *= noise(rc * 92.0) * 0.5 + 0.5;
    rc.y -= frameTimeCounter * speed;
    dustColor *= noise1 * 0.998 + 0.002;
    coverage *= noise2;
    radialCoords.y += frameTimeCounter * speed * 0.5;
    dustColor *= pow(texture(noisetex, radialCoords.yx * vec2(0.15, 0.27)).rgb, vec3(2.0)) * 4.0;
    coverage = saturate(coverage * 1200.0 / float(ITERATIONS));
    dustColor = max(vec3(0.0), dustColor);
    coverage *= pcurve(radialGradient, 4.0, 0.9);
    color = (1.0 - alpha) * dustColor * coverage + color;
    alpha = (1.0 - alpha) * coverage + alpha;
}

void WarpSpace(inout vec3 eyevec, inout vec3 raypos) {
    vec3 origin = vec3(0.0, 0.0, 0.0);
    float singularityDist = distance(raypos, origin);
    float warpFactor = 1.0 / (pow(singularityDist, 2.0) + 0.000001);
    vec3 singularityVector = normalize(origin - raypos);
    float warpAmount = 5.0;
    eyevec = normalize(eyevec + singularityVector * warpFactor * warpAmount / float(ITERATIONS));
}

vec3 get_sky_end(vec3 ViewPosN) {
    const float far = 15.0;
    vec3 color = vec3(0.0);
    float alpha = 0.0;
    float dither = 0.0;
#ifdef GL_FRAGMENT_SHADER
    dither = bayer8(gl_FragCoord.xy);
#endif
    vec3 raypos = cameraPosition + ViewPosN * dither * far / float(ITERATIONS);
    for(int i = 0; i < ITERATIONS; i++) {
        WarpSpace(ViewPosN, raypos);
        raypos += ViewPosN * far / float(ITERATIONS);
        GasDisc(color, alpha, raypos);
        Haze(color, raypos, alpha);
    }
    color *= 0.01; // adjust for brightness
    return color;
}

vec3 get_sky(vec3 ViewPosN, const bool DrawSun, float PlayerPosY) {
    #ifdef DIMENSION_OVERWORLD
    return get_sky_overworld(ViewPosN, DrawSun, PlayerPosY);
    #elif defined DIMENSION_NETHER
    return get_sky_nether();
    #elif defined DIMENSION_END
    return get_sky_end(ViewPosN);
    #else
    return vec3(1);
    #endif
}
