#include "/lib/util/temporalDither.glsl"

float getNoise(vec2 pos) {
	return fract(sin(dot(pos, vec2(12.9898, 4.1414))) * 43758.5453);
}

#ifdef OVERWORLD

float CloudSampleBasePerlin(vec2 coord) {
	float noiseBase = texture2D(noisetex, coord).r;

	return noiseBase;
}

float CloudSampleBaseWorley(vec2 coord) {
	float noiseBase = texture2D(noisetex, coord).g;
	noiseBase = pow(1.0 - noiseBase, 2.0) * 0.5 + 0.25;

	return noiseBase;
}

float CloudSampleBaseBlocky(vec2 coord) {
	float noiseRes = 512.0;

	coord.xy = coord.xy * noiseRes - 0.5;

	vec2 flr = floor(coord.xy);
	vec2 frc = coord.xy - flr;

	frc = clamp(frc * 5.0 - 2.0, vec2(0.0), vec2(1.0));
	frc = frc * frc * (3.0 - 2.0 * frc);

	coord.xy = (flr + frc + 0.5) / noiseRes;

	float noiseBase = texture2D(noisetex, coord).a;
	noiseBase = (1.0 - noiseBase) * 4.0;

	float noiseRain = texture2D(noisetex, coord + vec2(0.5,0.0)).a;
	noiseRain = (1.0 - noiseRain) * 4.0 * smoothstep(0.0, 0.5, rainStrength);

	noiseBase = min(noiseBase + noiseRain, 1.0);

	return noiseBase;
}

float CloudSampleDetail(vec2 coord, float cloudGradient) {
	float detailZ = floor(cloudGradient * float(CLOUD_THICKNESS)) * 0.04;
	float detailFrac = fract(cloudGradient * float(CLOUD_THICKNESS));

	float noiseDetailLow = texture2D(noisetex, coord.xy + detailZ).b;
	float noiseDetailHigh = texture2D(noisetex, coord.xy + detailZ + 0.04).b;

	float noiseDetail = mix(noiseDetailLow, noiseDetailHigh, detailFrac);

	return noiseDetail;
}

float CloudCoverageDefault(float cloudGradient) {
	float noiseCoverage = abs(cloudGradient - 0.125);

	noiseCoverage *= cloudGradient > 0.125 ? (2.14 - CLOUD_AMOUNT * 0.1) : 8.0;
	noiseCoverage = noiseCoverage * noiseCoverage * 4.0;

	return noiseCoverage;
}

float CloudCoverageBlocky(float cloudGradient) {
	float noiseCoverage = abs(cloudGradient - 0.5) * 2.0;

	noiseCoverage *= noiseCoverage;
	noiseCoverage *= noiseCoverage;

	return noiseCoverage;
}

float CloudApplyDensity(float noise) {
	noise *= CLOUD_DENSITY * CLOUD_DENSITY_SCALE;
	noise *= (1.0 - CLOUD_RAIN_DAMPEN * rainStrength);
	noise = noise / sqrt(noise * noise + 0.5);

	return noise;
}

float CloudCombineDefault(float noiseBase, float noiseDetail, float noiseCoverage) {
	float noise = mix(noiseBase, noiseDetail, 0.0476 * CLOUD_DETAIL) * 21.0;

	noise = mix(noise - noiseCoverage, 18.0 - noiseCoverage * 2.5, 0.33 * rainStrength);
	noise = max(noise - CLOUD_AMOUNT, 0.0);

	noise = CloudApplyDensity(noise);

	return noise;
}

float CloudCombineBlocky(float noiseBase, float noiseCoverage) {
	float noise = (noiseBase - noiseCoverage) * 2.0;

	noise = max(noise, 0.0);
	
	noise = CloudApplyDensity(noise);

	return noise;
}

float CloudSample(vec2 coord, vec2 wind, float cloudGradient, float dither) {
	coord *= 0.004 * CLOUD_STRETCH;

	#if CLOUD_BASE == 0
	vec2 baseCoord = coord * 0.25 + wind;
	vec2 detailCoord = coord.xy * 0.5 - wind * 2.0;

	float noiseBase = CloudSampleBasePerlin(baseCoord);
	float noiseDetail = CloudSampleDetail(detailCoord, cloudGradient);
	float noiseCoverage = CloudCoverageDefault(cloudGradient);

	float noise = CloudCombineDefault(noiseBase, noiseDetail, noiseCoverage);
	
	return noise;
	#elif CLOUD_BASE == 1
	vec2 baseCoord = coord * 0.5 + wind * 2.0;
	vec2 detailCoord = coord.xy * 0.5 - wind * 2.0;

	float noiseBase = CloudSampleBaseWorley(baseCoord);
	float noiseDetail = CloudSampleDetail(detailCoord, cloudGradient);
	float noiseCoverage = CloudCoverageDefault(cloudGradient);

	float noise = CloudCombineDefault(noiseBase, noiseDetail, noiseCoverage);
	
	return noise;
	#else
	vec2 baseCoord = coord * 0.125 + wind * 0.5;

	float noiseBase = CloudSampleBaseBlocky(baseCoord);
	float noiseCoverage = CloudCoverageBlocky(cloudGradient);

	float noise = CloudCombineBlocky(noiseBase, noiseCoverage);

	return noise;
	#endif
}

float InvLerp(float v, float l, float h) {
	return clamp((v - l) / (h - l), 0.0, 1.0);
}

vec3 GetReflectedCameraPos(vec3 worldPos, vec3 normal) {
	vec4 worldNormal = gbufferModelViewInverse * vec4(normal, 1.0);
	worldNormal.xyz /= worldNormal.w;

	vec3 cameraPos = cameraPosition + 2.0 * worldNormal.xyz * dot(worldPos, worldNormal.xyz);
	cameraPos = cameraPos + reflect(worldPos, worldNormal.xyz); //bobbing stabilizer, works like magic

	return cameraPos;
}

vec4 DrawCloudVolumetric(vec3 viewPos, vec3 cameraPos, float z, float dither, vec3 lightCol, vec3 ambientCol, inout float cloudViewLength, bool fadeFaster) {
	dither = TemporalDither(dither);

	vec3 nViewPos = normalize(viewPos);
	vec3 worldPos = (gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz;
	vec3 nWorldPos = normalize(worldPos);
	float viewLength = length(viewPos);

	float cloudHeight = CLOUD_HEIGHT * CLOUD_VOLUMETRIC_SCALE + CLOUD_BASE_HEIGHT_OFFSET;
	// float cloudHeight = CLOUD_HEIGHT * CLOUD_VOLUMETRIC_SCALE + cameraPosition.y;
	// float cloudHeight = 63;
	float cloudThickness = CLOUD_THICKNESS;

	int maxSamples = CLOUD_SAMPLES_DEFAULT;

	#if CLOUD_BASE == 2
	cloudThickness *= 0.5;
	maxSamples = CLOUD_SAMPLES_BLOCKY;
	#endif

	float lowerY = cloudHeight;
	float upperY = cloudHeight + cloudThickness * CLOUD_VOLUMETRIC_SCALE;

	float lowerPlane = (lowerY - cameraPos.y) / nWorldPos.y;
	float upperPlane = (upperY - cameraPos.y) / nWorldPos.y;

	float nearestPlane = max(min(lowerPlane, upperPlane), 0.0);
	float furthestPlane = max(lowerPlane, upperPlane);

	float maxcloudViewLength = cloudViewLength;

	if (furthestPlane < 0) return vec4(0.0);

	float planeDifference = furthestPlane - nearestPlane;

	vec3 startPos = cameraPos + nearestPlane * nWorldPos;

	float scaling = abs(cameraPosition.y - (upperY + lowerY) * 0.5) / ((upperY - lowerY) * 0.5);
	scaling = clamp((scaling - 1.0) * cloudThickness * 0.125, 0.0, 1.0);

	float sampleLength = cloudThickness * CLOUD_VOLUMETRIC_SCALE / 2.0;
	sampleLength /= (4.0 * nWorldPos.y * nWorldPos.y) * scaling + 1.0;
	vec3 sampleStep = nWorldPos * sampleLength;
	int samples = int(min(planeDifference / sampleLength, maxSamples) + 1);
	
	vec3 samplePos = startPos + sampleStep * dither;
	float sampleTotalLength = nearestPlane + sampleLength * dither;

	vec2 wind = vec2(
		frametime * CLOUD_SPEED * 0.0005,
		sin(frametime * CLOUD_SPEED * 0.001) * 0.005
	) * CLOUD_HEIGHT / 15.0;

	float cloud = 0.0;
	float cloudFaded = 0.0;
	float cloudLighting = 0.0;

	float VoU = dot(nViewPos, upVec);
	float VoL = dot(nViewPos, lightVec);

	float halfVoL = mix(abs(VoL) * 0.8, VoL, shadowFade) * 0.5 + 0.5;
	float halfVoLSqr = halfVoL * halfVoL;

	float scattering = pow(halfVoL, 6.0);
	float noiseLightFactor = (2.0 - 1.5 * VoL * shadowFade) * CLOUD_DENSITY * 0.5;

	float viewLengthSoftMin = viewLength - sampleLength * 0.5;
	float viewLengthSoftMax = viewLength + sampleLength * 0.5;

	float fade = 1.0;
	float fadeStart = 32.0 / max(fogDensity, 0.5);
	float fadeEnd = (fadeFaster ? 120.0 : 320.0) / max(fogDensity, 0.5);

	for (int i = 0; i < samples; i++) {
		if (cloud > 0.99) break;
		if (sampleTotalLength > viewLengthSoftMax && z < 1.0) break;

		float cloudGradient = InvLerp(samplePos.y, lowerY, upperY);
		float xzNormalizedDistance = length(samplePos.xz - cameraPos.xz) / CLOUD_VOLUMETRIC_SCALE;
		vec2 cloudCoord = samplePos.xz / CLOUD_VOLUMETRIC_SCALE;

		float noise = CloudSample(cloudCoord * 0.75, wind, cloudGradient, dither);
		noise *= step(lowerY, samplePos.y) * step(samplePos.y, upperY);

		float sampleLighting = pow(cloudGradient, 1.125 * halfVoLSqr + 0.875) * 0.8 + 0.2;
		sampleLighting *= 1.0 - pow(noise, noiseLightFactor);

		float sampleFade = InvLerp(xzNormalizedDistance, fadeEnd, fadeStart);
		fade *= mix(1.0, sampleFade, noise * (1.0 - cloud));
		noise *= step(xzNormalizedDistance, fadeEnd);

		cloudLighting = mix(cloudLighting, sampleLighting, noise * (1.0 - cloud * cloud));
		
		if (z < 1.0) {
			noise *= InvLerp(sampleTotalLength, viewLengthSoftMax, viewLengthSoftMin);
		}

		cloud = mix(cloud, 1.0, noise);

		if (cloudViewLength == maxcloudViewLength && cloud > 0.5) {
			cloudViewLength = sampleTotalLength;
		}
		
		samplePos += sampleStep;
		sampleTotalLength += sampleLength;
	}

	cloudFaded = cloud * fade;

	cloudLighting = mix(cloudLighting, 1.0, (1.0 - cloud * cloud) * scattering * 0.5);
	cloudLighting *= (1.0 - 0.9 * rainStrength);
	
	vec3 cloudColor = mix(
		ambientCol * (0.3 * sunVisibility + 0.5),
		lightCol * (0.85 + 1.15 * scattering),
		cloudLighting
	);

	cloudColor *= 1.0 - 0.4 * rainStrength;
	cloudColor *= CLOUD_BRIGHTNESS * (0.5 - 0.25 * (1.0 - sunVisibility) * (1.0 - rainStrength));

	cloudFaded *= cloudFaded * CLOUD_OPACITY;

	if (cloudFaded < dither) {
		cloudViewLength = maxcloudViewLength;
	}

	return vec4(cloudColor, cloudFaded);
}

void DrawStars(inout vec3 color, vec3 viewPos) {
	vec3 worldPos = vec3(gbufferModelViewInverse * vec4(viewPos * 100.0, 1.0));
	vec2 planeCoord0 = worldPos.xz / (length(worldPos.y) + length(worldPos.xz));
		planeCoord0 += cameraPosition.xz * 0.00001;
		planeCoord0 += frameTimeCounter * 0.0001;
		planeCoord0 = floor(planeCoord0 * 500.0) / 500.0;

	vec2 planeCoord1 = worldPos.xz / (length(worldPos.y) + length(worldPos.xz));
		planeCoord1 += cameraPosition.xz * 0.00001;
		planeCoord1 += frameTimeCounter * 0.0001;
		planeCoord1 = floor(planeCoord1 * 200.0) / 200.0;
	
	float VoU = clamp(dot(normalize(viewPos), upVec), 0.0, 1.0);
	float nightFactor = moonVisibility * (1.0 - rainStrength * 0.5);
	float multiplier = sqrt(sqrt(VoU)) * (4.5 + 2.5 * nightFactor) * (1.0 - rainStrength);
	
	float star = 1.0;
	if (VoU > 0.0) {
		float smallStars = getNoise(planeCoord0 + 10.0);
			  smallStars*= getNoise(planeCoord0 + 14.0);
			  smallStars = clamp(smallStars - 0.765, 0.0, 1.0);
			  smallStars *= multiplier;
			  smallStars *= smallStars * 0.85;

		float bigStars = getNoise(planeCoord1 + 11.0);
			  bigStars*= getNoise(planeCoord1 + 13.0);
			  bigStars*= getNoise(planeCoord1 + 15.0);
			  bigStars = clamp(bigStars - 0.745, 0.0, 1.0);
			  bigStars *= multiplier;
			  bigStars *= bigStars * 2.0;

		star = smallStars + bigStars;
	}
	star = clamp(star - 0.79, 0.0, 1.0);
	star *= star * multiplier * (0.8 + 0.6 * nightFactor);
	
	star *= clamp((cameraPosition.y + DEEP_UNDERGROUND_Y_NEW) / DEEP_UNDERGROUND_RANGE, 0.0, 1.0);

	#ifdef UNDERGROUND_SKY
	star *= mix(clamp((cameraPosition.y - UNDERGROUND_SKY_START) / UNDERGROUND_SKY_RANGE, 0.0, 1.0), 1.0, eBS);
	#endif
		
	color += star * mix(pow(lightNight, vec3(0.8)), vec3(0.65, 0.72, 0.9), 0.25);
}

void DrawShootingStars(inout vec3 color, vec3 viewPos) {
	vec3 worldPos = vec3(gbufferModelViewInverse * vec4(viewPos * 100.0, 1.0));
	vec2 skyCoord = worldPos.xz / (length(worldPos.y) + length(worldPos.xz));
	float VoU = clamp(dot(normalize(viewPos), upVec), 0.0, 1.0);
	float visibility = moonVisibility * moonVisibility * (1.0 - rainStrength) * (1.0 - rainStrength);

	if (VoU <= 0.0 || visibility <= 0.0) return;

	vec3 shootingStar = vec3(0.0);

	for (int i = 0; i < 1; i++) {
		float seed = 11.37 + float(i) * 17.13;
		float cycle = frametime * 0.009 + seed;
		float eventIndex = floor(cycle);
		float phase = fract(cycle);
		float streakActive = smoothstep(0.015, 0.035, phase) * (1.0 - smoothstep(0.09, 0.14, phase));

		if (streakActive <= 0.0) continue;

		float eventSeed = eventIndex + seed * 37.0;
		vec2 start = vec2(
			mix(-0.75, 0.75, getNoise(vec2(eventSeed, seed))),
			mix(0.15, 0.70, getNoise(vec2(seed, eventSeed)))
		);

		float angle = mix(-1.10, -0.35, getNoise(vec2(eventSeed + 13.0, seed + 7.0)));
		vec2 dir = vec2(cos(angle), sin(angle));
		vec2 normal = vec2(-dir.y, dir.x);

		float travel = mix(0.25, 0.55, getNoise(vec2(eventSeed + 21.0, seed + 17.0)));
		float progress = clamp((phase - 0.015) / 0.085, 0.0, 1.0);
		vec2 head = start + dir * travel * progress;

		vec2 delta = skyCoord - head;
		float along = dot(delta, dir);
		float across = abs(dot(delta, normal));

		float trail = smoothstep(0.008, 0.0, across);
		trail *= smoothstep(-0.18, -0.01, along);
		trail *= 1.0 - smoothstep(-0.01, 0.02, along);

		float headGlow = exp2(-960.0 * dot(delta, delta));
		float intensity = (trail * 0.7 + headGlow * 0.5) * streakActive;
		shootingStar += intensity * vec3(0.80, 0.88, 1.08);
	}

	shootingStar *= visibility * (0.5 + 0.5 * VoU);
	color += shootingStar;
}

#ifdef AURORA
#include "/lib/color/auroraColor.glsl"

float AuroraSample(vec2 coord, vec2 wind, float time) {
	// Primary curtain band
	float noise1 = texture2D(noisetex, coord * 0.04 + wind * 0.25).b * 3.0;
		  noise1+= texture2D(noisetex, coord * 0.02 + wind * 0.15).b * 3.0;
	float curtain1 = max(1.0 - 2.0 * abs(noise1 - 3.0), 0.0);

	// Secondary ribbon band (offset, thinner)
	float noise2 = texture2D(noisetex, coord * 0.06 + wind * 0.30 + 0.37).b * 3.0;
		  noise2+= texture2D(noisetex, coord * 0.03 - wind * 0.10 + 0.19).b * 3.0;
	float curtain2 = max(1.0 - 2.5 * abs(noise2 - 3.0), 0.0);

	float combined = curtain1 + curtain2 * 0.5;

	// Vertical fold distortion (magnetic field draping)
	combined *= 1.0 + sin(coord.x * 25.0 + time * 0.3) * 0.15;

	return max(combined, 0.0);
}

vec3 DrawAurora(vec3 viewPos, float dither, int samples) {
	dither = TemporalDither(dither);

	float sampleStep = 1.0 / samples;
	float currentStep = dither * sampleStep;

	float VoU = dot(normalize(viewPos), upVec);

	float visibility = sqrt(moonVisibility) * (1.0 - rainStrength) * (1.0 - rainStrength);

	#if defined(WEATHER_PERBIOME) && !defined(AURORA_EVERYWHERE)
	visibility *= isCold * isCold;
	#endif

	// Rarity system: deterministic aurora nights based on moon phase
	// Common: 6/8 nights, Uncommon: 3/8 nights, Rare: 1/8 nights
	#if AURORA_RARITY == 1
	if (moonPhase == 2 || moonPhase == 4) visibility = 0.0;
	#elif AURORA_RARITY == 2
	if (moonPhase != 0 && moonPhase != 3 && moonPhase != 5) visibility = 0.0;
	#elif AURORA_RARITY == 3
	if (moonPhase != 0) visibility = 0.0;
	#endif
	visibility *= 0.7 + 0.3 * sin(frametime * 0.0008);

	// Color variant selection: deterministic per moon phase
	// Phase 3,6: Red | Phase 7: Golden | Phase 4: Silver | Rest: Normal
	float mp = float(moonPhase);
	float isRed    = step(0.5, 1.0 - abs(mp - 3.0)) + step(0.5, 1.0 - abs(mp - 6.0));
	      isRed    = min(isRed, 1.0);
	float isGolden = step(0.5, 1.0 - abs(mp - 7.0));
	float isSilver = step(0.5, 1.0 - abs(mp - 4.0));
	float isNormal = max(1.0 - isRed - isGolden - isSilver, 0.0);

	vec3 lowCol  = auroraLowCol  * isNormal + vec3(0.50, 0.014, 0.038) * isRed + vec3(1.0, 0.30, 0.006) * isGolden + vec3(0.50, 0.61, 1.0) * isSilver;
	vec3 midCol  = auroraMidCol  * isNormal + vec3(1.0, 0.055, 0.22)   * isRed + vec3(1.0, 0.61, 0.038) * isGolden + vec3(0.88, 0.92, 1.0) * isSilver;
	vec3 highCol = auroraHighCol * isNormal + vec3(1.0, 0.35, 0.50)    * isRed + vec3(1.0, 0.88, 0.35)  * isGolden + vec3(0.61, 0.50, 1.0) * isSilver;

	vec2 wind = vec2(
		frametime * CLOUD_SPEED * 0.000125,
		sin(frametime * CLOUD_SPEED * 0.05) * 0.00025
	);
	float time = frametime * CLOUD_SPEED;

	vec3 aurora = vec3(0.0);

	if (VoU > 0.0 && visibility > 0.0) {
		vec3 worldPos = normalize((gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz);
		for(int i = 0; i < samples; i++) {
			vec3 planeCoord = worldPos * ((6.0 + currentStep * 16.0) / worldPos.y) * 0.003;

			vec2 coord = cameraPosition.xz * 0.00004 + planeCoord.xz;
				 coord += vec2(coord.y, -coord.x) * 0.6;

			float noise = AuroraSample(coord, wind, time);

			if (noise > 0.0) {
				noise *= texture2D(noisetex, coord * 0.125 + wind * 0.25).b;
				noise *= texture2D(noisetex, coord + wind * 16.0).b * 0.5 + 0.75;
				noise = pow(noise, 0.85);
				noise = noise * noise * 3.0 * sampleStep;
				noise *= max(sqrt(1.0 - length(planeCoord.xz) * 3.0), 0.0);

				// Temporal pulsation + shimmer
				float pulsation = 0.75 + 0.25 * sin(frametime * 0.0012 + coord.x * 8.0);
				float shimmer = 0.9 + 0.1 * sin(frametime * 0.008 + coord.y * 20.0 + float(i) * 1.7);
				noise *= pulsation * shimmer;

				// 3-stop color gradient with subtle hue shift
				float t = pow(currentStep, 0.4);
				vec3 auroraColor = t < 0.5
					? mix(lowCol, midCol, t * 2.0)
					: mix(midCol, highCol, (t - 0.5) * 2.0);
				auroraColor = mix(auroraColor, auroraColor.gbr, 0.05 * sin(frametime * 0.001));

				// Bottom-lit depth falloff
				float depthFade = exp2(-6.0 * float(i) * sampleStep) * (1.0 + 0.5 * (1.0 - currentStep));
				aurora += noise * auroraColor * depthFade;
			}
			currentStep += sampleStep;
		}
	}

	visibility *= clamp((cameraPosition.y + DEEP_UNDERGROUND_Y_NEW) / DEEP_UNDERGROUND_RANGE, 0.0, 1.0);

	#ifdef UNDERGROUND_SKY
	visibility *= mix(clamp((cameraPosition.y - UNDERGROUND_SKY_START) / UNDERGROUND_SKY_RANGE, 0.0, 1.0), 1.0, eBS);
	#endif

	return aurora * visibility * 1.15;
}
#endif
#endif

float InterleavedGradientNoise1() {
	float n = IGN_FACTOR * fract(IGN_COEFF_X * gl_FragCoord.x + IGN_COEFF_Y * gl_FragCoord.y);

	return fract(n);
}

#ifdef OVERWORLD_NEBULA
float nebulaSample(vec2 coord, vec2 wind, float VoU) {
	float noise = texture2D(noisetex, coord * 1.0000 - wind * 0.25).b * 2.5;
		  noise-= texture2D(noisetex, coord * 0.5000 + wind * 0.20).b * 1.5;
		  noise+= texture2D(noisetex, coord * 0.2500 - wind * 0.15).b * 3.0;
		  noise+= texture2D(noisetex, coord * 0.1250 + wind * 0.10).b * 3.5;

	noise *= NEBULA_AMOUNT;

	noise = max(1.0 - 2.0 * (0.5 * VoU + 0.5) * abs(noise - 3.5), 0.0);

	return noise;
}

void erodeCoord(inout vec2 coord, in float currentStep, in float erosionStrength){
	coord += cos(mix(vec2(cos(currentStep * 1.00), sin(currentStep * 2.00)), vec2(cos(currentStep * 3.0), sin(currentStep * 4.00)), currentStep) * erosionStrength);
}

vec3 DrawNebula(vec3 viewPos) {
	float VoU = dot(normalize(viewPos.xyz), upVec);
	float visFactor = (1.0 - rainStrength) * sqrt(moonVisibility);

	// Rarity system: deterministic nebula nights based on moon phase
	#if NEBULA_RARITY == 1
	if (moonPhase == 1 || moonPhase == 5) visFactor = 0.0;
	#elif NEBULA_RARITY == 2
	if (moonPhase != 1 && moonPhase != 4 && moonPhase != 6) visFactor = 0.0;
	#elif NEBULA_RARITY == 3
	if (moonPhase != 2 && moonPhase != 6) visFactor = 0.0;
	#endif

	// Color variant selection: deterministic per moon phase
	// Phase 2,5: Warm (amber/orange) | Phase 1: Rose (pink/magenta) | Phase 7: Teal (cyan) | Rest: Normal (blue/purple)
	float mp = float(moonPhase);
	float isWarm = step(0.5, 1.0 - abs(mp - 2.0)) + step(0.5, 1.0 - abs(mp - 5.0));
	      isWarm = min(isWarm, 1.0);
	float isRose   = step(0.5, 1.0 - abs(mp - 1.0));
	float isTeal   = step(0.5, 1.0 - abs(mp - 7.0));
	float isNormal = max(1.0 - isWarm - isRose - isTeal, 0.0);

	vec3 nebLowCol  = nebulaLowCol  * isNormal + vec3(0.08, 0.02, 0.001)  * isWarm + vec3(0.08, 0.004, 0.04) * isRose + vec3(0.004, 0.04, 0.06)  * isTeal;
	vec3 nebMidCol  = mix(nebulaLowCol, nebulaHighCol, 0.5) * isNormal + vec3(0.10, 0.04, 0.005) * isWarm + vec3(0.10, 0.01, 0.07)  * isRose + vec3(0.006, 0.07, 0.09)  * isTeal;
	vec3 nebHighCol = nebulaHighCol * isNormal + vec3(0.10, 0.06, 0.01)  * isWarm + vec3(0.10, 0.02, 0.08) * isRose + vec3(0.01, 0.08, 0.10)   * isTeal;

	vec3 nebula = vec3(0.0);
	vec3 nebulaColor = vec3(0.0);

	if (visFactor > 0 && VoU > 0){
		float dither = InterleavedGradientNoise1();

		#ifdef TAA
		dither = fract(16.0 * frameTimeCounter + dither);
		#endif

		int samples = 5;
		float sampleStep = 1.0 / samples;
		float currentStep = dither * sampleStep;

		vec2 wind = vec2(
			frametime * NEBULA_SPEED * 0.000125,
			sin(frametime * NEBULA_SPEED * 0.05) * 0.00125
		);

		vec3 worldPos = normalize((gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz);
		for(int i = 0; i < samples; i++) {
			vec3 planeCoord = worldPos * ((6.0 + currentStep * -2.0) / worldPos.y) * 0.005 * NEBULA_STRETCHING;

			vec2 coord = (cameraPosition.xz * 0.000005 + planeCoord.xz);

			erodeCoord(coord, currentStep, 0.1);
			erodeCoord(coord, currentStep, 0.2);

			float noise = nebulaSample(coord, wind, VoU);
				 noise *= texture2D(noisetex, coord * 0.25 + wind * 0.25).r;
			float colorNoise = texture2D(noisetex, coord + wind * 16.0).r;
				 noise *= colorNoise + 0.75;
				 noise = noise * noise * 2.0 * sampleStep;
				 noise *= max(sqrt(1.0 - length(planeCoord.xz) * 4.0), 0.0);

			// 3-stop gradient with noise-driven variation
			float t = clamp(currentStep + (colorNoise - 0.5) * 0.7, 0.0, 1.0);
			nebulaColor = t < 0.5
				? mix(nebLowCol, nebMidCol, t * 2.0)
				: mix(nebMidCol, nebHighCol, (t - 0.5) * 2.0);
			// Hue shift across the nebula for visible color variety
			float hueShift = 0.25 * sin(coord.x * 12.0 + coord.y * 8.0) + 0.1 * sin(coord.x * 25.0 - coord.y * 15.0);
			nebulaColor = mix(nebulaColor, nebulaColor.gbr, hueShift);

			nebula += noise * nebulaColor * exp2(-4.0 * i * sampleStep);
			currentStep += sampleStep;
		}
	}

	#if defined UNDERGROUND_SKY && defined OVERWORLD
	nebula *= mix(clamp((cameraPosition.y - UNDERGROUND_SKY_START) / UNDERGROUND_SKY_RANGE, 0.0, 1.0), 1.0, eBS);
	#endif

	return nebula * NEBULA_BRIGHTNESS * visFactor * 1.1;
}
#endif

#ifdef END_NEBULA
float nebulaSample2(vec2 coord, vec2 wind, float VoU) {
	#ifdef OVERWORLD
	coord *= 2.0;
	#endif

	float noise = texture2D(noisetex, coord * 1.0000 - wind * 0.25).b * 2.5;
		  noise-= texture2D(noisetex, coord * 0.5000 + wind * 0.20).b * 1.5;
		  noise+= texture2D(noisetex, coord * 0.2500 - wind * 0.15).b * 3.0;
		  noise+= texture2D(noisetex, coord * 0.1250 + wind * 0.10).b * 3.5;

	noise *= NEBULA_AMOUNT;
	
	#ifdef END
	noise *= 1.4;
	noise = pow(noise, 1.5) * END_RIFT_INTENSITY * 0.8;
	#endif

	#ifdef END
	float riftShape = abs(noise - 3.5);
	riftShape = 1.0 - smoothstep(0.0, 0.3, riftShape);
	noise = riftShape * (1.0 - VoU * 0.3);
	#else
	noise = max(1.0 - 2.0 * (0.5 * VoU + 0.5) * abs(noise - 3.5), 0.0);
	#endif

	return noise;
}

vec3 DrawEndNebula(vec3 viewPos) {
	int samples = 8;

	float dither = InterleavedGradientNoise1();

	#ifdef TAA
	dither = fract(16.0 * frameTimeCounter + dither);
	#endif

	float VoU = abs(dot(normalize(viewPos.xyz), upVec));

	float sampleStep = 1.0 / samples;
	float currentStep = dither * sampleStep;

	vec2 wind = vec2(
		frametime * END_RIFT_SPEED * 0.000125,
		sin(frametime * END_RIFT_SPEED * 0.05 + 3.14) * 0.002
	);

	vec3 nebula = vec3(0.0);
	vec3 nebulaColor = vec3(0.0);

	vec3 wpos = normalize((gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz);
	for(int i = 0; i < samples; i++) {
		vec3 planeCoord = wpos * (16.0 + currentStep * -2.0) * 0.00025 * NEBULA_STRETCHING;
		vec2 coord = (cameraPosition.xz * 0.000001 + planeCoord.xz);
		coord += vec2(coord.y, -coord.x) * 8.00 * NEBULA_DISTORTION;

		erodeCoord(coord, currentStep, 0.175);

		float noise = nebulaSample2(coord, wind, VoU);
			 noise *= texture2D(noisetex, coord * 0.5 + wind * 0.25).r;
			 noise *= texture2D(noisetex, coord * 1.5 + wind * 16.0).r + 0.75;
			 noise = noise * noise * sampleStep;
			 noise *= max(sqrt(1.0 - length(planeCoord.xz) * 4.0), 0.0);

		float edgeFactor = pow(currentStep, 0.5);
		nebulaColor = mix(
			endVoidCol * 2.0,
			endRiftCol * END_RIFT_INTENSITY,
			edgeFactor
		);

		nebula += noise * nebulaColor * exp2(-12.0 * i * sampleStep);
		currentStep += sampleStep;
	}

	return nebula * NEBULA_BRIGHTNESS * (1.0 - END_VOID_DARKNESS * 0.5);
}
#endif

#ifdef END_STARS
void DrawEndStars(inout vec3 color, vec3 viewPos) {
	vec3 worldPos = vec3(gbufferModelViewInverse * vec4(viewPos * 100.0, 1.0));
	vec2 planeCoord0 = worldPos.xz / (length(worldPos.y) + length(worldPos.xz));
		planeCoord0 += cameraPosition.xz * 0.00001;
		planeCoord0 += frameTimeCounter * 0.0001;
		planeCoord0 = floor(planeCoord0 * 600.0) / 600.0;

	vec2 planeCoord1 = worldPos.xz / (length(worldPos.y) + length(worldPos.xz));
		planeCoord1 *= 0.35;
		planeCoord1 += cameraPosition.xz * 0.00001;
		planeCoord1 += frameTimeCounter * 0.0001;
		planeCoord1 = floor(planeCoord1 * 1000.0) / 1000.0;
	
	float star = 1.0;

	float smallStars = getNoise(planeCoord0 + 10.0);
			smallStars*= getNoise(planeCoord0 + 14.0);
			smallStars = clamp(smallStars - 0.75, 0.0, 1.0);
			smallStars *= 7;
			smallStars *= smallStars * 0.85;

	float bigStars = getNoise(planeCoord1 + 11.0);
			bigStars*= getNoise(planeCoord1 + 13.0);
			bigStars*= getNoise(planeCoord1 + 15.0);
			bigStars = clamp(bigStars - 0.75, 0.0, 1.0);
			bigStars *= 21;
			bigStars *= bigStars * 2.0;

	star = smallStars + bigStars;
	
	star = clamp(star - END_STAR_SPARSITY, 0.0, 1.0);

	float flicker = fract(sin(dot(planeCoord0, vec2(41.13, 73.97)) + frameTimeCounter * 0.7) * 43758.5);
	flicker = smoothstep(0.3, 0.7, flicker);
	star *= 0.5 + 0.5 * flicker;

	vec3 starColor = mix(vec3(dot(endCol.rgb, vec3(0.299, 0.587, 0.114))), endCol.rgb, 0.2);
	color += star * pow(starColor, vec3(1.2));
}
#endif
