// Procedural End atmosphere for NOISE: a quiet, cold void with sparse stars,
// restrained nebula ribbons, and distance haze that preserves island silhouettes.
#ifndef NOISE_END_GLSL
#define NOISE_END_GLSL

vec3 noiseEndAtmosphereColor(vec3 baseFog) {
	vec3 vanillaFog = clamp(baseFog, vec3(0.0), vec3(0.55));
	vec3 voidViolet = vec3(0.035, 0.022, 0.090);
	vec3 inheritedColor = vanillaFog * vec3(0.66, 0.72, 0.92);
	float fogPresence = smoothstep(0.01, 0.18, max(vanillaFog.r, max(vanillaFog.g, vanillaFog.b)));
	return clamp(mix(voidViolet, max(inheritedColor, vec3(0.012, 0.010, 0.026)),
		fogPresence * 0.42), vec3(0.008), vec3(0.28, 0.25, 0.42));
}

float noiseEndStars(vec3 worldDirection, float frameTimeCounter) {
	vec3 direction = noiseSafeNormalize(worldDirection, vec3(0.0, 1.0, 0.0));
	vec2 sphereUv = vec2(
		atan(direction.z, direction.x) * 0.15915494309 + 0.5,
		asin(clamp(direction.y, -1.0, 1.0)) * 0.31830988618 + 0.5
	);
	vec2 starGrid = vec2(720.0, 360.0);
	vec2 gridPosition = sphereUv * starGrid;
	vec2 cell = floor(gridPosition);
	vec2 localPosition = fract(gridPosition);
	float seed = noiseHash(cell + vec2(19.17, 73.41));
	vec2 starPosition = vec2(
		noiseHash(cell + vec2(4.73, 21.11)),
		noiseHash(cell + vec2(41.29, 7.13))
	);
	float starRadius = mix(0.055, 0.14, noiseHash(cell + vec2(8.91, 54.37)));
	float star = 1.0 - smoothstep(starRadius, starRadius * 2.2,
		length(localPosition - starPosition));
	star *= smoothstep(0.984, 0.9995, seed);
	float twinkle = 0.82 + sin(frameTimeCounter * mix(0.45, 1.15, seed)
		+ seed * 31.0) * 0.18;
	return star * twinkle;
}

vec3 noiseEndSkyColor(
	vec3 worldDirection,
	vec3 baseFog,
	float frameTimeCounter
) {
	vec3 direction = noiseSafeNormalize(worldDirection, vec3(0.0, 1.0, 0.0));
	float longitude = atan(direction.z, direction.x);
	float ribbonCenter = sin(longitude * 2.2 + frameTimeCounter * 0.006) * 0.14
		+ sin(longitude * 4.7 - frameTimeCounter * 0.003) * 0.045;
	float ribbon = exp(-abs(direction.y - ribbonCenter) * 7.5);
	float ribbonDetail = sin(longitude * 9.0 + direction.y * 17.0
		+ frameTimeCounter * 0.009) * 0.5 + 0.5;
	ribbon *= mix(0.52, 0.92, ribbonDetail);

	vec3 atmosphere = noiseEndAtmosphereColor(baseFog);
	vec3 sky = vec3(0.0035, 0.0028, 0.012);
	sky += mix(vec3(0.032, 0.010, 0.082), vec3(0.018, 0.045, 0.090),
		ribbonDetail) * ribbon;
	float horizonVeil = 1.0 - smoothstep(0.02, 0.44, abs(direction.y));
	sky = mix(sky, atmosphere * 0.60, horizonVeil * 0.34);

	float rayPattern = pow(max(sin(longitude * 7.0 - frameTimeCounter * 0.004)
		* 0.5 + 0.5, 0.0), 14.0);
	sky += vec3(0.028, 0.010, 0.072) * rayPattern * horizonVeil * 0.42;

	float stars = noiseEndStars(direction, frameTimeCounter);
	sky += mix(vec3(0.58, 0.68, 1.00), vec3(0.92, 0.80, 1.00),
		noiseHash(floor(direction.xz * 117.0))) * stars * 1.35;
	return sky;
}

float noiseEndFogAmount(float distanceFraction, vec3 worldPosition) {
	float distanceStart = max((distanceFraction - 0.045) / 0.955, 0.0);
	float distanceOpticalDepth = pow(distanceStart * 1.08, 1.85) * 0.74;
	float lowerVoid = 1.0 - smoothstep(38.0, 68.0, worldPosition.y);
	float voidHaze = lowerVoid * smoothstep(0.08, 0.86, distanceFraction) * 0.20;
	return clamp(1.0 - exp(-(distanceOpticalDepth + voidHaze)), 0.0, 0.84);
}

vec3 noiseEndGrade(vec3 color) {
	float luminance = dot(color, NOISE_LUMA);
	vec3 restrainedColor = mix(vec3(luminance), color, 0.92);
	vec3 shadowTint = vec3(0.82, 0.84, 1.02);
	vec3 lightTint = vec3(1.02, 0.96, 1.10);
	return restrainedColor * mix(shadowTint, lightTint,
		smoothstep(0.055, 0.88, luminance));
}

#endif
