/*
 * clouds.glsl -- Volumetric cloud raymarcher
 *
 * Adapted from Escheridia V4.1 by jrwilliamson (MIT License, 2025)
 * Source: lib/atmosphere/clouds.glsl  (sampleDensity, cloudRaymarch, remap)
 *         shaders/deferred4.fsh       (call-site pattern)
 */

#ifndef CLOUDS_GLSL
#define CLOUDS_GLSL

#define PI 3.14159265358979

// -- Phase function (Escheridia: lib/phaseFunctions.glsl)
float henyeyGreenstein(float cosTheta, float g) {
    float g2 = g * g;
    return (1.0 - g2) / (PI * 4.0 * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));
}

// -- World-space light direction (Escheridia: lib/uniforms.glsl worldLightVector)
vec3 worldLightDir() {
    return normalize(mat3(gbufferModelViewInverse) * normalize(shadowLightPosition));
}

// -- Sun / moon colour --
// Replaces Escheridia's currentSunColor().
vec3 sunColor() {
    // Intensity scalar: transitions cleanly from sun to moon, no overlap
    float nightW = clamp(smoothstep(0.44, 0.52, sunAngle)
                    - smoothstep(0.92, 1.00, sunAngle), 0.0, 1.0);
    #if CLOUD_STYLE == 0
        // Rain dims both sun and moon light reaching the clouds independently.
        float sunIntensity  = mix(CLOUD_SUN_INTENSITY,  RAIN_CLOUD_SUN_INTENSITY,  rainStrength);
        float moonIntensity = mix(CLOUD_MOON_INTENSITY / 10, RAIN_CLOUD_MOON_INTENSITY, rainStrength);
        float intensity   = mix(sunIntensity, moonIntensity, nightW);
    #else
        // Rain dims both sun and moon light reaching the clouds independently.
        float sunIntensity  = mix(CLOUD_SUN_INTENSITY,  RAIN_CLOUD_SUN_INTENSITY/6,  rainStrength);
        float moonIntensity = mix(CLOUD_MOON_INTENSITY / 10, RAIN_CLOUD_MOON_INTENSITY/6, rainStrength);
        float intensity   = mix(sunIntensity, moonIntensity, nightW);
    #endif

    // Hue weights
    float sunriseW = clamp(1.0 - abs(sunAngle - 0.0) / 0.06, 0.0, 1.0)
                   + clamp(1.0 - abs(sunAngle - 0.5) / 0.06, 0.0, 1.0);
    sunriseW = clamp(sunriseW, 0.0, 1.0);

    vec3 noonCol   = vec3(0.95, 0.90, 0.78);
    vec3 sunsetCol = vec3(SKY_SUNSET_R, SKY_SUNSET_G, SKY_SUNSET_B) * CLOUD_SUNSET_TINT;
    vec3 moonHue   = vec3(0.55, 0.65, 1.00);

    vec3 col = mix(noonCol, sunsetCol, sunriseW * 0.85);
    col      = mix(col, moonHue, nightW);

    // Horizon fade:
    // Sun fades in 0->0.03 and out 0.47->0.50; moon mirrors on the night arc.
    float sunFade  = smoothstep(0.0, 0.03, sunAngle) * (1.0 - smoothstep(0.47, 0.50, sunAngle));
    float moonFade = smoothstep(0.50, 0.53, sunAngle) * (1.0 - smoothstep(0.97, 1.00, sunAngle));
    float horizonFade = mix(sunFade, moonFade, nightW);

    return col * intensity * horizonFade;
}

// -- Ambient sky colour --
vec3 skyAmbient() {
    return customHorizonFogColor(sunAngle, vec3(0.0), rainStrength) * 0.18;
}

// -- Per-frame stable noise offset --
// IGN + integer frameCounter. Replaces Escheridia's STBN blue_noise().
float cloudNoise(vec2 fragCoord) {
    // Static IGN -- frameCounter removed. The golden-ratio temporal offset only
    // helps with TAA; without TAA it just makes the dither crawl every frame.
    return fract(52.9829189 * fract(dot(fragCoord, vec2(0.06711056, 0.00583715))));
}

// -- remap() -- verbatim from Escheridia clouds.glsl
// Credit: Escheridia V4.1 -- jrwilliamson (MIT 2025)
float remap(float value, float origMin, float origMax, float newMin, float newMax) {
    return newMin + (((value - origMin) / (origMax - origMin)) * (newMax - newMin));
}

// -- sampleDensity() -- adapted from Escheridia clouds.glsl
// Credit: Escheridia V4.1 -- jrwilliamson (MIT 2025)
float sampleDensity(vec3 pos) {
    // Early out before any texture reads
    #if CLOUD_STYLE == 0
        if (pos.y > float(CLOUD_PLANE_TOP) + float(CLOUD_THICKNESS)) return 0.0;
        if (pos.y < float(CLOUD_PLANE_BOTTOM)) return 0.0;
    #else
        if (pos.y > 180.0 + float(CLOUD_THICKNESS)) return 0.0;
        if (pos.y < 171.0) return 0.0;
    #endif

    float height = 0.0;
    #if CLOUD_STYLE == 0
        height = smoothstep(float(CLOUD_PLANE_BOTTOM), float(CLOUD_PLANE_TOP), pos.y);
    #else
        height = smoothstep(170.0, 210.0, pos.y);
    #endif

    vec3 uvw     = pos * CLOUD_NOISE_SCALE * 0.0001 + 0.1;
    uvw.x += frameTimeCounter * CLOUD_SCROLL_SPEED / 1000; //scroll clouds
    vec4 shape   = vec4(0.0);
    vec4 detail1 = texture2D(fogTex, uvw.xz);
    vec4 detail2 = texture2D(detail, uvw.xz);

    #if CLOUD_STYLE == 0
        shape = texture2D(clouds, uvw.xz);

        // Rain coverage: OR in shifted copies of the cloud texture, weighted by rainStrength.
        float r1 = texture2D(clouds, uvw.xz + vec2(0.25,  0.25 )).r; // step 1: +64,64
        float r2 = texture2D(clouds, uvw.xz + vec2(0.50,  0.50 )).r; // step 2: +128,128
        float r3 = texture2D(clouds, uvw.xz + vec2(0.75,  0.25 )).r; // step 3: +192,64
        float r4 = texture2D(clouds, uvw.xz + vec2(0.25,  0.75 )).r; // step 4: +64,192

        // Stagger each copy so they blend in progressively across 0->1 rainStrength
        float t1 = clamp(rainStrength * 4.0 - 0.0, 0.0, 1.0); // starts at rain=0.00
        float t2 = clamp(rainStrength * 4.0 - 1.0, 0.0, 1.0); // starts at rain=0.25
        float t3 = clamp(rainStrength * 4.0 - 2.0, 0.0, 1.0); // starts at rain=0.50
        float t4 = clamp(rainStrength * 4.0 - 3.0, 0.0, 1.0); // starts at rain=0.75

        shape.r = mix(shape.r, max(shape.r, r1), t1);
        shape.r = mix(shape.r, max(shape.r, r2), t2);
        shape.r = mix(shape.r, max(shape.r, r3), t3);
        shape.r = mix(shape.r, max(shape.r, r4), t4);
    #else
        shape = texture2D(cloudBase, uvw.xz);

        // Rain coverage: sample cloudBase at shifted UVs to fill gaps, same stagger as style 0.
        float rb1 = texture2D(cloudBase, uvw.xz + vec2(0.25, 0.25)).r;
        float rb2 = texture2D(cloudBase, uvw.xz + vec2(0.50, 0.50)).r;
        float rb3 = texture2D(cloudBase, uvw.xz + vec2(0.75, 0.25)).r;
        float rb4 = texture2D(cloudBase, uvw.xz + vec2(0.25, 0.75)).r;

        float tb1 = clamp(rainStrength * 4.0 - 0.0, 0.0, 1.0);
        float tb2 = clamp(rainStrength * 4.0 - 1.0, 0.0, 1.0);
        float tb3 = clamp(rainStrength * 4.0 - 2.0, 0.0, 1.0);
        float tb4 = clamp(rainStrength * 4.0 - 3.0, 0.0, 1.0);

        shape.r = mix(shape.r, max(shape.r, rb1), tb1);
        shape.r = mix(shape.r, max(shape.r, rb2), tb2);
        shape.r = mix(shape.r, max(shape.r, rb3), tb3);
        shape.r = mix(shape.r, max(shape.r, rb4), tb4);

        shape.r = remap(shape.r, 1.0 - detail1.r, 1.0, 0.0, 1.0)
                + remap(shape.r, 1.0 - detail2.r, 1.0, 0.0, 3.0);
    #endif

    #if CLOUD_STYLE == 1
        float effectiveThreshold = CLOUD_DENSITY_THRESHOLD - rainStrength * 8.5;
    #else
        float effectiveThreshold = CLOUD_DENSITY_THRESHOLD;
    #endif
        float density = max(0.0, shape.r - effectiveThreshold) * CLOUD_DENSITY;
    #if CLOUD_STYLE == 0
        density *= 1.1;
    #else
        density *= 7.0;
    #endif
        return density * height;
}

// -- cloudRaymarch() -- adapted from Escheridia clouds.glsl
// Credit: Escheridia V4.1 -- jrwilliamson (MIT 2025)
vec4 cloudRaymarch(vec2 fragCoord, vec3 worldPos, vec3 sceneColor) {
    #if CLOUDS != 1
        // CLOUDS == 0 (Off) or CLOUDS == 2 (Vanilla): no volumetric/shader clouds.
        // This guarantees vanilla clouds and shader clouds never render simultaneously.
        return vec4(sceneColor, 0.0);
    #endif

    const float uniformPhase        = 1.0 / (4.0 * PI);
    const float _StepSize           = 8.4;
    const float _NoiseOffset        = 16.65;
    const float MULTI_SCATTER_GAIN  = 15.09;
    const float MULTI_SCATTER_DECAY = 0.93;
    const float liningIntensity     = 1.0;
    const float liningSpread        = 0.426;

    vec3  viewDir    = worldPos - cameraPosition;
    vec3  eyePos     = viewDir - gbufferModelViewInverse[3].xyz;
    float viewLength = length(eyePos);
    vec3  rayDir     = normalize(eyePos);

    float farPlane  = far * 4.0;
    float distLimit = 0.0;

    #if CLOUD_DISTANCE_TYPE == 1
        distLimit = min(viewLength, farPlane);
    #else
        distLimit = min(viewLength, float(CLOUD_DISTANCE));
    #endif

    float distTravelled = cloudNoise(fragCoord) * _NoiseOffset;

    vec3  lightDir = worldLightDir();
    float cosTheta = dot(rayDir, lightDir);

    float transmittance      = 1.0;
    vec3  transmission       = vec3(1.0);
    vec3  sunCol             = sunColor();
    // fogCol is the ambient sky colour filling the cloud interior.
    vec3 fogCol = max(skyAmbient() * CLOUD_AMBIENT_INTENSITY, vec3(0.06, 0.07, 0.10));
	// if you want no minimum brightness: vec3 fogCol = skyAmbient() * CLOUD_AMBIENT_INTENSITY;
    vec3  multiScatterEnergy = vec3(0.0);

    while (distTravelled < distLimit) {
        
        vec3  rayPos  = cameraPosition + rayDir * distTravelled;
        float density = sampleDensity(rayPos);

        if (transmittance < 0.01) break;

        if (density > 0.0) {
            transmission *= exp(-vec3(1.0) * density * _StepSize);

            float phase = henyeyGreenstein(cosTheta,  0.75)
                        + henyeyGreenstein(cosTheta, -0.45);
            float silverLining = max(
                henyeyGreenstein(cosTheta, 0.65),
                liningIntensity * henyeyGreenstein(cosTheta, 0.99 - liningSpread)
            );
            phase += silverLining;

            float scatter    = density * _StepSize * transmittance;
            float energy     = exp(-density) * phase;
            float msFactor   = clamp(1.0 - transmittance, 0.0, 1.0);
            float msPhase    = mix(energy, uniformPhase, msFactor);
            vec3  powder     = clamp(1.0 - exp(-density * 2.0 * vec3(1.0)), 0.0, 1.0);
            float scattering = density;

            vec3 singleScatter = energy * scatter * sunCol;
            multiScatterEnergy += singleScatter * MULTI_SCATTER_GAIN * density;
            multiScatterEnergy *= MULTI_SCATTER_DECAY;

            vec3 multiScatter = multiScatterEnergy * msPhase * scatter
                              * mix(2.0 * powder, vec3(1.0), cosTheta * 0.5 + 0.5);

            float extinctionCoeff     = 3.3 * density;
            vec3  sampleExtinction    = (fogCol + vec3(1.0)) * extinctionCoeff;
            float sampleTransmittance = exp(-_StepSize);

            vec3 totalInscatter = (singleScatter + multiScatter) * scattering;
            fogCol      += (totalInscatter - totalInscatter * sampleTransmittance) / sampleExtinction;
            transmission *= sampleTransmittance;
            transmittance *= exp(-density * _StepSize);
        }

        distTravelled += _StepSize;
    }
	float cloudAlpha = 1.0 - clamp(transmittance, 0.0, 1.0);

		vec3 result = mix(sceneColor, fogCol + transmission, cloudAlpha);
		// Return opacity in .a for godray occlusion buffer
		return vec4(result, cloudAlpha);
	}
#endif // CLOUDS_GLSL
