/*
 * Visuality fluffy curved clouds
 *
 * A lightweight 2D cloud layer: three noise taps for the shape and one
 * directional tap for fake sunlight. The layer is intersected as a gently
 * curved shell instead of an infinite flat plane, so the distant cloud bank
 * falls toward the horizon and the sky feels rounder. No volumetric ray march.
 */

#define CLOUD_QUALITY 0 //[0 1]
#define CLOUD_COVERAGE 0.68 //[0.35 0.45 0.55 0.62 0.68 0.74 0.80 0.85]
#define CLOUD_DENSITY 1.00 //[0.65 0.80 0.95 1.00 1.15 1.30 1.50]
#define CLOUD_SPEED 1.00 //[0.00 0.25 0.50 0.75 1.00 1.25 1.50 2.00]
#define CLOUD_SUNSET_GLOW 1.25 //[0.50 0.75 1.00 1.25 1.50 1.75 2.00]
#define CLOUD_FLUFFINESS 1.15 //[0.50 0.70 0.85 1.00 1.15 1.30 1.50]
#define CLOUD_HORIZON_CURVE 1.00 //[0.00 0.25 0.50 0.75 1.00 1.25 1.50]
#define CLOUD_SELF_SHADOW 0.82 //[0.00 0.20 0.40 0.60 0.75 0.82 0.90 1.00]
#define CLOUD_TOP_LIGHT 1.12 //[0.60 0.80 1.00 1.12 1.25 1.40 1.60]
#define CLOUD_UNDERSIDE_DARKNESS 0.74 //[0.30 0.45 0.60 0.74 0.85 1.00]
#define CLOUD_DAY_BRIGHTNESS 0.70 //[0.55 0.60 0.65 0.70 0.75 0.80 0.90 1.00]
#define CLOUD_TWILIGHT_BRIGHTNESS 0.88 //[0.65 0.70 0.75 0.80 0.85 0.88 0.92 1.00]
#define CLOUD_NIGHT_BRIGHTNESS 1.35 //[0.85 1.00 1.15 1.25 1.35 1.50 1.70 1.90]

const float CLOUD_HEIGHT = 1900.0;
const float CLOUD_SCALE  = 0.000025;

/* Rounded cellular noise from the red channel makes sheep-wool-like lobes. */
float cloudShape(vec2 uv, vec2 windUV) {
    mat2 cloudRotation = mat2(0.8870, -0.4617,
                                    0.4617,  0.8870);

    float broad = texture2D(noisetex,
        uv * vec2(0.55, 0.72) + vec2(0.071, 0.193)).r;

    vec2 rotatedUV = cloudRotation * uv;
    float lobes = texture2D(noisetex,
        rotatedUV * vec2(1.45, 1.80) + windUV * 0.22
        + vec2(0.413, 0.127)).r;

    float wool = texture2D(noisetex,
        uv * vec2(3.40, 4.60) - windUV * 0.16
        + vec2(0.237, 0.681)).r;

    float shape = broad * 0.56 + lobes * 0.31 + wool * 0.13;

    /* Raise isolated round lobes so the outline is puffy rather than smeared. */
    shape += max(lobes - broad * 0.72, 0.0)
           * (0.070 * CLOUD_FLUFFINESS);

#if CLOUD_QUALITY == 1
    float fineWool = texture2D(noisetex,
        rotatedUV * vec2(7.20, 9.10) + windUV * 0.11
        + vec2(0.611, 0.347)).r;
    shape += (fineWool - 0.24) * 0.040;
#endif

    return shape;
}

/*
 * Intersect a ray with a parabolic approximation of a spherical cloud shell.
 * CLOUD_HORIZON_CURVE 0 is almost flat; larger values bend the distant layer
 * downward more strongly. This costs only a square root per sky pixel.
 */
float curvedCloudDistance(vec3 rayDir, float layerHeight, out float shellDrop) {
    if (CLOUD_HORIZON_CURVE <= 0.001) {
        shellDrop = 0.0;
        if (rayDir.y <= 0.004) return -1.0;
        return layerHeight / rayDir.y;
    }

    float curve = max(CLOUD_HORIZON_CURVE, 0.02);
    float fakeRadius = 48000.0 / curve;
    float horizontal2 = dot(rayDir.xz, rayDir.xz);
    float a = horizontal2 / (2.0 * fakeRadius);
    float b = rayDir.y;

    shellDrop = 0.0;

    if (a < 0.0000001) {
        if (b <= 0.0001) return -1.0;
        return layerHeight / b;
    }

    float discriminant = b * b + 4.0 * a * layerHeight;
    float distanceToLayer = (-b + sqrt(max(discriminant, 0.0))) / (2.0 * a);
    float horizontalDistance = sqrt(horizontal2) * distanceToLayer;
    shellDrop = horizontalDistance * horizontalDistance / (2.0 * fakeRadius);

    return distanceToLayer;
}

vec3 cloud2D(vec3 fragpos, vec3 col) {
    vec3 rayDir = normalize(fragpos);

    if (cameraPosition.y >= CLOUD_HEIGHT) return col;
    if (CLOUD_HORIZON_CURVE < 0.05 && rayDir.y <= 0.004) return col;
    if (rayDir.y <= -0.10) return col;

    float layerHeight = CLOUD_HEIGHT - cameraPosition.y;
    float shellDrop;
    float layerDistance = curvedCloudDistance(rayDir, layerHeight, shellDrop);
    if (layerDistance <= 0.0) return col;
    layerDistance = min(layerDistance, 180000.0);

    vec2 windWorld = vec2(frameTimeCounter * 18.0,
                          frameTimeCounter * 5.0) * CLOUD_SPEED;
    vec2 windUV = windWorld * CLOUD_SCALE;

    vec2 cloudWorld = cameraPosition.xz
                    + rayDir.xz * layerDistance
                    + windWorld;
    vec2 uv = cloudWorld * CLOUD_SCALE;

    float shape = cloudShape(uv, windUV);

    /* Larger coverage lowers the threshold and fills more of the sky. */
    float threshold = 0.350 - CLOUD_COVERAGE * 0.205;
    float rainBuild = smoothstep(0.02, 0.92, rainStrength);
    threshold -= rainBuild * 0.075;

    float fluffAmount = clamp((CLOUD_FLUFFINESS - 0.50) / 1.00, 0.0, 1.0);
    float edgeWidth = mix(0.110, 0.178, fluffAmount);
    float cloudMask = smoothstep(threshold, threshold + edgeWidth, shape);
    float coreMask = smoothstep(threshold + edgeWidth * 0.30,
                                threshold + edgeWidth * 0.98,
                                shape);
    float woolEdge = max(cloudMask - coreMask, 0.0);

    vec3 sunDir = normalize(sunVec);
    vec2 sunXZ = sunDir.xz;
    float sunXZLength = sqrt(max(dot(sunXZ, sunXZ), 0.0001));
    vec2 sunPlanar = sunXZ / sunXZLength;
    float sunHeight = clamp(sunDir.y, 0.0, 1.0);
    float sunVisible = smoothstep(-0.12, 0.08, sunDir.y);
    float moonVisible = smoothstep(-0.12, 0.08, -sunDir.y);

    /*
     * Dynamic cloud self-shadowing. The offset follows the horizontal sun
     * direction, while the reach grows as the sun gets lower. This makes the
     * darker side of the cloud bank move naturally from morning to noon to
     * evening instead of staying fixed in one direction.
     */
    float lowSunShadowReach = 1.0 / max(sunHeight, 0.10);
    float lightOffset = clamp(0.008 + lowSunShadowReach * 0.010, 0.008, 0.115);
    float towardSunNoise = texture2D(noisetex,
        (uv + sunPlanar * lightOffset) * vec2(0.55, 0.72)
        + vec2(0.071, 0.193)).r;
    float towardSunShape = towardSunNoise * 0.72 + shape * 0.28;
    float towardSunMask = smoothstep(threshold + 0.004,
                                     threshold + edgeWidth,
                                     towardSunShape);

    /*
     * A dense lobe between the current fragment and the sun blocks direct
     * light. The shadow strength rises when the sun is high, while the
     * longer low-sun offset creates the stretched, directional evening look.
     */
    float selfShadow = towardSunMask
                     * CLOUD_SELF_SHADOW
                     * sunVisible
                     * (0.42 + sunHeight * 0.58);
    float sunAccess = clamp(1.0 - selfShadow, 0.0, 1.0);
    float litEdge = max(cloudMask - towardSunMask, 0.0);

    float daytime = sunVisible;
    float lowSun = (1.0 - smoothstep(0.075, 0.42, sunHeight)) * daytime;
    float sunsetAmount = clamp(lowSun * CLOUD_SUNSET_GLOW, 0.0, 1.35);
    vec3 sunsetTint = mix(vec3(1.0), vec3(1.22, 0.61, 0.25), sunsetAmount);

    float viewSun = max(dot(rayDir, sunDir), 0.0);
    float forwardScatter = pow(viewSun, 6.0);

    /*
     * Side-aware lighting: when the camera is below the cloud deck we see the
     * underside, so the direct sun contribution is deliberately suppressed.
     * When the camera is above the deck, the top surface receives the sun and
     * becomes brighter. This keeps the cloud tops bright at noon and the
     * underside dark, matching the natural light direction.
     */
    float cameraAboveClouds = step(CLOUD_HEIGHT + 18.0, cameraPosition.y);
    float topFacingLight = mix(0.66, CLOUD_TOP_LIGHT, sunHeight) * sunVisible;
    float undersideDark = mix(1.0, 1.0 - CLOUD_UNDERSIDE_DARKNESS * sunHeight,
                              1.0 - cameraAboveClouds);
    float moonFill = moonVisible * (0.58 + 0.30 * (1.0 - rainStrength));

    vec3 darkCloud = mix(col * 0.55, cloudCol * 0.105, 0.70);
    vec3 brightCloud = cloudCol * sunsetTint
                     * (0.48 + sunAccess * 0.64)
                     * max(topFacingLight, 0.20);
    brightCloud = mix(brightCloud,
                      cloudCol * vec3(0.55, 0.63, 0.78) * moonFill,
                      moonVisible * 0.32);

    vec3 cloudColor = mix(darkCloud, brightCloud, 0.24 + sunAccess * 0.62);
    cloudColor *= undersideDark;

    /* Soft white wool rim plus warmer sun-facing silver lining. */
    cloudColor += cloudCol * sunsetTint * woolEdge
                * (0.10 + fluffAmount * 0.15)
                * (0.60 + topFacingLight * 0.45);
    cloudColor += cloudCol * sunsetTint * litEdge
                * (0.24 + forwardScatter * 1.05 + lowSun * 0.26)
                * (0.70 + topFacingLight * 0.35);

    /*
     * Strengthen the underside shadow when viewed from below. The horizon
     * curve still darkens distant cloud banks gently, but no longer flattens
     * the whole cloud layer into one uniform shade.
     */
    float curvedDistance = clamp(shellDrop / max(layerHeight, 1.0), 0.0, 1.0);
    float undersideView = 1.0 - cameraAboveClouds;
    cloudColor *= 1.0
                - coreMask * curvedDistance * (0.16 + 0.12 * undersideView)
                - selfShadow * (0.10 + 0.18 * undersideView);

    float luminance = dot(cloudColor, vec3(0.2126, 0.7152, 0.0722));
    cloudColor = mix(cloudColor,
                     vec3(luminance) * vec3(0.68, 0.76, 0.86),
                     rainStrength * 0.62);

    // Independent HDR control for cloud light: high noon is intentionally
    // softer than morning/evening so cloud tops keep shape instead of clipping.
    float dayWeight = smoothstep(0.22, 0.62, sunDir.y);
    float nightWeight = 1.0 - smoothstep(-0.12, 0.10, sunDir.y);
    float twilightWeight = clamp(1.0 - dayWeight - nightWeight, 0.0, 1.0);
    float cloudPhaseBrightness = dayWeight * CLOUD_DAY_BRIGHTNESS
                              + twilightWeight * CLOUD_TWILIGHT_BRIGHTNESS
                              + nightWeight * CLOUD_NIGHT_BRIGHTNESS;
    cloudColor *= cloudPhaseBrightness;

    float opacity = clamp(cloudMask * CLOUD_DENSITY, 0.0, 0.95);

    /* Keep a visible curved cloud bank right down to the horizon. */
    opacity *= smoothstep(-0.020, 0.008, rayDir.y);
    opacity *= 1.0 - smoothstep(90000.0, 170000.0, layerDistance);

    return mix(col, cloudColor, opacity);
}
