#if CLOUD_UNBOUND_SIZE_MULT != 100
    #define CLOUD_UNBOUND_SIZE_MULT_M CLOUD_UNBOUND_SIZE_MULT * 0.01
#endif

#include "/lib/colors/lightAndAmbientColors.glsl"
#include "/lib/colors/cloudColors.glsl"
#include "/lib/atmospherics/sky.glsl"

float InterleavedGradientNoiseForClouds() {
    float n = 52.9829189 * fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y);
    #ifdef TAA
        return fract(n + goldenRatio * mod(float(frameCounter), 3600.0));
    #else
        return fract(n);
    #endif
}

#if SHADOW_QUALITY > -1 && !defined VOXY_PATCH
    vec3 GetShadowOnCloudPosition(vec3 tracePos, vec3 cameraPos) {
        vec3 wpos = PlayerToShadow(tracePos - cameraPos);
        float distb = sqrt(wpos.x * wpos.x + wpos.y * wpos.y);
        float distortFactor = 1.0 - shadowMapBias + distb * shadowMapBias;
        vec3 shadowPosition = vec3(vec2(wpos.xy / distortFactor), wpos.z * 0.2);
        return shadowPosition * 0.5 + 0.5;
    }

    bool GetShadowOnCloud(vec3 tracePos, vec3 cameraPos, int cloudAltitude, float lowerPlaneAltitude, float higherPlaneAltitude) {
        const float cloudShadowOffset = 0.5;

        vec3 shadowPosition0 = GetShadowOnCloudPosition(tracePos, cameraPos);
        if (length(shadowPosition0.xy * 2.0 - 1.0) < 1.0) {
            float shadowsample0 = shadow2D(shadowtex0, shadowPosition0).z;

            if (shadowsample0 == 0.0) return true;
        }

        return false;
    }
#endif

#ifdef CLOUDS_REIMAGINED
    #include "/lib/atmospherics/clouds/reimaginedClouds.glsl"
#endif
#ifdef CLOUDS_UNBOUND
    #include "/lib/atmospherics/clouds/unboundClouds.glsl"
#endif

vec4 GetClouds(inout float cloudLinearDepth, float skyFade, vec3 cameraPosOffset, vec3 playerPos,
               float lViewPos, float VdotS, float VdotU, float dither, vec3 auroraBorealis, vec3 nightNebula) {
    vec4 clouds = vec4(0.0);

    vec3 nPlayerPos = normalize(playerPos);
    float lViewPosM = lViewPos < renderDistance * 1.5 ? lViewPos - 1.0 : 1000000000.0;
    float skyMult0 = pow2(skyFade * 3.333333 - 2.333333);

    #if IRIS_VERSION >= 10800
        #ifdef CLOUDS_REIMAGINED
            float modFactor = 1.0 / cloudNarrowness * 256.0;
        #else
            #if CLOUD_UNBOUND_SIZE_MULT == 100
                float modFactor = 1.0 / cloudNarrowness;
            #else
                float modFactor = 1.0 / (cloudNarrowness * CLOUD_UNBOUND_SIZE_MULT_M);
            #endif
        #endif

        int modFactorM = int(modFactor);
        
        vec2 cameraPositionBIM = cameraPositionInt.xz - modFactorM * (cameraPositionInt.xz / modFactorM);

        vec3 cameraPos = vec3(
            cameraPositionBIM.x + cameraPositionBestFract.x,
            cameraPosition.y,
            cameraPositionBIM.y + cameraPositionBestFract.z
        );
    #else
        vec3 cameraPos = cameraPosition;
    #endif
    cameraPos += cameraPosOffset;

    #ifdef CLOUDS_REIMAGINED
        float thresholdF = 4000.0;
    #else
        float thresholdF = 4000.0;
    #endif

    //float thresholdMix = pow2(clamp01(VdotU * 15.0));
    //thresholdF = mix(far, thresholdF, thresholdMix * 0.5 + 0.5);

    #ifdef CLOUDS_REIMAGINED
        cloudAmbientColor *= 1.0 - 0.25 * rainFactor;
    #endif

    vec3 cloudColorMult = vec3(1.0);
    #if CLOUD_R != 100 || CLOUD_G != 100 || CLOUD_B != 100
        cloudColorMult *= vec3(CLOUD_R, CLOUD_G, CLOUD_B) * 0.01;
    #endif
    cloudAmbientColor *= cloudColorMult;
    cloudLightColor *= cloudColorMult;

    #if defined CLOUDS_UNBOUND
        #ifdef DOUBLE_UNBOUND_CLOUDS
            // Two visually distinct cloud layers (see cloudLayer in
            // unboundClouds.glsl): trace whichever altitude is nearer to
            // the camera first, and only trace the other one if the near
            // plane had no cloud at this pixel. Layer index is tied to
            // which altitude slot it is (ALT1 => 0/cumulus-style,
            // ALT2 => 1/wispy variant), not to which one is nearer, so
            // each altitude keeps its own consistent look.
            bool alt1Nearer = abs(cameraPos.y - cloudAlt1i) < abs(cameraPos.y - cloudAlt2i);
            int firstAlt   = alt1Nearer ? cloudAlt1i : cloudAlt2i;
            int firstLayer = alt1Nearer ? 0 : 1;
            int secondAlt   = alt1Nearer ? cloudAlt2i : cloudAlt1i;
            int secondLayer = alt1Nearer ? 1 : 0;

            clouds = GetVolumetricClouds(firstAlt, firstLayer, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                            cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
            if (clouds.a == 0.0) {
                clouds = GetVolumetricClouds(secondAlt, secondLayer, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                                cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
            }
        #else
            clouds = GetVolumetricClouds(cloudAlt1i, 0, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                            cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
        #endif
    #elif !defined DOUBLE_REIM_CLOUDS
        clouds = GetVolumetricClouds(cloudAlt1i, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                        cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
    #else
        int maxCloudAlt = max(cloudAlt1i, cloudAlt2i);
        int minCloudAlt = min(cloudAlt1i, cloudAlt2i);

        if (abs(cameraPos.y - minCloudAlt) < abs(cameraPos.y - maxCloudAlt)) {
            clouds = GetVolumetricClouds(minCloudAlt, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                            cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
            if (clouds.a == 0.0) {
            clouds = GetVolumetricClouds(maxCloudAlt, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                            cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
            }
        } else {
            clouds = GetVolumetricClouds(maxCloudAlt, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                            cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
            if (clouds.a == 0.0) {
            clouds = GetVolumetricClouds(minCloudAlt, thresholdF, cloudLinearDepth, skyFade, skyMult0,
                                            cameraPos, nPlayerPos, lViewPosM, VdotS, VdotU, dither);
            }
        }
    #endif

    #ifdef ATM_COLOR_MULTS
        clouds.rgb *= sqrtAtmColorMult; // C72380KD - Reduced atmColorMult impact on some things
    #endif
    #ifdef MOON_PHASE_INF_ATMOSPHERE
        clouds.rgb *= moonPhaseInfluence;
    #endif

    #if AURORA_STYLE > 0
        clouds.rgb += auroraBorealis * 0.1;
    #endif
    #if NIGHT_NEBULAE == 1
        clouds.rgb += nightNebula * 0.2;
    #endif

    return clouds;
}