float cloud_height = 1500.;
float cloud_height_max = cloud_height + 150;

float cloudDensity = 1;
const int maxIT_clouds = 8;

vec2 cubeSmooth(vec2 x)
{
    return x * x * (3.0 - 2.0 * x); // Smoothstep-like curve
}

float cloudTextureCubic(sampler2D tex, vec2 pos)
{
    vec2 texSize = vec2(textureSize(tex, 0)) * 2.0;
    vec2 texelSize = 1.0 / texSize;

    vec2 uv = pos * texSize - 0.5; // Sample at center of texels
    vec2 iuv = floor(uv);
    vec2 fuv = cubeSmooth(fract(uv));

    // Offsets for 2x2 block
    vec2 baseUV = (iuv + vec2(0.0, 0.0) + 0.5) * texelSize;
    vec2 rightUV = baseUV + vec2(texelSize.x, 0.0);
    vec2 upUV = baseUV + vec2(0.0, texelSize.y);
    vec2 diagUV = baseUV + texelSize;

    // Sample alpha channel only
    float a00 = texture(tex, baseUV).a;
    float a10 = texture(tex, rightUV).a;
    float a01 = texture(tex, upUV).a;
    float a11 = texture(tex, diagUV).a;

    // Bilinear blend with smoothed weights
    float a0 = mix(a00, a10, fuv.x);
    float a1 = mix(a01, a11, fuv.x);
    return mix(a0, a1, fuv.y);
}

float getCloudDensity(in vec3 pos, float dummy, vec2 wind)
{

    if (rainStrength >= 1.0)
        return 1.0;

    float timeOffset = frameTimeCounter * 24.0;
    pos.x += timeOffset;

    // Pre-scale position

    vec3 samplePos = pos * 0.25;

    vec2 sampleCoord = samplePos.xz / CloudSize;

    // Precomputed cloud height bounds
    float baseHeight = cloud_height + 100.0;
    float topHeight = cloud_height + 450.0;
    float bottomHeight = cloud_height + 125.0;

    // Vertical fade factors
    float heightAboveBase = max(pos.y - baseHeight, 0.0);
    float belowTop = max(topHeight - pos.y, 0.0);
    float belowBottom = min(bottomHeight - pos.y, 0.0);

    // Fade multipliers
    float fadeTop = heightAboveBase / 500.0;
    float fadeBottom = belowTop / 500.0;
    float boostLower = belowBottom / 100.0;

    // Sample cloud texture with smoothed bilinear
#ifdef DH
    float texVal = cloudTextureCubic(depthtex0, sampleCoord) * 2.0;
#else
    float texVal = cloudTextureCubic(depthtex1, sampleCoord) * 2.0;
#endif

    // Combine texture contribution and rain strength
    float coverage = (texVal + rainStrength) / (0.8 * (1.0 - rainStrength));
    coverage = clamp(coverage, 0.0, 1.0);

    // Final cloud density
    return max(coverage - fadeTop - fadeBottom + boostLower, 0.0);
}

// ——————————————————————————————————————————————————————————
// Optimized cloud renderer with fog‑cull
// ——————————————————————————————————————————————————————————
vec4 renderClouds(float dither, vec3 view, vec3 fogC, bool usefade)
{
    if (rainStrength >= 1.0)
        return vec4(fogC, 0.0);
    // ——————————————————————————————————————————————————————————
    // File‑scope (compile‑time) constants
    // ——————————————————————————————————————————————————————————
    const float CDENSITY = CloudDensity * 10.0;
    const float LN2_INV = 1.442695;                               // 1/ln(2) for exp2 approximation
    const float CULL_X = 6.64;                                    // exp2(-6.64)≈0.01 → 1% visibility cutoff
    float INV_FOG_DIST = 1.0 / mix(8000, 3000.0, float(usefade)); // controls fog falloff distance

    // 1) Dynamic step count based on view tilt
    float sqrtVy = sqrt(max(view.y, 1e-6));
    int steps = clamp(int(float(maxIT_clouds) / sqrtVy), 1, maxIT_clouds);
    float invSteps = 1.0 / float(steps);

    // 2) World‐space ray origin + cloud‐layer check
    vec3 worldPos = view * 300000.0 + cameraPosition;
    if (worldPos.y < 300000.0 - cloud_height * 200.0)
        return vec4(0.0);

    // 3) Precompute step vector & length once
    vec3 stepFull = (view * 150.0) / (view.y * float(steps));
    float stepLen = length(stepFull);
    vec3 stepDelta = stepFull * 0.4;

    // 4) Compute entry point into clouds
    float entryT = (cloud_height - cameraPosition.y) / stepFull.y;
    vec3 samplePos = cameraPosition + stepFull * entryT;

    // 5) Lighting setup (unchanged)
    mat3 VM = mat3(gbufferModelView);
    float sdv = dot(sunVec, VM * view);
    float ph1 = phaseg(sdv, cloudMieG);
    float ph2 = phaseg(sdv, cloudMieG2) * 0.7;
    float mieP = max(ph1, ph2);
    vec3 sunCol = mieP * sunLight * PI;
    float ambF = exp(-(0.8 * rainStrength + 1.24) * CDENSITY * 5.0);
    vec3 ambL = atmosphereUp * ambF;
    vec3 lighting = sunCol + ambL;

    // 6) Fog params & early‐exit for flat/horizon rays
    float normVy = normalize(stepFull).y;
    float fogDist = (cloud_height - cameraPosition.y) / normVy;
    if (fogDist > 30000.0 && abs(normVy) < 0.1)
        return vec4(fogC, 0.0);

    // 7) Early‑cull based on fog alone
    float rainS = 1.0 + rainStrength * 2.0;
    float x = fogDist * INV_FOG_DIST * rainS * LN2_INV;
    if (x > CULL_X)
        return vec4(fogC, 0.0);

    // 8) Ray‑march: incremental gradient & exp2 extinction
    vec3 col = vec3(0.0);
    float trans = 1.0;
    float grad = 0.5;             // starts at mix(0.5,1.0,0)
    float gStep = 0.5 * invSteps; // per‐step gradient increment

    for (int i = 0; i < steps && trans > 1e-4; ++i, grad += gStep)
    {
        float dens = getCloudDensity(samplePos, 1.0, vec2(0.0)) * CDENSITY;
        // approximate exp(-dens*stepLen) with exp2
        float ext = exp2(-dens * stepLen * LN2_INV);
        float scat = (1.0 - ext) * (1.0 - ext) * grad;
        col += lighting * scat * trans;
        trans *= ext;
        samplePos += stepDelta;
    }

    // 9) Final fog blend
    float fogF = clamp(exp2(-x), 0.0, 1.0);

    return mix(vec4(fogC, 0.0), vec4(col, clamp(trans, 0.0, 1.0)), fogF);
}
