#define CLOUD_CUMULUS_CLEAR_ALTITUDE	1000 // [60 80 100 150 200 300 356 400 500 600 700 800 1000 1200 1500 2000 5000 10000]
#define CLOUD_CUMULUS_CLEAR_THICKNESS	1400 // [0 100 200 300 400 500 550 600 700 800 1000 1200 1400 1500 1800 2000 3000 5000 6000]
#define CLOUD_CUMULUS_CLEAR_COVERY		1.0  // [0.5 0.7 0.8 0.9 1.0 1.1 1.15 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.3 2.5 2.7 3.0]
#define CLOUD_CUMULUS_CLEAR_DENSITY 	1.2  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_CUMULUS_CLEAR_SUNLIGHTING	1.0  // [0.1 0.3 0.35 0.4 0.45 0.5 0.7 0.9 1.0 1.1 1.3 1.5 1.7 1.9 2.1 2.3 2.5]
#define CLOUD_CUMULUS_CLEAR_SKYLIGHTING	1.0	 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]

#define CLOUD_CUMULUS_RAIN_ALTITUDE		800  // [60 80 100 150 200 300 356 400 500 600 700 800 1000 1200 1500 2000 5000 10000]
#define CLOUD_CUMULUS_RAIN_THICKNESS	3000 // [0 100 200 300 400 465 500 600 800 1000 1200 1500 2000 2500 3000 3500 5000 6000 7000]
#define CLOUD_CUMULUS_RAIN_COVERY		1.2  // [0.5 0.7 0.8 0.9 1.0 1.1 1.15 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.3 2.5 2.7 3.0]
#define CLOUD_CUMULUS_RAIN_DENSITY 		1.0  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_CUMULUS_RAIN_SUNLIGHTING	0.3  // [0.1 0.15 0.2 0.3 0.5 0.7 0.9 1.1 1.3 1.5 1.7 1.9 2.1 2.3 2.5]
#define CLOUD_CUMULUS_RAIN_SKYLIGHTING	0.3	 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]

#define CLOUD_LAYER2_ENABLED 1 // [0 1]
#define CLOUD_LAYER2_CLEAR_ALTITUDE_OFFSET 7500.0 // [1000 1500 2000 2500 3000 3500 4000 4500 5000]
#define CLOUD_LAYER2_RAIN_ALTITUDE_OFFSET 1000.0  // [500 1000 1500 2000 2500 3000 3500 4000]
#define CLOUD_LAYER2_CLEAR_THICKNESS	2400 // [400 600 800 1000 1200 1500 1800 2000]
#define CLOUD_LAYER2_CLEAR_COVERY		1.1  // [0.3 0.5 0.7 0.8 0.9 1.0 1.2 1.5]
#define CLOUD_LAYER2_CLEAR_DENSITY 		0.3  // [0.3 0.5 0.7 0.9 1.0 1.2 1.5]
#define CLOUD_LAYER2_CLEAR_SUNLIGHTING	2.1  // [0.3 0.5 0.7 0.8 1.0 1.2 1.5]
#define CLOUD_LAYER2_CLEAR_SKYLIGHTING	5.0  // [0.3 0.5 0.7 0.8 1.0 1.2 1.5]

#define CLOUD_LAYER2_RAIN_THICKNESS		4800
#define CLOUD_LAYER2_RAIN_COVERY		1.0
#define CLOUD_LAYER2_RAIN_DENSITY 		0.8
#define CLOUD_LAYER2_RAIN_SUNLIGHTING	1.4
#define CLOUD_LAYER2_RAIN_SKYLIGHTING	1.1

#define CLOUD_LAYER3_ENABLED 1 // [0 1]
#define CLOUD_LAYER3_CLEAR_ALTITUDE_OFFSET 9000.0 // [3000 3500 4000 4500 5000 5500 6000 6500 7000]
#define CLOUD_LAYER3_RAIN_ALTITUDE_OFFSET 1200.0  // [2000 2500 3000 3500 4000 4500 5000 5500]
#define CLOUD_LAYER3_CLEAR_THICKNESS	2400  // [60 80 100 150 200 300 356 400 500 600 700 800 1000 1200 1500 2000 5000 10000]
#define CLOUD_LAYER3_CLEAR_COVERY		1.1  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_LAYER3_CLEAR_DENSITY 		0.2  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_LAYER3_CLEAR_SUNLIGHTING	2.1  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_LAYER3_CLEAR_SKYLIGHTING	5.0  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]

#define CLOUD_LAYER3_RAIN_THICKNESS		5500 // [60 80 100 150 200 300 356 400 500 600 700 800 1000 1200 1500 2000 5000 10000]
#define CLOUD_LAYER3_RAIN_COVERY		1.2 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_LAYER3_RAIN_DENSITY 		0.6 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_LAYER3_RAIN_SUNLIGHTING	1.0 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]
#define CLOUD_LAYER3_RAIN_SKYLIGHTING	1.0  // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.5 3.0 5.0]

#define CLOUD_BOXY_ENABLED 0 // [0 1]
#define CLOUD_BOXY_CLEAR_ALTITUDE_OFFSET 7000.0 // [5000 5500 6000 6500 7000 7500 8000 8500 9000 10000]
#define CLOUD_BOXY_RAIN_ALTITUDE_OFFSET 5500.0 // [3000 4000 4500 5000 5500 6000 6500 7000]


#define CLOUD_BOXY_CLEAR_SUNLIGHTING 0.7 // [0.2 0.4 0.5 0.6 0.7 0.8 1.0]
#define CLOUD_BOXY_CLEAR_SKYLIGHTING 0.7 // [0.2 0.4 0.5 0.6 0.7 0.8 1.0]

#define CLOUD_BOXY_CLEAR_THICKNESS	6000 // было 1000 - слишком тонкие
#define CLOUD_BOXY_CLEAR_COVERY		0.7  // было 0.5 - больше покрытия
#define CLOUD_BOXY_CLEAR_DENSITY 	1.2  // было 0.6 - больше плотности
#define CLOUD_BOXY_SCALE 0.0004 

#define CLOUD_BOXY_RAIN_THICKNESS	1500
#define CLOUD_BOXY_RAIN_COVERY		0.7
#define CLOUD_BOXY_RAIN_DENSITY 	0.7
#define CLOUD_BOXY_RAIN_SUNLIGHTING	0.4
#define CLOUD_BOXY_RAIN_SKYLIGHTING	0.4



#define MAKEUP_CLOUD_PLANE 192.0        // Lower cloud layer
#define MAKEUP_CLOUD_PLANE_CENTER 220.0 // Middle of clouds
#define MAKEUP_CLOUD_PLANE_SUP 248.0    // Upper cloud layer
#define MAKEUP_CLOUD_SAMPLES 16         // [8 12 16 20 24 32]
#define MAKEUP_CLOUD_SPEED 0.001        // Cloud movement speed
#define MAKEUP_CLOUD_SCALE 1.0002777777777777778 // 1/3600




#define CLOUD_BOXY_DETAIL 1.5 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.5 2.0]

#define CLOUD_CUMULUS_SAMPLES 			32 	 // [4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 36 40 50 60 100]
#define CLOUD_CUMULUS_SUNLIGHT_SAMPLES 	4 	 // [2 3 4 5 6 7 8 9 10 12 15 17 20]
#define CLOUD_CUMULUS_SKYLIGHT_SAMPLES 	2 	 // [2 3 4 5 6 7 8 9 10 12 15 17 20]

#define CLOUD_BOTTOM_SHADOW_STRENGTH 0.3 // [0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.2 1.5 2.0]

#define CLOUD_LOCAL_COVERAGE

struct CloudProperties {
	float altitude;
	float thickness;
	float coverage;
	float density;
	float sunlighting, skylighting;
	float maxAltitude;
	float noiseScale;
	float cloudPeakWeight;
	vec3 offset;
};

#ifdef CLOUDS_WEATHER
	flat in vec3 cloudDynamicWeather;
#endif

CloudProperties GetGlobalCloudProperties() {
	CloudProperties cloudProperties;

	#ifdef CLOUDS_WEATHER
		if (cloudDynamicWeather.z > 5e-3) {
			cloudProperties.altitude    = mix(CLOUD_CUMULUS_CLEAR_ALTITUDE,	   CLOUD_CUMULUS_RAIN_ALTITUDE,	   wetness) * (1.0 + cloudDynamicWeather.z * 2.0);
			cloudProperties.density     = mix(CLOUD_CUMULUS_CLEAR_DENSITY,	   CLOUD_CUMULUS_RAIN_DENSITY,	   wetness) * oneMinus(cloudDynamicWeather.z * 0.3);
			cloudProperties.sunlighting = mix(CLOUD_CUMULUS_CLEAR_SUNLIGHTING, CLOUD_CUMULUS_RAIN_SUNLIGHTING, wetness) * (1.0 + cloudDynamicWeather.z * 0.2);
			cloudProperties.skylighting = mix(CLOUD_CUMULUS_CLEAR_SKYLIGHTING, CLOUD_CUMULUS_RAIN_SKYLIGHTING, wetness) * (1.0 + cloudDynamicWeather.z * 0.2);
		} else
	#endif
	{
		cloudProperties.altitude    = mix(CLOUD_CUMULUS_CLEAR_ALTITUDE,	   CLOUD_CUMULUS_RAIN_ALTITUDE,	   wetness);
		cloudProperties.density     = mix(CLOUD_CUMULUS_CLEAR_DENSITY,	   CLOUD_CUMULUS_RAIN_DENSITY,	   wetness);
		cloudProperties.sunlighting = mix(CLOUD_CUMULUS_CLEAR_SUNLIGHTING, CLOUD_CUMULUS_RAIN_SUNLIGHTING, wetness);
		cloudProperties.skylighting = mix(CLOUD_CUMULUS_CLEAR_SKYLIGHTING, CLOUD_CUMULUS_RAIN_SKYLIGHTING, wetness);
	}
	cloudProperties.thickness   = mix(CLOUD_CUMULUS_CLEAR_THICKNESS,   CLOUD_CUMULUS_RAIN_THICKNESS,   wetness);
	cloudProperties.coverage    = mix(CLOUD_CUMULUS_CLEAR_COVERY,	   CLOUD_CUMULUS_RAIN_COVERY,	   wetness);
	cloudProperties.maxAltitude	= cloudProperties.altitude + cloudProperties.thickness;
	cloudProperties.noiseScale 	= 4e-4 + 6e-5 * wetness;
	cloudProperties.cloudPeakWeight = 0.1 + 0.7 * wetness;
	cloudProperties.offset = vec3(0.0, 0.0, 0.0);

	return cloudProperties;
}

CloudProperties GetLayer2CloudProperties() {
	CloudProperties cloudProperties;
	
	float baseAltitude = mix(CLOUD_CUMULUS_CLEAR_ALTITUDE, CLOUD_CUMULUS_RAIN_ALTITUDE, wetness);
	float altitudeOffset = mix(CLOUD_LAYER2_CLEAR_ALTITUDE_OFFSET, CLOUD_LAYER2_RAIN_ALTITUDE_OFFSET, wetness);
	cloudProperties.altitude = baseAltitude + altitudeOffset;
	cloudProperties.thickness   = mix(CLOUD_LAYER2_CLEAR_THICKNESS,   CLOUD_LAYER2_RAIN_THICKNESS,   wetness);
	cloudProperties.coverage    = mix(CLOUD_LAYER2_CLEAR_COVERY,      CLOUD_LAYER2_RAIN_COVERY,      wetness);
	cloudProperties.density     = mix(CLOUD_LAYER2_CLEAR_DENSITY,     CLOUD_LAYER2_RAIN_DENSITY,     wetness);
	cloudProperties.sunlighting = mix(CLOUD_LAYER2_CLEAR_SUNLIGHTING, CLOUD_LAYER2_RAIN_SUNLIGHTING, wetness);
	cloudProperties.skylighting = mix(CLOUD_LAYER2_CLEAR_SKYLIGHTING, CLOUD_LAYER2_RAIN_SKYLIGHTING, wetness);
	cloudProperties.maxAltitude = cloudProperties.altitude + cloudProperties.thickness;
	cloudProperties.noiseScale  = 3e-4 + 5e-5 * wetness;
	cloudProperties.cloudPeakWeight = 0.08 + 0.5 * wetness;
	cloudProperties.offset = vec3(300.0, 0.0, 250.0);
	
	return cloudProperties;
}

CloudProperties GetLayer3CloudProperties() {
	CloudProperties cloudProperties;
	
	float baseAltitude = mix(CLOUD_CUMULUS_CLEAR_ALTITUDE, CLOUD_CUMULUS_RAIN_ALTITUDE, wetness);
	float altitudeOffset = mix(CLOUD_LAYER3_CLEAR_ALTITUDE_OFFSET, CLOUD_LAYER3_RAIN_ALTITUDE_OFFSET, wetness);
	cloudProperties.altitude = baseAltitude + altitudeOffset;
	cloudProperties.thickness   = mix(CLOUD_LAYER3_CLEAR_THICKNESS,   CLOUD_LAYER3_RAIN_THICKNESS,   wetness);
	cloudProperties.coverage    = mix(CLOUD_LAYER3_CLEAR_COVERY,      CLOUD_LAYER3_RAIN_COVERY,      wetness);
	cloudProperties.density     = mix(CLOUD_LAYER3_CLEAR_DENSITY,     CLOUD_LAYER3_RAIN_DENSITY,     wetness);
	cloudProperties.sunlighting = mix(CLOUD_LAYER3_CLEAR_SUNLIGHTING, CLOUD_LAYER3_RAIN_SUNLIGHTING, wetness);
	cloudProperties.skylighting = mix(CLOUD_LAYER3_CLEAR_SKYLIGHTING, CLOUD_LAYER3_RAIN_SKYLIGHTING, wetness);
	cloudProperties.maxAltitude = cloudProperties.altitude + cloudProperties.thickness;
	cloudProperties.noiseScale  = 2.5e-4 + 4e-5 * wetness;
	cloudProperties.cloudPeakWeight = 0.05 + 0.3 * wetness;
	cloudProperties.offset = vec3(-280.0, 0.0, 320.0);
	
	return cloudProperties;
}

CloudProperties GetBoxyCloudProperties() {
	CloudProperties cloudProperties;
	
	float baseAltitude = mix(CLOUD_CUMULUS_CLEAR_ALTITUDE, CLOUD_CUMULUS_RAIN_ALTITUDE, wetness);
	float altitudeOffset = mix(CLOUD_BOXY_CLEAR_ALTITUDE_OFFSET, CLOUD_BOXY_RAIN_ALTITUDE_OFFSET, wetness);
	cloudProperties.altitude = baseAltitude + altitudeOffset;
	cloudProperties.thickness   = mix(CLOUD_BOXY_CLEAR_THICKNESS,   CLOUD_BOXY_RAIN_THICKNESS,   wetness);
	cloudProperties.coverage    = mix(CLOUD_BOXY_CLEAR_COVERY,      CLOUD_BOXY_RAIN_COVERY,      wetness);
	cloudProperties.density     = mix(CLOUD_BOXY_CLEAR_DENSITY,     CLOUD_BOXY_RAIN_DENSITY,     wetness);
	cloudProperties.sunlighting = mix(CLOUD_BOXY_CLEAR_SUNLIGHTING, CLOUD_BOXY_RAIN_SUNLIGHTING, wetness);
	cloudProperties.skylighting = mix(CLOUD_BOXY_CLEAR_SKYLIGHTING, CLOUD_BOXY_RAIN_SKYLIGHTING, wetness);
	cloudProperties.maxAltitude = cloudProperties.altitude + cloudProperties.thickness;
	cloudProperties.noiseScale  = CLOUD_BOXY_SCALE;
	cloudProperties.cloudPeakWeight = 0.03 + 0.2 * wetness;
	cloudProperties.offset = vec3(450.0, 0.0, -380.0);
	
	return cloudProperties;
}

vec3 wind = vec3(2e-3, 2e-4, 1e-3) * worldTimeCounter * CLOUDS_SPEED;
vec3 wind2 = vec3(1.5e-3, 1.5e-4, 1.2e-3) * worldTimeCounter * CLOUDS_SPEED;
vec3 wind3 = vec3(2.5e-3, 3e-4, 0.8e-3) * worldTimeCounter * CLOUDS_SPEED;
vec3 windBoxy = vec3(1.2e-3, 1e-4, 0.9e-3) * worldTimeCounter * CLOUDS_SPEED;

float cloudForwardG = 0.6 - wetness * 0.2, cloudBackwardG = -0.4 + wetness * 0.2;
const float cloudBackwardWeight = 0.25, octWeight = 0.5, octScale = 3.0;

vec3 GetMakeupCloudColor(in float rainFactor) {
	// Day colors
	vec3 cloudColorDay = vec3(1.4, 1.35, 1.3);
	vec3 darkCloudColorDay = vec3(0.5, 0.55, 0.6);
	
	// Sunset colors
	vec3 cloudColorSunset = vec3(1.5, 0.8, 0.5);
	vec3 darkCloudColorSunset = vec3(0.7, 0.4, 0.3);
	
	// Night colors
	vec3 cloudColorNight = vec3(0.15, 0.18, 0.22);
	vec3 darkCloudColorNight = vec3(0.05, 0.06, 0.08);
	
	// Rain colors
	vec3 cloudColorRain = vec3(0.6, 0.65, 0.7);
	vec3 darkCloudColorRain = vec3(0.3, 0.32, 0.35);
	
	// Blend based on time of day
	float dayTime = worldSunVector.y;
	vec3 cloudColor, darkCloudColor;
	
	if (dayTime > 0.0) {
		// Day/Sunset transition
		float sunsetFactor = saturate((0.1 - dayTime) * 5.0);
		cloudColor = mix(cloudColorDay, cloudColorSunset, sunsetFactor);
		darkCloudColor = mix(darkCloudColorDay, darkCloudColorSunset, sunsetFactor);
	} else {
		// Night/Sunrise transition  
		float sunriseFactor = saturate((dayTime + 0.1) * 5.0);
		cloudColor = mix(cloudColorNight, cloudColorSunset, sunriseFactor);
		darkCloudColor = mix(darkCloudColorNight, darkCloudColorSunset, sunriseFactor);
	}
	
	// Apply rain
	cloudColor = mix(cloudColor, cloudColorRain, rainFactor);
	darkCloudColor = mix(darkCloudColor, darkCloudColorRain, rainFactor);
	
	// Return combined (will be separated in main function)
	return cloudColor;
}

vec4 RenderMakeupClouds(
	in vec3 worldDir,
	in vec3 skyColor,
	in float dither
) {
	// Only render if looking up
	if (worldDir.y <= 0.06) return vec4(skyColor, 1.0);
	
	vec3 basePos = vec3(0.0, eyeAltitude, 0.0);
	float viewYInv = 1.0 / worldDir.y;
	
	// Calculate intersection with cloud planes
	float planeDistInf = (MAKEUP_CLOUD_PLANE - basePos.y) * viewYInv;
	vec3 intersectionPos = (worldDir * planeDistInf) + basePos;
	
	float planeDistSup = (MAKEUP_CLOUD_PLANE_SUP - basePos.y) * viewYInv;
	vec3 intersectionPosSup = (worldDir * planeDistSup) + basePos;
	
	// Cloud layer parameters
	float difSup = MAKEUP_CLOUD_PLANE_SUP - MAKEUP_CLOUD_PLANE_CENTER;
	float difInf = MAKEUP_CLOUD_PLANE_CENTER - MAKEUP_CLOUD_PLANE;
	
	// Ray marching setup
	vec3 increment = (intersectionPosSup - intersectionPos) / float(MAKEUP_CLOUD_SAMPLES);
	float incrementDist = length(increment);
	
	// Opacity parameters
	float distAuxCoeff = (MAKEUP_CLOUD_PLANE_SUP - MAKEUP_CLOUD_PLANE) * 0.075;
	float distAuxCoeffBlur = distAuxCoeff * 0.3;
	float opacityDist = distAuxCoeff * 2.0 * viewYInv;
	
	// Cloud colors
	float rainFactor = wetness;
	float brightness = saturate(worldSunVector.y * 2.0);
	
	// Day colors
	vec3 cloudColorDay = vec3(1.4, 1.35, 1.3);
	vec3 darkCloudColorDay = vec3(0.5, 0.55, 0.6);
	
	// Sunset colors  
	vec3 cloudColorSunset = vec3(1.5, 0.8, 0.5);
	vec3 darkCloudColorSunset = vec3(0.7, 0.4, 0.3);
	
	// Night colors
	vec3 cloudColorNight = vec3(0.15, 0.18, 0.22);
	vec3 darkCloudColorNight = vec3(0.05, 0.06, 0.08);
	
	// Rain colors
	vec3 cloudColorRain = vec3(0.6, 0.65, 0.7);
	vec3 darkCloudColorRain = vec3(0.3, 0.32, 0.35);
	
	// Blend colors based on time
	vec3 cloudColor, darkCloudColor;
	float dayTime = worldSunVector.y;
	
	if (dayTime > 0.0) {
		float sunsetFactor = saturate((0.1 - dayTime) * 5.0);
		cloudColor = mix(cloudColorDay, cloudColorSunset, sunsetFactor);
		darkCloudColor = mix(darkCloudColorDay, darkCloudColorSunset, sunsetFactor);
	} else {
		float sunriseFactor = saturate((dayTime + 0.1) * 5.0);
		cloudColor = mix(cloudColorNight, cloudColorSunset, sunriseFactor);
		darkCloudColor = mix(darkCloudColorNight, darkCloudColorSunset, sunriseFactor);
	}
	
	// Apply rain
	cloudColor = mix(cloudColor, cloudColorRain, rainFactor);
	darkCloudColor = mix(darkCloudColor, darkCloudColorRain, rainFactor);
	
	// Threshold for cloud formation
	float umbral = mix(0.55, 0.25, 1.0 - rainFactor);
	
	// Accumulation variables
	float cloudValue = 0.0;
	float density = 0.0;
	bool firstContact = true;
	
	// Start position with dither
	intersectionPos += increment * dither;
	
	// Ray march through cloud layer
	for (int i = 0; i < MAKEUP_CLOUD_SAMPLES; i++) {
		// Sample noise texture (using colortex12 as boxy texture)
		vec2 sampleUV = intersectionPos.xz * MAKEUP_CLOUD_SCALE + vec2(frameTimeCounter * MAKEUP_CLOUD_SPEED);
		float currentValue = texture(colortex12, sampleUV).r;
		
		// Optional: Add second layer for more detail
		#ifdef CLOUD_BOXY_DETAIL
			if (CLOUD_BOXY_DETAIL > 0.5) {
				vec2 sampleUV2 = intersectionPos.zx * MAKEUP_CLOUD_SCALE * 2.0 + vec2(frameTimeCounter * MAKEUP_CLOUD_SPEED * 0.7);
				currentValue += texture(colortex12, sampleUV2).r;
				currentValue = smoothstep(0.05, 0.95, currentValue * 0.5);
			}
		#endif
		
		// Apply threshold
		currentValue = (currentValue - umbral) / (1.0 - umbral);
		
		// Calculate cloud surfaces
		float surfaceInf = MAKEUP_CLOUD_PLANE_CENTER - (currentValue * difInf);
		float surfaceSup = MAKEUP_CLOUD_PLANE_CENTER + (currentValue * difSup);
		float cloudThickness = surfaceSup - surfaceInf;
		
		float currentOpacity = 0.0;
		
		// Inside cloud
		if (intersectionPos.y > surfaceInf && intersectionPos.y < surfaceSup) {
			currentOpacity = min(incrementDist, cloudThickness);
		}
		// Near cloud edge (blur)
		else if (cloudThickness > 0.0 && i > 0) {
			float distanceAux = min(
				abs(intersectionPos.y - surfaceInf),
				abs(intersectionPos.y - surfaceSup)
			);
			
			if (distanceAux < distAuxCoeffBlur) {
				float blurFactor = 1.0 - (distanceAux / distAuxCoeffBlur);
				currentOpacity = min(blurFactor * incrementDist, cloudThickness);
			}
		}
		
		// Accumulate opacity
		if (currentOpacity > 0.0) {
			cloudValue += currentOpacity;
			
			if (firstContact) {
				firstContact = false;
				density = (surfaceSup - intersectionPos.y) / (MAKEUP_CLOUD_PLANE_SUP - MAKEUP_CLOUD_PLANE);
			}
		}
		
		intersectionPos += increment;
	}
	
	// Normalize cloud value
	cloudValue = saturate(cloudValue / opacityDist);
	density = saturate(density);
	
	// Attenuation factor
	float attFactor = mix(1.0, 0.75, brightness * (1.0 - rainFactor));
	
	// Mix cloud colors based on density
	vec3 finalCloudColor = mix(
		cloudColor * attFactor,
		darkCloudColor * attFactor,
		sqrt(sqrt(density)) // Approximation of pow(density, 0.25)
	);
	
	// Add bright halo against sun
	float sunHalo = (1.0 - sqrt(sqrt(cloudValue))) * brightness * brightness * (1.0 - rainFactor);
	finalCloudColor = mix(finalCloudColor, finalCloudColor * 13.0, sunHalo);
	
	// Fade near horizon
	float horizonFade = saturate((worldDir.y - 0.06) * 5.0);
	
	// Mix with sky
	vec3 result = mix(skyColor, finalCloudColor, cloudValue * horizonFade);
	
	// Return color and transmittance
	float transmittance = 1.0 - cloudValue * horizonFade;
	return vec4(result, transmittance);
}

float CloudBoxyDensity(in CloudProperties cloudProperties, in vec3 worldPos, in vec3 windOffset) {
	vec3 offsetPos = worldPos + cloudProperties.offset;
	
	// Calculate height factor first for early exit
	float heightFactor = (worldPos.y - cloudProperties.altitude) / cloudProperties.thickness;
	if (heightFactor < 0.0 || heightFactor > 1.0) return 0.0;
	
	// Sample the boxy texture for base shape (2D pattern)
	vec2 uv = offsetPos.xz * cloudProperties.noiseScale - windOffset.xz;
	uv = fract(uv);
	
	float boxySample = texture(colortex12, uv).r;
	
	// Height attenuation for flat bottom and rounded top (like Minecraft clouds)
	float heightAtten = saturate(heightFactor * 6.0) * saturate((1.0 - heightFactor) * 2.0);
	
	// Add 3D noise for volumetric details
	float detailNoise = 0.0;
	if (CLOUD_BOXY_DETAIL > 0.01) {
		vec3 detailPos = offsetPos * cloudProperties.noiseScale * 3.0 - windOffset;
		detailNoise = Get3DNoise(detailPos) * CLOUD_BOXY_DETAIL;
	}
	
	// Combine boxy pattern with 3D noise
	float density = boxySample + detailNoise * 0.3;
	
	// Apply coverage threshold for distinct cloud separation
	density = saturate((density - 1.0 + cloudProperties.coverage) / cloudProperties.coverage);
	
	// Apply height attenuation
	density *= heightAtten;
	
	return saturate(density * cloudProperties.density * 2.0);
}

float CloudVolumeDensity(in CloudProperties cloudProperties, in vec3 worldPos, in uint steps, in float noiseDetail, in vec3 windOffset) {
	vec3 offsetPos = worldPos + cloudProperties.offset;
	
	#ifdef CLOUD_LOCAL_COVERAGE
		float localCoverage = texture(noisetex, offsetPos.xz * 2e-7 - windOffset.xz * 2e-3).y;
		localCoverage = saturate(fma(localCoverage, 3.0, wetness - 0.4)) * 0.5 + 0.5;
		if (localCoverage < 0.1) return 0.0;
    #endif

	vec3 position = offsetPos * cloudProperties.noiseScale - windOffset;

	float density = noiseDetail * 0.03, weight = 0.5;

    for (uint i = 0u; i < steps; ++i, weight *= octWeight) {
		density += weight * Get3DNoiseSmooth(position);
        position = position * octScale - windOffset;
    }

	density += octWeight / octScale / steps;

	if (density < 1e-6) return 0.0;

    #ifdef CLOUD_LOCAL_COVERAGE
		density *= localCoverage;
    #endif

	float normalizedHeight  = saturate((worldPos.y - cloudProperties.altitude) * rcp(cloudProperties.thickness));
	float heightAttenuation = saturate(normalizedHeight * 6.6) * saturate(oneMinus(normalizedHeight) * (2.0 + wetness));

	density = cloudProperties.coverage == 1.0 ? density : saturate((density - 1.0 + cloudProperties.coverage) * rcp(cloudProperties.coverage));

	density *= heightAttenuation * 1.9;
	density -= heightAttenuation * 0.9 + normalizedHeight * 0.5 + 0.1;

	return saturate(density * 3.0 * cloudProperties.density);
}

#ifdef CLOUDS_SHADOW
	float GetShadow3DNoiseSmooth(in vec3 position) {
		vec3 p = floor(position);
		vec3 b = curve(position - p);

		ivec2 texel = ivec2(p.xy + 97.0 * p.z);

		vec2 s0 = texelFetch(noisetex, texel % noiseTextureResolution, 0).xy;
		vec2 s1 = texelFetch(noisetex, (texel + ivec2(1, 0)) % noiseTextureResolution, 0).xy;
		vec2 s2 = texelFetch(noisetex, (texel + ivec2(0, 1)) % noiseTextureResolution, 0).xy;
		vec2 s3 = texelFetch(noisetex, (texel + ivec2(1, 1)) % noiseTextureResolution, 0).xy;

		vec2 rg = mix(mix(s0, s1, b.x), mix(s2, s3, b.x), b.y);

		return mix(rg.x, rg.y, b.z);
	}

	float CloudVolumeDensitySmooth(in CloudProperties cloudProperties, in vec3 worldPos, in vec3 windOffset) {
		vec3 offsetPos = worldPos + cloudProperties.offset;
		
		#ifdef CLOUD_LOCAL_COVERAGE
			float localCoverage = texture(noisetex, offsetPos.xz * 2e-7 - windOffset.xz * 2e-3 + 0.5).y;
			localCoverage = saturate(fma(localCoverage, 3.0, wetness - 0.4)) * 0.5 + 0.5;
			if (localCoverage < 0.1) return 0.0;
		#endif

		vec3 position = offsetPos * cloudProperties.noiseScale - windOffset;

		float density = 0.03, weight = 0.5;

		for (uint i = 0u; i < 4u; ++i, weight *= octWeight) {
			density += weight * GetShadow3DNoiseSmooth(position);
			position = (position - windOffset) * octScale;
		}

		density += octWeight / octScale * 0.25;

		if (density < 1e-6) return 0.0;

		#ifdef CLOUD_LOCAL_COVERAGE
			density *= localCoverage;
		#endif

float normalizedHeight  = saturate((worldPos.y - cloudProperties.altitude) * rcp(cloudProperties.thickness));
		float heightAttenuation = saturate(normalizedHeight * 6.6) * saturate(oneMinus(normalizedHeight) * (2.0 + wetness));

		density = cloudProperties.coverage == 1.0 ? density : saturate((density - 1.0 + cloudProperties.coverage) * rcp(cloudProperties.coverage));

		density *= heightAttenuation * 1.9;
		density -= heightAttenuation * 0.9 + normalizedHeight * 0.5 + 0.1;

		return saturate(density * 3.0 * cloudProperties.density);
	}
#endif

float GetNoiseDetail(in vec3 worldDir) {
	worldDir *= 48.0;

	float pnoise = 	Get3DNoise(worldDir - wind); 		 worldDir += pnoise * 1e-3 - wind;
	pnoise +=  		Get3DNoise(worldDir * 2.0);			 worldDir += pnoise * 1e-3 - wind;
	pnoise +=  		Get3DNoise(worldDir * 4.0) * 0.5;	 worldDir += pnoise * 1e-3 - wind;
	pnoise +=  		Get3DNoise(worldDir * 8.0) * 0.25;	 worldDir += pnoise * 1e-3 - wind;
	pnoise +=  		Get3DNoise(worldDir * 16.0) * 0.125; worldDir += pnoise * 1e-3 - wind;

	return pnoise - 0.15;
}

float CloudVolumeSunLightOD(in CloudProperties cloudProperties, in vec3 rayPos, in float lightNoise, in vec3 windOffset) {
    float rayLength = cloudProperties.thickness * (0.2 / float(CLOUD_CUMULUS_SUNLIGHT_SAMPLES));
	vec4 rayStep = vec4(worldLightVector, 1.0) * rayLength;

    float opticalDepth = 0.0;

	for (uint i = 0u; i < CLOUD_CUMULUS_SUNLIGHT_SAMPLES; ++i, rayPos += rayStep.xyz) {
        rayStep *= 2.0;

		float density = CloudVolumeDensity(cloudProperties, rayPos + rayStep.xyz * lightNoise, 5u, 1.0, windOffset);
		if (density < 1e-4) continue;

        opticalDepth += density;
    }

    return opticalDepth * rayLength * 0.12;
}

float CloudVolumeSkyLightOD(in CloudProperties cloudProperties, in vec3 rayPos, in float lightNoise, in vec3 windOffset) {
    float rayLength = cloudProperties.thickness * (0.2 / float(CLOUD_CUMULUS_SKYLIGHT_SAMPLES));
	vec4 rayStep = vec4(vec3(0.0, 1.0, 0.0), 1.0) * rayLength;

    float opticalDepth = 0.0;

	for (uint i = 0u; i < CLOUD_CUMULUS_SKYLIGHT_SAMPLES; ++i, rayPos += rayStep.xyz) {
        rayStep *= 2.0;

		float density = CloudVolumeDensity(cloudProperties, rayPos + rayStep.xyz * lightNoise, 3u, 1.0, windOffset);
		if (density < 1e-4) continue;

        opticalDepth += density;
    }

    return opticalDepth * rayLength * 0.04;
}

float CloudBoxySunLightOD(in CloudProperties cloudProperties, in vec3 rayPos, in float lightNoise, in vec3 windOffset) {
    float rayLength = cloudProperties.thickness * (0.2 / float(CLOUD_CUMULUS_SUNLIGHT_SAMPLES));
	vec4 rayStep = vec4(worldLightVector, 1.0) * rayLength;

    float opticalDepth = 0.0;

	for (uint i = 0u; i < CLOUD_CUMULUS_SUNLIGHT_SAMPLES; ++i, rayPos += rayStep.xyz) {
        rayStep *= 1.8;

		float density = CloudBoxyDensity(cloudProperties, rayPos + rayStep.xyz * lightNoise, windOffset);
		if (density < 1e-4) continue;

        opticalDepth += density;
    }

    return opticalDepth * rayLength * 0.12;
}

float CloudBoxySkyLightOD(in CloudProperties cloudProperties, in vec3 rayPos, in float lightNoise, in vec3 windOffset) {
    float rayLength = cloudProperties.thickness * (0.2 / float(CLOUD_CUMULUS_SKYLIGHT_SAMPLES));
	vec4 rayStep = vec4(vec3(0.0, 1.0, 0.0), 1.0) * rayLength;

    float opticalDepth = 0.0;

	for (uint i = 0u; i < CLOUD_CUMULUS_SKYLIGHT_SAMPLES; ++i, rayPos += rayStep.xyz) {
        rayStep *= 1.8;

		float density = CloudBoxyDensity(cloudProperties, rayPos + rayStep.xyz * lightNoise, windOffset);
		if (density < 1e-4) continue;

        opticalDepth += density;
    }

    return opticalDepth * rayLength * 0.04;
}

vec4 RenderCloudLayer(
	in CloudProperties cloudProperties,
	in vec3 worldDir,
	in float dither,
	in vec4 phases,
	in vec3 windOffset,
	in vec3 skyRadiance,
	in vec3 planeOrigin,
	in float noiseDetail
) {
	vec4 cloudsData = vec4(0.0, 0.0, 0.0, 1.0);
	
	bool intersectsGround = RaySphereIntersection(planeOrigin, worldDir, planetRadius).y >= 0.0;
	
	if ((worldDir.y > 0.0 && eyeAltitude < cloudProperties.altitude)
	|| (intersectsGround && eyeAltitude > cloudProperties.maxAltitude)) {

		vec2 bottomIntersection = RaySphereIntersection(planeOrigin, worldDir, planetRadius + cloudProperties.altitude);
		vec2 topIntersection = RaySphereIntersection(planeOrigin, worldDir, planetRadius + cloudProperties.maxAltitude);

		float startLength, endLength;
		if (eyeAltitude > cloudProperties.maxAltitude) {
			startLength = topIntersection.x;
			endLength = bottomIntersection.x;
		} else {
			startLength = bottomIntersection.y;
			endLength = topIntersection.y;
		}

		float rayRange = oneMinus(saturate((eyeAltitude - cloudProperties.maxAltitude) * 0.1)) *
						oneMinus(saturate((cloudProperties.altitude - eyeAltitude) * 0.1));

		float rayDist = bottomIntersection.y >= 0.0 && eyeAltitude > cloudProperties.altitude ? bottomIntersection.x : topIntersection.y;

		startLength *= oneMinus(rayRange);
		endLength = mix(endLength, rayDist, rayRange);

		uint raySteps = CLOUD_CUMULUS_SAMPLES;
		raySteps = uint(mix(raySteps, uint(raySteps * 0.6), abs(worldDir.y)));

		float rayLength = clamp(endLength - startLength, 0.0, 2e4) * rcp(raySteps);
		vec3 rayStep = rayLength * worldDir;
		vec3 rayPos = (startLength + rayLength * dither) * worldDir + cameraPosition;

		float scatteringSun = 0.0;
		float scatteringSky = 0.0;
		float transmittance = 1.0;

		for (uint i = 0u; i < raySteps; ++i, rayPos += rayStep) {
			if (transmittance < minTransmittance) break;
			if (rayPos.y < cloudProperties.altitude || rayPos.y > cloudProperties.maxAltitude) continue;

			float dist = distance(rayPos, cameraPosition);
			if (dist > planetRadius + cloudProperties.maxAltitude) continue;

			float density = CloudVolumeDensity(cloudProperties, rayPos, 5u, mix(noiseDetail, 1.0, exp2(-dist * 0.001f)), windOffset);
			if (density < 1e-4) continue;

			vec2 lightNoise = hash2(fract(rayPos)) * 0.4 + 0.3;

			float sunlightOD = CloudVolumeSunLightOD(cloudProperties, rayPos, lightNoise.x, windOffset);

			float normalizedHeight = saturate((rayPos.y - cloudProperties.altitude) * rcp(cloudProperties.thickness));
			float bottomShadow = 1.0 - (1.0 - normalizedHeight) * CLOUD_BOTTOM_SHADOW_STRENGTH;

			float bounceEstimate = oneMinus(expf(-density * 36.0)) * 0.82;
			bounceEstimate /= 1.0 - bounceEstimate;

			float sunlightEnergy = 	expf(-sunlightOD * 2.0) * phases.x;
			sunlightEnergy += 		expf(-sunlightOD * 0.8) * phases.y;
			sunlightEnergy += 		expf(-sunlightOD * 0.3) * phases.z;
			sunlightEnergy += 		expf(-sunlightOD * 0.1) * phases.w;
			sunlightEnergy *= bottomShadow;

			float skylightEnergy = CloudVolumeSkyLightOD(cloudProperties, rayPos, lightNoise.y, windOffset);
			skylightEnergy = expf(-skylightEnergy) + expf(-skylightEnergy * 0.1) * 0.1;
			skylightEnergy *= bottomShadow;

			float stepTransmittance = expf(-density * 0.12 * rayLength);
			float cloudsTemp = bounceEstimate * transmittance * oneMinus(stepTransmittance);
			scatteringSun += sunlightEnergy * cloudsTemp;
			scatteringSky += skylightEnergy * cloudsTemp;
			transmittance *= stepTransmittance;	
		}

		if (transmittance < 1.0 - minTransmittance) {
			bool moonlit = worldSunVector.y < -0.04;
			vec3 scattering = scatteringSun * 22.0 * cloudProperties.sunlighting * (moonlit ? moonIlluminance : sunIlluminance);
			scattering += scatteringSky * 0.15 * cloudProperties.skylighting * skyIlluminance;

			if (isLightningFlashing > 1e-2) scattering += sqr(scatteringSky) * 0.1 * lightningColor;

			#ifdef AURORA
				scattering += scatteringSky * vec3(0.0, 0.005, 0.0025) * auroraAmount;
			#endif

			rayPos -= cameraPosition;
			float atmosFade = expf(-length(rayPos) * (0.2 + 0.1 * wetness) * 1e-4);
			scattering = scattering * atmosFade + skyRadiance * oneMinus(transmittance) * oneMinus(atmosFade);

			cloudsData = vec4(scattering, transmittance);
		}
	}
	
	return cloudsData;
}

vec4 RenderBoxyCloudLayer(
	in CloudProperties cloudProperties,
	in vec3 worldDir,
	in float dither,
	in vec4 phases,
	in vec3 windOffset,
	in vec3 skyRadiance,
	in vec3 planeOrigin,
	in float noiseDetail
) {
	vec4 cloudsData = vec4(0.0, 0.0, 0.0, 1.0);
	
	bool intersectsGround = RaySphereIntersection(planeOrigin, worldDir, planetRadius).y >= 0.0;
	
	if ((worldDir.y > 0.0 && eyeAltitude < cloudProperties.altitude)
	|| (intersectsGround && eyeAltitude > cloudProperties.maxAltitude)) {

		vec2 bottomIntersection = RaySphereIntersection(planeOrigin, worldDir, planetRadius + cloudProperties.altitude);
		vec2 topIntersection = RaySphereIntersection(planeOrigin, worldDir, planetRadius + cloudProperties.maxAltitude);

		float startLength, endLength;
		if (eyeAltitude > cloudProperties.maxAltitude) {
			startLength = topIntersection.x;
			endLength = bottomIntersection.x;
		} else {
			startLength = bottomIntersection.y;
			endLength = topIntersection.y;
		}

		float rayRange = oneMinus(saturate((eyeAltitude - cloudProperties.maxAltitude) * 0.1)) *
						oneMinus(saturate((cloudProperties.altitude - eyeAltitude) * 0.1));

		float rayDist = bottomIntersection.y >= 0.0 && eyeAltitude > cloudProperties.altitude ? bottomIntersection.x : topIntersection.y;

		startLength *= oneMinus(rayRange);
		endLength = mix(endLength, rayDist, rayRange);

		uint raySteps = uint(CLOUD_CUMULUS_SAMPLES * 0.5);
		raySteps = uint(mix(raySteps, uint(raySteps * 0.6), abs(worldDir.y)));

		float rayLength = clamp(endLength - startLength, 0.0, 2e4) * rcp(raySteps);
		vec3 rayStep = rayLength * worldDir;
		vec3 rayPos = (startLength + rayLength * dither) * worldDir + cameraPosition;

		float scatteringSun = 0.0;
		float scatteringSky = 0.0;
		float transmittance = 1.0;

		for (uint i = 0u; i < raySteps; ++i, rayPos += rayStep) {
			if (transmittance < minTransmittance) break;
			if (rayPos.y < cloudProperties.altitude || rayPos.y > cloudProperties.maxAltitude) continue;

			float dist = distance(rayPos, cameraPosition);
			if (dist > planetRadius + cloudProperties.maxAltitude) continue;

			float density = CloudBoxyDensity(cloudProperties, rayPos, windOffset);
			if (density < 1e-4) continue;

			vec2 lightNoise = hash2(fract(rayPos)) * 0.4 + 0.3;

			float sunlightOD = CloudBoxySunLightOD(cloudProperties, rayPos, lightNoise.x, windOffset);

			float normalizedHeight = saturate((rayPos.y - cloudProperties.altitude) * rcp(cloudProperties.thickness));
			float bottomShadow = 1.0 - (1.0 - normalizedHeight) * CLOUD_BOTTOM_SHADOW_STRENGTH;

			float bounceEstimate = oneMinus(expf(-density * 32.0)) * 0.75;
			bounceEstimate /= 1.0 - bounceEstimate;

			float sunlightEnergy = 	expf(-sunlightOD * 2.0) * phases.x;
			sunlightEnergy += 		expf(-sunlightOD * 0.8) * phases.y;
			sunlightEnergy += 		expf(-sunlightOD * 0.3) * phases.z;
			sunlightEnergy += 		expf(-sunlightOD * 0.1) * phases.w;
			sunlightEnergy *= bottomShadow;

			float skylightEnergy = CloudBoxySkyLightOD(cloudProperties, rayPos, lightNoise.y, windOffset);
			skylightEnergy = expf(-skylightEnergy) + expf(-skylightEnergy * 0.1) * 0.1;
			skylightEnergy *= bottomShadow;

			float stepTransmittance = expf(-density * 0.14 * rayLength);
			float cloudsTemp = bounceEstimate * transmittance * oneMinus(stepTransmittance);
			scatteringSun += sunlightEnergy * cloudsTemp;
			scatteringSky += skylightEnergy * cloudsTemp;
			transmittance *= stepTransmittance;	
		}

		if (transmittance < 1.0 - minTransmittance) {
			bool moonlit = worldSunVector.y < -0.04;
			vec3 scattering = scatteringSun * 20.0 * cloudProperties.sunlighting * (moonlit ? moonIlluminance : sunIlluminance);
			scattering += scatteringSky * 0.15 * cloudProperties.skylighting * skyIlluminance;

			if (isLightningFlashing > 1e-2) scattering += sqr(scatteringSky) * 0.1 * lightningColor;

			#ifdef AURORA
				scattering += scatteringSky * vec3(0.0, 0.005, 0.0025) * auroraAmount;
			#endif

			rayPos -= cameraPosition;
			float atmosFade = expf(-length(rayPos) * (0.2 + 0.1 * wetness) * 1e-4);
			scattering = scattering * atmosFade + skyRadiance * oneMinus(transmittance) * oneMinus(atmosFade);

			cloudsData = vec4(scattering, transmittance);
		}
	}
	
	return cloudsData;
}