/*
====================================================================
  SXGG: Realis — Shader ultra-realistica per Minecraft
  Autore : Samax.GG
  File   : lib/clouds.glsl
  Modulo : 4 — Nuvole volumetriche raymarched + nebbia volumetrica.

  Le nuvole vivono in uno strato [CLOUD_BOTTOM, CLOUD_TOP] world-Y.
  Densità = FBM 3D animato - copertura; luce = march secondario
  verso il sole (auto-ombreggiamento del volume).

  Richiede: lib/common.glsl, lib/noise.glsl, lib/settings.glsl
====================================================================
*/

#ifndef CLOUDS_GLSL
#define CLOUDS_GLSL

const float CLOUD_BOTTOM = 150.0;
const float CLOUD_TOP    = 210.0;

float cloudDensity(vec3 p, float time, float rain) {
    // Le nuvole scorrono col vento e si deformano lentamente
    vec3 q = p * 0.0055;
    q.xz += time * 0.010;
    q.x  += time * 0.004;

    float base = fbm3D(q);
    // Dettaglio ad alta frequenza eroso dai bordi
    float detail = fbm3D(q * 3.7 + vec3(time * 0.02, 0.0, 0.0));

    float coverage = CLOUD_COVERAGE + rain * 0.30;
    float density = base - (1.0 - coverage) - detail * 0.18;

    // Profilo verticale: piatte sotto, soffici sopra
    float h = saturate((p.y - CLOUD_BOTTOM) / (CLOUD_TOP - CLOUD_BOTTOM));
    density *= smoothstep(0.0, 0.18, h) * smoothstep(1.0, 0.55, h);

    return saturate(density * 2.2);
}

// March secondario corto verso il sole: quanta luce arriva nel punto
float cloudLightMarch(vec3 p, vec3 sunDir, float time, float rain) {
    float depth = 0.0;
    float stepLen = (CLOUD_TOP - CLOUD_BOTTOM) / 4.0;
    for (int i = 1; i <= 3; i++) {
        depth += cloudDensity(p + sunDir * stepLen * float(i), time, rain);
    }
    return exp(-depth * 1.4);
}

/*
  Raymarch delle nuvole. worldDir normalizzata, camPos world.
  sceneDist: distanza della geometria (per non disegnare nuvole
  davanti alle montagne). Ritorna rgb premoltiplicato + alpha.
*/
vec4 volumetricClouds(vec3 camPos, vec3 worldDir, vec3 sunDir,
                      float sceneDist, float time, float rain,
                      vec3 skyLight, vec3 sunLight, float dither) {
    // Interseca lo strato
    if (abs(worldDir.y) < 0.01) return vec4(0.0);
    float t0 = (CLOUD_BOTTOM - camPos.y) / worldDir.y;
    float t1 = (CLOUD_TOP    - camPos.y) / worldDir.y;
    float tNear = min(t0, t1);
    float tFar  = max(t0, t1);
    tNear = max(tNear, 0.0);
    tFar  = min(tFar, sceneDist);
    if (tFar <= tNear || tNear > 6000.0) return vec4(0.0);

    float stepLen = (tFar - tNear) / float(CLOUD_STEPS);
    float t = tNear + stepLen * dither;

    vec3  scattered    = vec3(0.0);
    float transmittance = 1.0;
    float cosTheta = dot(worldDir, sunDir);
    float phase = miePhase(cosTheta, 0.45) * 0.8 + 0.15;   // forward + ambient

    for (int i = 0; i < CLOUD_STEPS; i++) {
        vec3 p = camPos + worldDir * t;
        float density = cloudDensity(p, time, rain);

        if (density > 0.01) {
            float light = cloudLightMarch(p, sunDir, time, rain);
            // Powder effect: bordi scuri quando guardi verso la luce
            float powder = 1.0 - exp(-density * 4.0);

            vec3 luminanceStep = sunLight * light * phase * powder
                               + skyLight * 0.25;

            float absorb = exp(-density * stepLen * 0.045);
            scattered += luminanceStep * transmittance * (1.0 - absorb);
            transmittance *= absorb;
            if (transmittance < 0.02) break;
        }
        t += stepLen;
    }

    // Fade con la distanza orizzontale (le nuvole svaniscono all'orizzonte)
    float horizonFade = exp(-tNear * 0.0004);
    return vec4(scattered, (1.0 - transmittance) * horizonFade);
}

/*
  Ombra delle nuvole sul terreno: campiona la densità sopra il punto.
  Ritorna 0.35 (piena ombra nuvola) .. 1.0.
*/
float cloudShadow(vec3 worldPos, vec3 sunDir, float time, float rain) {
    #ifndef CLOUD_SHADOWS
        return 1.0;
    #endif
    if (sunDir.y < 0.05) return 1.0;
    // Proietta il punto sullo strato nuvole lungo la direzione del sole
    float t = ((CLOUD_BOTTOM + CLOUD_TOP) * 0.5 - worldPos.y) / sunDir.y;
    if (t < 0.0) return 1.0;
    vec3 p = worldPos + sunDir * t;
    float density = cloudDensity(p, time, rain);
    return mix(1.0, 0.35, smoothstep(0.05, 0.5, density));
}

/*
  Nebbia volumetrica: densità esponenziale con l'altezza + FBM che
  "riempie le valli". Integrata analiticamente lungo la vista con
  modulazione noise a 3 campioni (economica ma volumetrica).
*/
vec3 volumetricFog(vec3 color, vec3 camPos, vec3 worldPos, vec3 sunDir,
                   float time, float rain, vec3 skyColor, vec3 sunLight) {
    #ifndef VOLUMETRIC_FOG
        return color;
    #endif

    vec3  ray  = worldPos - camPos;
    float dist = length(ray);
    vec3  dir  = ray / max(dist, EPSILON);

    // Densità di base: più fitta in basso, più fitta con pioggia/alba
    float sunLow = 1.0 - saturate(abs(sunDir.y) * 3.0);   // alba/tramonto
    float baseDensity = 0.0012 * FOG_DENSITY
                      * (1.0 + rain * 3.0 + sunLow * 1.2);

    // Height falloff integrato analiticamente
    float heightFactor = exp(-(camPos.y - 64.0) * 0.015);
    float fogAmount = baseDensity * heightFactor
                    * (1.0 - exp(-dist * dir.y * 0.015)) / max(dir.y * 0.015, EPSILON);
    if (abs(dir.y) < 0.01)
        fogAmount = baseDensity * heightFactor * dist;

    // Modulazione volumetrica: 3 campioni FBM lungo il raggio
    float noiseMod = 0.0;
    for (int i = 1; i <= 3; i++) {
        vec3 p = camPos + dir * dist * (float(i) / 3.5);
        noiseMod += fbm2D(p.xz * 0.015 + time * 0.01) * exp(-(p.y - 64.0) * 0.02);
    }
    fogAmount *= 0.55 + saturate(noiseMod / 3.0) * 0.9;
    fogAmount = 1.0 - exp(-fogAmount);
    fogAmount = saturate(fogAmount);

    // Colore: cielo + inscattering solare (la nebbia "brilla" verso il sole)
    float sunAmount = pow(saturate(dot(dir, sunDir)), 8.0);
    vec3 fogColor = skyColor * 0.85 + sunLight * sunAmount * 0.25;

    return mix(color, fogColor, fogAmount);
}

#endif // CLOUDS_GLSL
