// Shared colour, atmosphere, and reconstruction helpers for NOISE.
// This file intentionally contains no #version directive.

#ifndef NOISE_COMMON_GLSL
#define NOISE_COMMON_GLSL

const vec3 NOISE_LUMA = vec3(0.2126, 0.7152, 0.0722);

float noiseSaturate(float value) {
	return clamp(value, 0.0, 1.0);
}

vec3 noiseSafeNormalize(vec3 value, vec3 fallbackValue) {
	float lengthSquared = dot(value, value);
	return lengthSquared > 1e-10 ? value * inversesqrt(lengthSquared) : fallbackValue;
}

float noiseDaylight(vec3 sunPosition, mat4 modelView) {
	#if defined(NETHER) || defined(END)
	return 0.0;
	#else
	vec3 safeSun = noiseSafeNormalize(sunPosition, vec3(0.0, -1.0, 0.0));
	vec3 safeUp = noiseSafeNormalize(modelView[1].xyz, vec3(0.0, 1.0, 0.0));
	float sunHeight = dot(safeSun, safeUp);
	return smoothstep(-0.075, 0.16, sunHeight);
	#endif
}

float noiseNight(float daylight) {
	return 1.0 - smoothstep(0.03, 0.42, daylight);
}

float noiseLocalLight(vec2 lightmapUv) {
	return smoothstep(0.12, 0.90, clamp(lightmapUv.x, 0.0, 1.0));
}

vec3 noiseApplyLightmap(vec3 baseColor, vec3 lightmapColor, float localLight, vec3 heldLighting) {
	#ifdef NETHER
	vec3 lightTint = mix(vec3(0.66, 0.48, 0.40), vec3(1.15, 0.82, 0.48), localLight);
	float lightContrast = mix(0.76, 1.66, pow(localLight, 1.62));
	#elif defined(END)
	vec3 lightTint = mix(vec3(0.64, 0.66, 0.88), vec3(1.05, 0.86, 0.70), localLight);
	float lightContrast = mix(0.78, 1.58, pow(localLight, 1.64));
	#else
	vec3 lightTint = mix(vec3(0.66, 0.75, 0.90), vec3(1.12, 0.94, 0.68), localLight);
	float lightContrast = mix(0.68, 1.65, pow(localLight, 1.7));
	#endif
	vec3 worldLighting = lightmapColor * lightTint * lightContrast;
	// Combine independent light sources as energy before applying the material.
	// Directly adding heldLighting to the finished pixel produced the white/orange
	// veil; multiplying the combined light by albedo keeps texture contrast intact.
	vec3 combinedLighting = sqrt(worldLighting * worldLighting + heldLighting * heldLighting);
	return baseColor * combinedLighting;
}

vec3 noiseApplyLightmap(vec3 baseColor, vec3 lightmapColor, float localLight) {
	return noiseApplyLightmap(baseColor, lightmapColor, localLight, vec3(0.0));
}

vec3 noiseDirectionalTint(float daylight) {
	#ifdef NETHER
	return vec3(0.92, 0.79, 0.70);
	#elif defined(END)
	return vec3(0.87, 0.88, 1.06);
	#else
	return mix(vec3(0.86, 0.91, 1.00), vec3(1.08, 1.015, 0.90), daylight);
	#endif
}

float noiseDirectionalLight(vec3 viewNormal, vec3 lightPosition, float daylight, float shadowVisibility) {
	vec3 safeNormal = noiseSafeNormalize(viewNormal, vec3(0.0, 1.0, 0.0));
	vec3 safeLight = noiseSafeNormalize(lightPosition, vec3(0.0, 1.0, 0.0));
	float facingLight = max(dot(safeNormal, safeLight), 0.0);
	#ifdef END
	return 0.58 + facingLight * 0.62 * shadowVisibility;
	#else
	return mix(0.70 + facingLight * 0.10, 0.61 + facingLight * 0.78 * shadowVisibility, daylight);
	#endif
}

vec3 noiseDirectionalLightColored(vec3 viewNormal, vec3 lightPosition, float daylight, vec3 shadowTransmission) {
	vec3 safeNormal = noiseSafeNormalize(viewNormal, vec3(0.0, 1.0, 0.0));
	vec3 safeLight = noiseSafeNormalize(lightPosition, vec3(0.0, 1.0, 0.0));
	float facingLight = max(dot(safeNormal, safeLight), 0.0);
	#ifdef END
	return vec3(0.58) + facingLight * 0.62
		* clamp(shadowTransmission, vec3(0.0), vec3(1.0));
	#else
	vec3 nightLighting = vec3(0.70 + facingLight * 0.10);
	vec3 dayLighting = vec3(0.61) + facingLight * 0.78 * clamp(shadowTransmission, vec3(0.0), vec3(1.0));
	return mix(nightLighting, dayLighting, daylight);
	#endif
}

vec3 noiseHeldItemColor(int itemId) {
	if (itemId == 111) return vec3(0.16, 0.48, 1.00);
	if (itemId == 112) return vec3(1.00, 0.78, 0.44);
	if (itemId == 113) return vec3(1.00, 0.30, 0.055);
	return vec3(1.00, 0.64, 0.32);
}

vec3 noiseHeldLighting(
	vec3 playerPosition,
	vec3 relativeEyePosition,
	int mainLightValue,
	int mainItemId,
	int offLightValue,
	int offItemId
) {
	float mainStrength = clamp(float(mainLightValue) / 15.0, 0.0, 1.0);
	float offStrength = clamp(float(offLightValue) / 15.0, 0.0, 1.0);
	if (max(mainStrength, offStrength) <= 0.0) return vec3(0.0);

	// playerPosition is camera-relative player space. Iris' offset cancels view
	// bobbing and third-person camera movement, leaving the lamp on the player.
	vec3 fromPlayer = playerPosition + relativeEyePosition;
	fromPlayer.y += 0.70;
	float lightDistanceSquared = dot(fromPlayer, fromPlayer);
	float lightDistance = sqrt(lightDistanceSquared);
	// Match the useful reach of Minecraft's placed block light. The previous
	// inverse-square curve technically reached ten blocks, but after material
	// response and tonemapping it became imperceptible beyond roughly three.
	// Keep a soft near field and retain enough energy through blocks 4-8.
	float falloff = pow(1.0 / (1.0 + lightDistanceSquared * 0.040), 1.08);
	float sourceStrength = max(mainStrength, offStrength);
	float usefulRange = mix(2.5, 8.5, sourceStrength);
	float rangeFade = 1.0 - smoothstep(usefulRange - 1.25, usefulRange + 1.0, lightDistance);

	// Like Hysteria, prefer the brighter colour per channel rather than adding
	// two full-strength lamps and accidentally doubling the exposure.
	vec3 mainLighting = noiseHeldItemColor(mainItemId) * mainStrength;
	vec3 offLighting = noiseHeldItemColor(offItemId) * offStrength;
	vec3 sourceLighting = max(mainLighting, offLighting);
	return sourceLighting * falloff * rangeFade * 0.98;
}

vec3 noiseAtmosphereColor(vec3 baseFog, float daylight, float rainStrength) {
	#ifdef NETHER
	vec3 vanillaFog = clamp(baseFog, vec3(0.0), vec3(0.72));
	vec3 infernalBase = vec3(0.150, 0.034, 0.018);
	vec3 biomeColor = vanillaFog * vec3(0.56, 0.58, 0.60);
	float biomePresence = smoothstep(0.015, 0.22, max(vanillaFog.r, max(vanillaFog.g, vanillaFog.b)));
	return mix(infernalBase, max(biomeColor, vec3(0.016, 0.008, 0.006)), biomePresence * 0.52);
	#elif defined(END)
	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 mix(voidViolet, max(inheritedColor, vec3(0.012, 0.010, 0.026)), fogPresence * 0.42);
	#else
	// Keep clear air blue rather than white: the previous values made the
	// horizon read as a permanent overexposed veil in clear weather.
	vec3 clearDay = vec3(0.43, 0.60, 0.75);
	vec3 overcast = vec3(0.48, 0.56, 0.65);
	vec3 night = vec3(0.060, 0.070, 0.085);
	vec3 dayFog = mix(clearDay, overcast, rainStrength);
	vec3 nightFog = mix(baseFog, night, 0.82);
	return mix(nightFog, dayFog, daylight);
	#endif
}

vec3 noiseFilmic(vec3 value) {
	// Neutral ACES-like curve with a gentle shoulder for Minecraft's bright blocks.
	const float a = 2.51;
	const float b = 0.03;
	const float c = 2.43;
	const float d = 0.59;
	const float e = 0.14;
	return clamp((value * (a * value + b)) / (value * (c * value + d) + e), 0.0, 1.0);
}

float noiseHash(vec2 p) {
	p = fract(p * vec2(123.34, 456.21));
	p += dot(p, p + 45.32);
	return fract(p.x * p.y);
}

#endif
