/*
 * Visuality thin-mist godrays
 *
 * Raymarches from the camera to each fragment through the existing shadow
 * map, so light only builds up where the sun can actually reach along that
 * path: through gaps in leaves, between trunks, over a ridge line, and so
 * on. Density is kept low and the phase function broad on purpose, so the
 * result reads as a soft hazy mist with faint shafts breaking through it,
 * rather than sharp cinematic sunbeams.
 *
 * The per-pixel dither on the starting sample is smoothed out by the TAA
 * pass in composite1.fsh that runs right after this, which is what keeps
 * the raymarch looking soft instead of banded/stepped even at a modest
 * sample count.
 *
 * Requires (already available wherever this is included from composite.fsh):
 *   gbufferModelViewInverse, shadowModelView, shadowProjection, cameraPosition
 *   (from lib/projections.glsl, along with the diagonal3() macro)
 *   sunVec, sunColor, sunIntensity, rainStrength (standard pipeline uniforms)
 *   SEA_LEVEL, ATMOSPHERIC_DENSITY (defined in composite.fsh)
 */

#define GODRAYS
#define GODRAY_STRENGTH 1.8 //[0.0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 2.0 2.5 3.0 3.5 4.0 5.0]
#define GODRAY_MISTINESS 0.70 //[0.0 0.15 0.3 0.45 0.6 0.75 0.9 1.0]
#define GODRAY_SAMPLES 10 //[6 8 10 12 16 20]
#define GODRAY_DISTANCE 100.0 //[48.0 64.0 80.0 100.0 128.0 160.0]
// Phase-specific godray tuning. Day is subtle, twilight is thick and hazy,
// and night uses soft blue moonrays.
#define GODRAY_DAY_INTENSITY 0.30 //[0.00 0.10 0.20 0.30 0.40 0.50 0.65 0.80 1.00]
#define GODRAY_TWILIGHT_INTENSITY 1.30 //[0.50 0.70 0.90 1.10 1.30 1.50 1.75 2.00]
#define GODRAY_NIGHT_INTENSITY 1.10 //[0.00 0.08 0.12 0.18 0.24 0.32 0.45 0.60]
#define GODRAY_DAY_MISTINESS_SCALE 0.45 //[0.20 0.30 0.40 0.45 0.55 0.65 0.80 1.00]
#define GODRAY_TWILIGHT_MISTINESS_SCALE 1.15 //[0.70 0.85 1.00 1.15 1.30 1.50]
#define GODRAY_NIGHT_MISTINESS_SCALE 1.55 //[0.70 0.85 1.00 1.15 1.30 1.50]
#define GODRAY_NIGHT_SHADOW_STRENGTH 0.40 //[0.00 0.25 0.50 0.65 0.75 0.85 1.00]

const int godrayMapRes = 2048;
const float GODRAY_SHADOW_BIAS = 0.8;

/* Same non-linear shadow-map warp used by the direct lighting passes, so
 * samples taken here line up with the same shadow map they wrote to. */
float godrayDistort(vec2 worldXZ){
	vec2 pos = worldXZ * 1.165;
	vec2 posSQ = pos*pos;
	float distb = pow(posSQ.x*posSQ.x*posSQ.x + posSQ.y*posSQ.y*posSQ.y, 1.0/6.0);
	return 1.08695652/((1.0-GODRAY_SHADOW_BIAS)+distb*GODRAY_SHADOW_BIAS);
}

float godrayDayWeight(float elevation){
	return smoothstep(0.22, 0.62, elevation);
}

float godrayNightWeight(float elevation){
	return 1.0 - smoothstep(-0.12, 0.10, elevation);
}

float godrayTwilightWeight(float elevation){
	return clamp(1.0 - godrayDayWeight(elevation) - godrayNightWeight(elevation), 0.0, 1.0);
}

#ifdef GODRAYS
vec3 calcGodrays(vec3 fragpos, float dist, float dither, sampler2DShadow shadowTex){
	float dayWeight = godrayDayWeight(sunElevation);
	float twilightWeight = godrayTwilightWeight(sunElevation);
	float nightWeight = godrayNightWeight(sunElevation);

	// Sun rays are strongest around sunrise/sunset, intentionally restrained at noon.
	float directLight = clamp(sunIntensity * 3.5, 0.0, 1.0) * (dayWeight + twilightWeight);
	// Moon rays are independent from sun color and always use the opposite solar direction at night.
	float moonFactor = clamp(moonIntensity * 2.5, 0.0, 1.0) * nightWeight;
	float lightFactor = clamp(max(directLight, moonFactor), 0.0, 1.0);
	if (lightFactor <= 0.001) return vec3(0.0);

	float phaseIntensity = dayWeight * GODRAY_DAY_INTENSITY
	                    + twilightWeight * GODRAY_TWILIGHT_INTENSITY
	                    + nightWeight * GODRAY_NIGHT_INTENSITY;
	float phaseMistiness = clamp(GODRAY_MISTINESS
	                    * (dayWeight * GODRAY_DAY_MISTINESS_SCALE
	                     + twilightWeight * GODRAY_TWILIGHT_MISTINESS_SCALE
	                     + nightWeight * GODRAY_NIGHT_MISTINESS_SCALE), 0.0, 1.0);
	// Night uses the same thick atmospheric mist profile as twilight, but with a softer blue moonlight intensity.
	float densityScale = dayWeight * 0.45 + twilightWeight * 1.55 + nightWeight * 2.05;
	float phaseDistance = dayWeight * 0.70 + twilightWeight * 1.10 + nightWeight * 1.10;

	// At night the light direction flips to the moon side, so the shafts do not keep the orange sunset direction.
	vec3 lightDir = normalize(mix(sunVec, -sunVec, nightWeight));
	vec3 moonRayColor = vec3(0.18, 0.30, 0.58);
	vec3 rayLightColor = mix(sunColor, moonRayColor, nightWeight);

	float rayLength = min(dist, GODRAY_DISTANCE * phaseDistance);
	if (rayLength <= 0.5) return vec3(0.0);

	vec3 rayDir = fragpos/max(dist,0.001);

	/* Soft forward-scatter. The direction becomes broader during twilight and night,
	 * while the density controls how much hazy volume is visible. */
	float VdotL = clamp(dot(rayDir, lightDir), -1.0, 1.0);
	const float g = 0.45;
	float phase = (1.0-g*g)/pow(1.0+g*g-2.0*g*VdotL, 1.5);
	float scatterShape = mix(phase, 1.0, phaseMistiness);
	float nightScatterFloor = nightWeight * 0.38;
	scatterShape = max(scatterShape, nightScatterFloor);

	/* Thin/medium haze density: deliberately lower at noon and much thicker
	 * around sunrise/sunset, where low-angle light catches atmospheric moisture. */
	float altitudeFactor = clamp(1.0-max(cameraPosition.y-SEA_LEVEL,0.0)*0.0018, 0.35, 1.0);
	float density = 0.022*ATMOSPHERIC_DENSITY*altitudeFactor*densityScale;

	float stepSize = rayLength/float(GODRAY_SAMPLES);
	float extinctionPerStep = exp(-stepSize*density);
	float rayStart = stepSize*dither;

	float transmittance = 1.0;
	float accumulated = 0.0;

	for (int i = 0; i < GODRAY_SAMPLES; i++){
		float currentDist = rayStart+stepSize*float(i);
		vec3 samplePos = rayDir*currentDist;

		vec3 shadowPos = toShadowSpaceProjected(samplePos);

		float distortFactor = godrayDistort(shadowPos.xy);
		shadowPos.xy *= distortFactor;
		shadowPos = shadowPos*vec3(0.5,0.5,0.5/3.0)+vec3(0.5,0.5,0.5);

		float sunVisible = 1.0;
		if (abs(shadowPos.x*2.0-1.0) < 1.0-1.5/float(godrayMapRes) && abs(shadowPos.y*2.0-1.0) < 1.0-1.5/float(godrayMapRes)){
			sunVisible = shadow2D(shadowTex, shadowPos).x;
		}
		// Keep shadowing active at night too. The shadow map follows the active celestial
		// light path, so moonrays can still break into bright and dark shafts.
		float nightShadowMix = nightWeight * GODRAY_NIGHT_SHADOW_STRENGTH;
		sunVisible = mix(1.0, sunVisible, max(1.0-nightWeight, nightShadowMix));
		float moonBaseline = 0.58;
		sunVisible = mix(sunVisible, max(sunVisible, moonBaseline), nightWeight);

		float stepContribution = (1.0-extinctionPerStep)*transmittance;
		accumulated += sunVisible*stepContribution;
		transmittance *= extinctionPerStep;
	}

	float nightBoost = 1.0 + nightWeight * 1.55;
	return rayLightColor*(accumulated*scatterShape*0.22*GODRAY_STRENGTH*phaseIntensity*lightFactor*nightBoost);
}
#endif

#ifdef GODRAYS
// Efficient night-only godray path. This is deliberately independent of the
// shadow map and moonIntensity so a dark shadow map or night exposure cannot
// switch the moonrays off. It uses an analytic phase term instead of a second
// raymarch, keeping the extra cost very small.
vec3 calcNightGodrays(vec3 fragpos, float dist, float nightWeight, float timeCounter){
	if (nightWeight <= 0.001 || dist <= 0.5) return vec3(0.0);

	vec3 viewDir = normalize(fragpos);
	vec3 moonDir = normalize(-sunVec);
	float VdotM = clamp(dot(viewDir, moonDir), -1.0, 1.0);

	// Broad forward scatter plus a narrow shaft core.
	const float g = 0.32;
	float phase = (1.0-g*g)/pow(1.0+g*g-2.0*g*VdotM, 1.5);
	float broad = mix(phase, 1.0, 0.52);
	float shaft = pow(max(VdotM, 0.0), 10.0);

	float viewHeight = clamp(1.0 - abs(viewDir.y)*0.55, 0.25, 1.0);
	float distanceBuild = 1.0 - exp(-dist * 0.026);
	float thinMotion = 0.92 + 0.08*sin(timeCounter*0.32 + fragpos.x*0.018 + fragpos.z*0.013);
	float strength = nightWeight * (0.022*broad + 0.065*shaft) * distanceBuild * viewHeight * thinMotion;

	return vec3(0.34, 0.52, 1.0) * strength;
}
#endif

/*
 * Underwater light shafts: a denser, broader variant of the normal godray
 * marcher.  The ray always starts at the camera (view-space origin, i.e. the
 * player's eye) and steps toward the fragment, so it is inherently anchored
 * to wherever the player/camera actually is.
 *
 * Previously the shaft brightness was weighted using an ad-hoc combination
 * of "looking upward" and "looking toward the sun" dot products. That made
 * the brightest part of the effect track the camera's *view direction*
 * instead of the real sun/moon position, which is what produced the
 * "effect position doesn't match camera/player/sun" mismatch. This version
 * reuses the exact same sun-elevation weighting and light direction as the
 * outdoor godrays above, so the underwater shaft always lines up with where
 * the sun or moon actually is in the sky, exactly like the shadows on
 * terrain do.
 */
#define UNDERWATER_GODRAYS
#define UNDERWATER_GODRAY_STRENGTH 2.2 //[0.0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 2.0 2.5 3.0 3.5 4.0 5.0]
#define UNDERWATER_GODRAY_SAMPLES 14 //[8 10 12 14 16 20 24]
#define UNDERWATER_GODRAY_DISTANCE 80.0 //[32.0 48.0 64.0 80.0 96.0 128.0]
// Colour of sunlight once it has entered the water - kept light blue rather
// than the raw daylight colour, per the requested look.
#define UNDERWATER_GODRAY_COLOR vec3(0.45, 0.78, 1.00)
#define UNDERWATER_GODRAY_COLOR_MIX 0.80 //[0.0 0.2 0.4 0.5 0.6 0.7 0.8 0.9 1.0]

#ifdef UNDERWATER_GODRAYS
vec3 calcUnderwaterGodrays(vec3 fragpos, float dist, float dither, sampler2DShadow shadowTex){
    // Same day/twilight/night weighting as the outdoor godrays, so the two
    // effects always agree on where the sun/moon currently is.
    float dayWeight = godrayDayWeight(sunElevation);
    float twilightWeight = godrayTwilightWeight(sunElevation);
    float nightWeight = godrayNightWeight(sunElevation);

    float directLight = clamp(sunIntensity*4.0, 0.0, 1.0) * (dayWeight + twilightWeight);
    float moonFactor = clamp(moonIntensity*3.0, 0.0, 1.0) * nightWeight;
    float dayFactor = clamp(max(directLight, moonFactor), 0.0, 1.0);
    if (dayFactor <= 0.001) return vec3(0.0);

    float rayLength = min(dist, UNDERWATER_GODRAY_DISTANCE);
    if (rayLength <= 0.5) return vec3(0.0);

    // Ray starts at the camera/player eye (view-space origin) and marches
    // toward the fragment - this is the actual camera/player anchor point.
    vec3 rayDir = fragpos/max(dist,0.001);

    // Light comes from the sun in the day, and flips to the opposite
    // (moonlit) direction at night, matching the outdoor shafts.
    vec3 lightDir = normalize(mix(sunVec, -sunVec, nightWeight));

    const float g = 0.35;
    float VdotL = clamp(dot(rayDir, lightDir), -1.0, 1.0);
    float phase = (1.0-g*g)/pow(1.0+g*g-2.0*g*VdotL, 1.5);
    float scatterShape = mix(phase, 1.0, 0.55);

    float density = 0.045;
    float stepSize = rayLength/float(UNDERWATER_GODRAY_SAMPLES);
    float extinctionPerStep = exp(-stepSize*density);
    float rayStart = stepSize*dither;

    float transmittance = 1.0;
    float accumulated = 0.0;

    for (int i = 0; i < UNDERWATER_GODRAY_SAMPLES; i++){
        float currentDist = rayStart+stepSize*float(i);
        vec3 samplePos = rayDir*currentDist;

        vec3 shadowPos = toShadowSpaceProjected(samplePos);
        float distortFactor = godrayDistort(shadowPos.xy);
        shadowPos.xy *= distortFactor;
        shadowPos = shadowPos*vec3(0.5,0.5,0.5/3.0)+vec3(0.5,0.5,0.5);

        float sunVisible = 1.0;
        if (abs(shadowPos.x*2.0-1.0) < 1.0-1.5/float(godrayMapRes) && abs(shadowPos.y*2.0-1.0) < 1.0-1.5/float(godrayMapRes)){
            sunVisible = shadow2D(shadowTex, shadowPos).x;
        }

        float stepContribution = (1.0-extinctionPerStep)*transmittance;
        accumulated += sunVisible*stepContribution;
        transmittance *= extinctionPerStep;
    }

    vec3 waterSunColor = mix(sunColor, UNDERWATER_GODRAY_COLOR, UNDERWATER_GODRAY_COLOR_MIX);
    float distanceBuild = 1.0-exp(-rayLength*0.055);
    return waterSunColor*(accumulated*scatterShape*0.30*UNDERWATER_GODRAY_STRENGTH*dayFactor*distanceBuild);
}
#endif
