#ifdef ATM_COLOR_MULTS
    #include "/lib/colors/colorMultipliers.glsl"
#endif
#ifdef MOON_PHASE_INF_ATMOSPHERE
    #include "/lib/colors/moonPhaseInfluence.glsl"
#endif

#ifdef BORDER_FOG
    #ifdef OVERWORLD
        #include "/lib/atmospherics/sky.glsl"
    #elif defined NETHER
        #include "/lib/colors/skyColors.glsl"
    #endif

    void DoBorderFog(inout vec4 color, inout float skyFade, float lPos, float VdotU, float VdotS, float dither) {
        #ifdef OVERWORLD
            float fog = lPos / renderDistance;
            fog = pow2(pow2(fog));
            #if !defined DISTANT_HORIZONS && !defined VOXY
                fog = pow2(pow2(fog));
            #endif
            fog = 1.0 - exp(-3.0 * fog);
        #endif
        #ifdef NETHER
            float farM = min(renderDistance, NETHER_VIEW_LIMIT); // consistency9023HFUE85JG
            float fog = lPos / farM;
            fog = mix(fog, min1(lPos / min(farM, 256.0)), 0.5);
        #endif
        #ifdef END
            float fog = lPos / renderDistance;
            fog = pow2(pow2(fog));
            fog = 1.0 - exp(-3.0 * fog);
        #endif

        #ifdef IRIS_FEATURE_FADE_VARIABLE
            #if defined GBUFFERS_WATER || defined DEFERRED1
                float chunkFadeM = mix(1.0, chunkFade, pow2(clamp01(lPos * 0.015))); // don't do fade very close to the player
                fog = mix(1.0, fog, chunkFadeM);
            #endif
        #endif

        if (fog > 0.0) {
            fog = clamp(fog, 0.0, 1.0);

            #ifdef OVERWORLD
                vec3 fogColorM = GetSky(VdotU, VdotS, dither, true, false);
            #elif defined NETHER
                vec3 fogColorM = netherColor;
            #else
                vec3 fogColorM = endSkyColor;
            #endif

            #ifdef ATM_COLOR_MULTS
                fogColorM *= atmColorMult;
            #endif
            #ifdef MOON_PHASE_INF_ATMOSPHERE
                fogColorM *= moonPhaseInfluence;
            #endif

            color = mix(color, vec4(fogColorM, 0.0), fog);

            #ifndef GBUFFERS_WATER
                skyFade = fog;
            #else
                skyFade = fog * (1.0 - isEyeInWater);
            #endif
        }
    }
#endif

#ifdef CAVE_FOG
    #include "/lib/atmospherics/fog/caveFactor.glsl"

    void DoCaveFog(inout vec4 color, float lViewPos) {
        float fog = GetCaveFactor() * (0.9 - 0.9 * exp(- lViewPos * 0.015));

        color = mix(color, vec4(caveFogColor, 0.0), fog);
    }
#endif

#ifdef ATMOSPHERIC_FOG
    #include "/lib/colors/lightAndAmbientColors.glsl"
    #include "/lib/colors/skyColors.glsl"

    // SRATA: Atm. fog starts reducing above this altitude
    // CRFTM: Atm. fog continues reducing for this meters
    #ifdef OVERWORLD
        #define atmFogSRATA ATM_FOG_ALTITUDE + 0.1
        #if !defined DISTANT_HORIZONS && !defined VOXY
            float atmFogCRFTM = 85.0;
        #else
            float atmFogCRFTM = 110.0;
        #endif

        vec3 GetAtmFogColor(float altitudeFactorRaw, float VdotS) {
            float nightFogMult = 2.5 - 0.625 * max(pow2(pow2(altitudeFactorRaw)), rainFactor);
            float dayNightFogBlend = pow(invNightFactor, 4.0 - VdotS - 2.5 * sunVisibility2);
            return mix(
                nightUpSkyColor * (nightFogMult - dayNightFogBlend * nightFogMult),
                dayDownSkyColor * (0.9 + 0.3 * noonFactor)
                    #if defined DISTANT_HORIZONS || defined VOXY
                        + dayUpSkyColor * 0.2 * sqrt1(noonFactor)
                    #endif
                ,
                dayNightFogBlend
            );
        }
    #else
        float atmFogSRATA = 55.1;
        float atmFogCRFTM = 30.0;
    #endif

    float GetAtmFogAltitudeFactor(float altitude) {
        float altitudeFactor = pow2(1.0 - clamp(altitude - atmFogSRATA, 0.0, atmFogCRFTM) / atmFogCRFTM);
        #ifndef LIGHTSHAFTS_ACTIVE
            altitudeFactor = mix(altitudeFactor, 1.0, rainFactor * 0.2);
        #endif
        return altitudeFactor;
    }

    void DoAtmosphericFog(inout vec4 color, vec3 playerPos, float lViewPos, float VdotS) {
        #if !defined DISTANT_HORIZONS && !defined VOXY
            float renDisFactor = min1(192.0 / renderDistance);

            #if ATM_FOG_DISTANCE != 100
                #define ATM_FOG_DISTANCE_M 100.0 / ATM_FOG_DISTANCE;
                renDisFactor *= ATM_FOG_DISTANCE_M;
            #endif

            float fog = 1.0 - exp(-pow(lViewPos * (0.0014 - 0.001 * rainFactor), 2.0 - rainFactor2) * lViewPos * renDisFactor);
        #else
            float fog = 1.0 - exp2(-max0(lViewPos - 40.0) * (0.4 + 0.4 * rainFactor) / ATM_FOG_DISTANCE);
        #endif

        float altitudeFactorRaw = GetAtmFogAltitudeFactor(playerPos.y + cameraPosition.y);
        float altitudeFactor = altitudeFactorRaw * 0.9 + 0.1;

        #if defined DISTANT_HORIZONS || defined VOXY
            altitudeFactor = mix(altitudeFactor, 1.0, pow2(pow2(fog)));
        #endif

        fog *= ATM_FOG_MULT - 0.1 - 0.30 * invRainFactor;

        #ifdef OVERWORLD
            float cameraAltitudeFactor = GetAtmFogAltitudeFactor(cameraPosition.y + 0.25 * atmFogCRFTM);
            altitudeFactor = mix(altitudeFactor, 1.0, 0.8 * cameraAltitudeFactor);

            altitudeFactor *= 1.0 - 0.5 * cameraAltitudeFactor * invRainFactor;

            #if defined SPECIAL_BIOME_WEATHER || RAIN_STYLE == 2
                if (isEyeInWater == 0) {
                    #if RAIN_STYLE == 2
                        float factor = 1.0;
                    #else
                        float factor = max(inSnowy, inDry);
                    #endif

                    float fogFactor = 4.0;
                    #ifdef SPECIAL_BIOME_WEATHER
                        fogFactor += 2.0 * inDry;
                    #endif
                    fogFactor *= 0.5 + 0.5 * sunVisibility;

                    float fogIntense = pow2(1.0 - exp(-lViewPos * fogFactor / ATM_FOG_DISTANCE));
                    fog = mix(fog, fogIntense / altitudeFactor, 1.0 * rainFactor * factor);
                }
            #endif

            #ifdef CAVE_FOG
                fog *= 0.2 + 0.8 * sqrt2(eyeBrightnessM);
                fog *= 1.0 - GetCaveFactor();
            #else
                fog *= eyeBrightnessM;
            #endif
        #else
            fog *= 0.5;
        #endif

        fog *= altitudeFactor;

        if (fog > 0.0) {
            fog = clamp(fog, 0.0, 1.0);

            #ifdef OVERWORLD
                vec3 fogColorM = GetAtmFogColor(altitudeFactorRaw, VdotS);
            #else
                vec3 fogColorM = endSkyColor * 1.5;
            #endif

            #ifdef ATM_COLOR_MULTS
                fogColorM *= atmColorMult;
            #endif
            #ifdef MOON_PHASE_INF_ATMOSPHERE
                fogColorM *= moonPhaseInfluence;
            #endif

            color = mix(color, vec4(fogColorM, 0.0), fog);
        }
    }
#endif

#include "/lib/atmospherics/fog/waterFog.glsl"

void DoWaterFog(inout vec4 color, float lViewPos) {
    #if REALISTIC_WATER_COLOR == 1
        // Same per-channel absorption as the surface-view case, so being
        // fully submerged fades to blue-green at a depth-accurate rate
        // instead of a flat single-speed fog mix.
        color.rgb = GetRealisticWaterColor(color.rgb, waterFogColor, lViewPos);

        // ("underwater kejernihan, nggak ada kabut" fix): di atas cuma
        // ngubah WARNA (tint) berdasarkan jarak, tapi nggak benar-benar
        // "mengaburkan" pandangan seperti kabut asli - hasilnya keliatan
        // jernih walau udah agak jauh. Tambahin blending kabut nyata di
        // atas tint-nya, pakai kurva fog yang sama dengan mode biasa,
        // supaya jarak pandang beneran terbatas & berkabut di dalam air.
        float haze = GetWaterFog(lViewPos);
        color.rgb = mix(color.rgb, waterFogColor, haze * 0.6);
        color.a *= 1.0 - haze;
    #else
        float fog = GetWaterFog(lViewPos);
        color = mix(color, vec4(waterFogColor, 0), fog);
    #endif
}

void DoLavaFog(inout vec4 color, float lViewPos) {
    #ifndef VOXY_PATCH
        float fog = (lViewPos * 3.0 - gl_Fog.start) * gl_Fog.scale;

        #ifdef LESS_LAVA_FOG
            fog = sqrt(fog) * 0.4;
        #endif

        fog = 1.0 - exp(-fog);

        fog = clamp(fog, 0.0, 1.0);
    #else
        float fog = 1.0;
    #endif

    color = mix(color, vec4(fogColor * 5.0, 0.0), fog);
}

void DoPowderSnowFog(inout vec4 color, float lViewPos) {
    float fog = lViewPos;

    #ifdef LESS_LAVA_FOG
        fog = sqrt(fog) * 0.4;
    #endif

    fog *= fog;
    fog = 1.0 - exp(-fog);

    fog = clamp(fog, 0.0, 1.0);
    color = mix(color, vec4(fogColor, 0.0), fog);
}

void DoBlindnessFog(inout vec4 color, float lViewPos) {
    float fog = lViewPos * 0.3 * blindness;
    fog *= fog;
    fog = 1.0 - exp(-fog);

    fog = clamp(fog, 0.0, 1.0);
    color *= 1.0 - fog;
}

void DoDarknessFog(inout vec4 color, float lViewPos) {
    float fog = lViewPos * 0.075 * darknessFactor;
    fog *= fog;
    fog *= fog;
    color *= exp(-fog);
}

void DoFog(inout vec4 color, inout float skyFade, float lViewPos, vec3 playerPos, float VdotU, float VdotS, float dither, bool isReflection, float lBlockPos) {
    #ifdef CAVE_FOG
        DoCaveFog(color, lViewPos);
    #endif
    #ifdef ATMOSPHERIC_FOG
        float lViewPosAtm = lViewPos;
        // Reduce fog if the reflecting block is already behind fog, and fogging the reflection would result in too much fog
        if (isReflection) lViewPosAtm *= max0(1.0 - lBlockPos / lViewPos);
        DoAtmosphericFog(color, playerPos, lViewPosAtm, VdotS);
    #endif
    #ifdef BORDER_FOG
        DoBorderFog(color, skyFade, max(length(playerPos.xz), abs(playerPos.y)), VdotU, VdotS, dither);
    #endif

    if (isEyeInWater == 1) DoWaterFog(color, lViewPos);
    else if (isEyeInWater == 2) DoLavaFog(color, lViewPos);
    else if (isEyeInWater == 3) DoPowderSnowFog(color, lViewPos);

    if (blindness > 0.00001) DoBlindnessFog(color, lViewPos);
    if (darknessFactor > 0.00001) DoDarknessFog(color, lViewPos);
}