#version 130
#include "commonSettings.glsl"

#define EMISSIVE_BASE_THRESHOLD 0.7        // Base brightness threshold [0.2 0.3 0.4 0.5 0.6]
#define EMISSIVE_SATURATION_BOOST 0.5      // How much saturation lowers threshold [0.1 0.2 0.3 0.4 0.5]
#define EMISSIVE_WARM_COLOR_BOOST 0.05     // Extra boost for warm colors (fire, torches) [0.05 0.1 0.15 0.2 0.25]
#define EMISSIVE_COOL_COLOR_BOOST 0.4      // Extra boost for cool colors (soul fire, etc) [0.1 0.15 0.2 0.25 0.3]
#define EMISSIVE_BROWN_PENALTY 0.0        // Penalty for brown/wood colors [0.1 0.15 0.2 0.25 0.3]
#define EMISSIVE_GRAY_THRESHOLD 0.005814
#define EMISSIVE_GRAY_PENALTY 0.3
#define EMISSIVE_BOOST_STRENGTH 0.5 // How much brighter emissive parts should be [0.0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 2.0]

#define VORONOI_BLOCK_DISTORTION 1 // Enable voronoi block distortion [0 1]

uniform sampler2D noisetex;
uniform sampler2D texture;
uniform sampler2D lightmap;
uniform sampler2D depthtex0;
uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferModelView;
uniform mat4 shadowModelView;
uniform mat4 shadowProjection;  
uniform mat4 shadowModelViewInverse;
uniform mat4 shadowProjectionInverse;
uniform vec4 entityColor;
uniform vec3 shadowLightPosition;
uniform vec3 skyColor;
uniform vec3 cameraPosition;
uniform ivec2 atlasSize;
uniform float blindness;
uniform int isEyeInWater;
uniform int heldItemId;
uniform int heldBlockLightValue;
uniform vec2 texelSize;
uniform float viewWidth;
uniform float viewHeight;
uniform sampler2D shadowcolor0;
uniform sampler2D shadowtex0;
uniform sampler2D shadowtex1;
uniform float frameTimeCounter;
uniform float rainStrength;
uniform vec3 upPosition;
uniform vec3 fogColor;
uniform float far;
uniform float sunAngle;
uniform ivec2 eyeBrightnessSmooth; // .y = sky light at eye: 240 = open sky, 0 = underground

varying vec4 color;
varying vec3 shadowPos;
varying vec3 world;
varying vec3 vert;
varying vec2 coord0;
varying vec2 coord1;
varying float id;
varying vec3 vPos;
varying vec4 N;
varying float emissiveFlag;

// Shadow sampling array https://github.com/jhk2/glsandbox/blob/master/kgl/samples/shadow/pcss.glsl
const vec2 offsets[64] = vec2[64](
    vec2(-0.04117257, -0.1597612),
    vec2(0.06731031, -0.4353096),
    vec2(-0.206701, -0.4089882),
    vec2(0.1857469, -0.2327659),
    vec2(-0.2757695, -0.159873),
    vec2(-0.2301117, 0.1232693),
    vec2(0.05028719, 0.1034883),
    vec2(0.236303, 0.03379251),
    vec2(0.1467563, 0.364028),
    vec2(0.516759, 0.2052845),
    vec2(0.2962668, 0.2430771),
    vec2(0.3650614, -0.1689287),
    vec2(0.5764466, -0.07092822),
    vec2(-0.5563748, -0.4662297),
    vec2(-0.3765517, -0.5552908),
    vec2(-0.4642121, -0.157941),
    vec2(-0.2322291, -0.7013807),
    vec2(-0.05415121, -0.6379291),
    vec2(-0.7140947, -0.6341782),
    vec2(-0.4819134, -0.7250231),
    vec2(-0.7627537, -0.3445934),
    vec2(-0.7032605, -0.13733),
    vec2(0.8593938, 0.3171682),
    vec2(0.5223953, 0.5575764),
    vec2(0.7710021, 0.1543127),
    vec2(0.6919019, 0.4536686),
    vec2(0.3192437, 0.4512939),
    vec2(0.1861187, 0.595188),
    vec2(0.6516209, -0.3997115),
    vec2(0.8065675, -0.1330092),
    vec2(0.3163648, 0.7357415),
    vec2(0.5485036, 0.8288581),
    vec2(-0.2023022, -0.9551743),
    vec2(0.165668, -0.6428169),
    vec2(0.2866438, -0.5012833),
    vec2(-0.5582264, 0.2904861),
    vec2(-0.2522391, 0.401359),
    vec2(-0.428396, 0.1072979),
    vec2(-0.06261792, 0.3012581),
    vec2(0.08908027, -0.8632499),
    vec2(0.9636437, 0.05915006),
    vec2(0.8639213, -0.309005),
    vec2(-0.03422072, 0.6843638),
    vec2(-0.3734946, -0.8823979),
    vec2(-0.3939881, 0.6955767),
    vec2(-0.4499089, 0.4563405),
    vec2(0.07500362, 0.9114207),
    vec2(-0.9658601, -0.1423837),
    vec2(-0.7199838, 0.4981934),
    vec2(-0.8982374, 0.2422346),
    vec2(-0.8048639, 0.01885651),
    vec2(-0.8975322, 0.4377489),
    vec2(-0.7135055, 0.1895568),
    vec2(0.4507209, -0.3764598),
    vec2(-0.395958, -0.3309633),
    vec2(-0.6084799, 0.02532744),
    vec2(-0.2037191, 0.5817568),
    vec2(0.4493394, -0.6441184),
    vec2(0.3147424, -0.7852007),
    vec2(-0.5738106, 0.6372389),
    vec2(0.5161195, -0.8321754),
    vec2(0.6553722, -0.6201068),
    vec2(-0.2554315, 0.8326268),
    vec2(-0.5080366, 0.8539945)
);

#define rot2D(r) mat2(cos(r), sin(r), -sin(r), cos(r))


// PCF shadow sampling function
float sPCF(sampler2D sTex, vec3 sSPos, float bRad){
    float sMap = 0.0;
    float bNoise = texture(noisetex, gl_FragCoord.xy / vec2(noiseTextureResolution)).r;
    for(int i = 0; i < PCF_SAMPLE; i++){
        sMap += step(sSPos.z, texture(sTex, sSPos.xy + (rot2D(bNoise * 6.28318531) * offsets[i] * bRad)).r);
    }
    return sMap / PCF_SAMPLE;
}

vec2 hash2(vec2 p)
{
    return fract(cos(p*mat2(85.6,-69.3,74.8,-81.2))*475.);
}

vec2 voronoi(vec2 p,vec2 s)
{
    float d = 2.;
    vec2 off = vec2(.5);
    vec2 f = floor(p);
    for(float i = -1.;i<=1.;i++)
    for(float j = -1.;j<=1.;j++)
    {
        vec2 o = (hash2(f+s+vec2(i,j))-.5)+f+.5+vec2(i,j)-p;
        float t = length(o);
        if (d>t)
        {
            d = t;
            off = f+.5+vec2(i,j);
        }
    }
    return off;
}

vec2 off(vec2 p,vec3 s)
{
    return voronoi(p,s.xy+s.yz);
}

// Simple texture outline detection with very small sampling to (attempt to) avoid block edges LEGACY FUNCTION
float getTEXTURETextureOutline(vec2 texCoord, vec4 originalColor) {
    vec2 res = vec2(textureSize(texture,0));
    vec2 texelSize = (1.0 / res) * OUTLINE_THICKNESS;
    
    // Use much smaller sampling - only 1-2 pixels to stay within texture boundaries
    texelSize *= 0.3;
    
    // Sample surrounding pixels
    vec4 colorRight = texture2D(texture, texCoord + vec2(texelSize.x, 0.0));
    vec4 colorDown = texture2D(texture, texCoord + vec2(0.0, texelSize.y));
    vec4 colorLeft = texture2D(texture, texCoord - vec2(texelSize.x, 0.0));
    vec4 colorUp = texture2D(texture, texCoord - vec2(0.0, texelSize.y));
    
    // Calculate color differences
    float diffRight = length(originalColor.rgb - colorRight.rgb);
    float diffDown = length(originalColor.rgb - colorDown.rgb);
    float diffLeft = length(originalColor.rgb - colorLeft.rgb);
    float diffUp = length(originalColor.rgb - colorUp.rgb);
    
    float maxDiff = max(max(diffRight, diffDown), max(diffLeft, diffUp));
    
    // Make it dramatic but keep the smooth gradations
    return clamp(maxDiff * 4.0, 0.0, 1.0);
}

// Function to bias mid-tone colors toward white for papery look, including biome-tinted areas
vec3 applyGrayBias(vec3 inputColor, vec3 originalTexture, vec3 vertexColor) {
    // Calculate luminance of both original texture and final color
    float inputLuminance = dot(inputColor, vec3(0.299, 0.587, 0.114));
    float textureLuminance = dot(originalTexture, vec3(0.299, 0.587, 0.114));
    
    // Create a bias curve that maintains luminance ordering
    // Use a smooth curve that approaches 1.0 (white) but never crosses over
    float biasAmount = 0.0;
    
    // Primary bias range for mid-tones
    float inMidToneRange = smoothstep(GRAY_BIAS_RANGE_MIN, GRAY_BIAS_RANGE_MIN + 0.1, textureLuminance) *
                          (1.0 - smoothstep(GRAY_BIAS_RANGE_MAX - 0.1, GRAY_BIAS_RANGE_MAX, textureLuminance));
    
    // Extended bias range for bright tones (reduced strength)
    float inBrightToneRange = smoothstep(GRAY_BIAS_RANGE_MAX - 0.1, GRAY_BIAS_RANGE_MAX + 0.05, textureLuminance) *
                             (1.0 - smoothstep(0.85, 0.95, textureLuminance));
    
    // Combine ranges
    float totalBiasRange = inMidToneRange + inBrightToneRange * 0.4;
    
    // Check if final color is in reasonable range
    float finalInRange = smoothstep(0.15, 0.25, inputLuminance) *
                        (1.0 - smoothstep(0.92, 0.98, inputLuminance));
    
    // Detect biome tinting
    vec3 neutralVertex = vec3(1.0);
    float biomeStrength = length(vertexColor - neutralVertex);
    float isBiomeTinted = smoothstep(0.1, 0.3, biomeStrength);
    
    // Calculate base bias strength
    float baseBiasStrength = GRAY_BIAS_STRENGTH * totalBiasRange * finalInRange;
    
    // Add biome boost
    float biomeBoost = GRAY_BIAS_STRENGTH * 0.4 * isBiomeTinted * finalInRange;
    biomeBoost *= (inMidToneRange + inBrightToneRange * 0.5);
    baseBiasStrength += biomeBoost;
    baseBiasStrength = min(baseBiasStrength, GRAY_BIAS_STRENGTH);
    
    // Create a curve that pushes mid-tones toward white while preserving order
    float newLuminance = inputLuminance;
    if (baseBiasStrength > 0.0) {
        // Use a power curve that maintains ordering
        // Lower values get pushed up more, but never past higher values
        float biasTarget = TEXTURE_PAPER_WHITENESS;
        
        // Create a smooth curve: f(x) = x + (target - x) * bias * (1 - x)
        // This ensures that higher input values always result in higher output values
        float luminanceBias = baseBiasStrength * (1.0 - inputLuminance) * (1.0 - inputLuminance);
        newLuminance = inputLuminance + (biasTarget - inputLuminance) * luminanceBias;
        
        // Ensure we never exceed the target or go below the input
        newLuminance = clamp(newLuminance, inputLuminance, biasTarget);
    }
    
    // Apply the luminance change while preserving color direction
    vec3 result = inputColor;
    if (inputLuminance > 0.001 && newLuminance != inputLuminance) {
        // Scale the color to match the new luminance
        float luminanceScale = newLuminance / inputLuminance;
        result = inputColor * luminanceScale;
        
        // For very gray colors, mix toward the target white to avoid pure scaling
        float grayness = 1.0 - length(inputColor - vec3(inputLuminance));
        grayness = smoothstep(0.8, 1.0, grayness); // Only very gray colors
        
        vec3 targetWhite = vec3(TEXTURE_PAPER_WHITENESS);
        
        // Preserve biome tints in the white target
        if (isBiomeTinted > 0.5) {
            vec3 tintDirection = normalize(vertexColor);
            targetWhite = mix(targetWhite, targetWhite * tintDirection * 1.05, 0.1);
        }
        
        // For very gray areas, blend toward the target white instead of just scaling
        result = mix(result, targetWhite * (newLuminance / TEXTURE_PAPER_WHITENESS), 
                    grayness * baseBiasStrength * 0.3);
    }
	
    
    // Apply brightness boost more carefully
    if (baseBiasStrength > 0.0) {
        float brightnessMultiplier = GRAY_BIAS_BRIGHTNESS_LIFT * (1.0 - smoothstep(0.7, 0.9, newLuminance));
        float brightnessBias = baseBiasStrength * brightnessMultiplier * 0.5;
        result = result * (1.0 + brightnessBias);
    }
    
    return result;
}

float calculateEmissiveThreshold(vec3 color) {
    float threshold = EMISSIVE_BASE_THRESHOLD;
    
    // Calculate color properties
    float brightness = dot(color, vec3(0.299, 0.587, 0.114));
    float maxChannel = max(max(color.r, color.g), color.b);
    float minChannel = min(min(color.r, color.g), color.b);
    float saturation = (maxChannel > 0.0) ? (maxChannel - minChannel) / maxChannel : 0.0;
    
    // Gray pixel protection: if saturation is very low, require much higher brightness
    if (saturation < EMISSIVE_GRAY_THRESHOLD) {
        threshold += EMISSIVE_GRAY_PENALTY; // Make it much harder for gray pixels to be emissive
    } else if (saturation > 0.6) {
        // Only apply saturation boost if saturation is actually high
        threshold -= saturation * EMISSIVE_SATURATION_BOOST;
    }
    
    // Specific color detection
    if (maxChannel > 0.1) { // Only process if there's actual color
        vec3 normalizedColor = color / maxChannel;
        
        // VERY specific warm color detection (fire/lava only)
        // Requires: high red, decent green, very low blue, high saturation, decent brightness
        bool isFireColor = (normalizedColor.r > 0.8) && 
                          (normalizedColor.g > 0.3) && 
                          (normalizedColor.b < 0.3) &&
                          (saturation > 0.7) &&
                          (brightness > 0.3);
        
        //Less specific cool color detection.
        float coolScore = normalizedColor.b * 1.5 - normalizedColor.r * 0.8 - normalizedColor.g * 0.3;
        coolScore = clamp(coolScore, 0.0, 1.0);
        
        // Apply boosts
        if (isFireColor) {
            threshold -= EMISSIVE_WARM_COLOR_BOOST;
        }
        threshold -= coolScore * EMISSIVE_COOL_COLOR_BOOST;
    }
    
    return clamp(threshold, 0.1, 1.0);
}

// Function to check if block may be emmisive.
bool isEmissiveBlock(vec4 originalTexture, float id, float gameEmissiveFlag) {
    // Primary system: Use game data if available (and not blacklisted)
	#ifdef IS_IRIS 
    if (gameEmissiveFlag > 0.5 && gameEmissiveFlag < 1.5) {
        return true;
    }
	#endif
    
    // Fallback system: Your existing block.properties based detection
    if (!(id > 4.5 && id < 5.5)) return false;
    
    // For fallback blocks, still check if they're actually bright enough
    float brightness = dot(originalTexture.rgb, vec3(0.299, 0.587, 0.114));
    float dynamicThreshold = calculateEmissiveThreshold(originalTexture.rgb);
    
    return brightness > dynamicThreshold;
}

float getTextureBasedEmissiveStrength(vec4 originalTexture) {
    float brightness = dot(originalTexture.rgb, vec3(0.299, 0.587, 0.114));
    float dynamicThreshold = calculateEmissiveThreshold(originalTexture.rgb);
    
    if (brightness <= dynamicThreshold) return 0.0;
    
    return (brightness - dynamicThreshold) / (1.0 - dynamicThreshold);
}

float getEmissiveStrength(vec4 originalTexture, float gameEmissiveFlag, float id) {
    // First check if this block should be emissive at all
    if (!isEmissiveBlock(originalTexture, id, gameEmissiveFlag)) {
        return 0.0;
    }
    
    // For ALL emissive blocks (game-flagged or fallback), use texture-based strength
    return getTextureBasedEmissiveStrength(originalTexture);
}

#if HANDHELD_LIGHT == 1
vec3 getHandheldLight(vec3 fragViewPos, int lightVal) {
    // lightVal is 0-15, normalize to 0-1
    float strength = float(lightVal) / 15.0;
    if (strength <= 0.0) return vec3(0.0);

    // Distance from the camera (eye) to the fragment in view space
    float dist = length(fragViewPos);

    // Smooth quadratic falloff clamped to range
    float attenuation = max(0.0, 1.0 - dist / (HANDHELD_LIGHT_RANGE * strength));
    attenuation = attenuation * attenuation; // quadratic for natural falloff

    // Warm torch-like tint — tweak RGB to taste
    vec3 lightColor = vec3(1.0, 0.9, 0.7);

    return lightColor * attenuation * HANDHELD_LIGHT_STRENGTH * strength;
}
#endif

/* RENDERTARGETS:0,4 */
void main()
{
    vec3 dir = normalize((gbufferModelViewInverse * vec4(shadowLightPosition,0)).xyz);
    float flip = clamp(dir.y/.1,-1.,1.); dir *= flip;
    vec3 norm = normalize(cross(dFdx(world),dFdy(world)));
    float lambert = (id>1.5)?dir.y*.5+.5:dot(norm,dir)*.5+.5;
    float sun = exp((dot(reflect(normalize(world),norm),dir)-1.)*15.*(1.5-.5*flip));
    vec4 shine = vec4(vec3(sun)*flip*flip,0)*Shininess*step(.9,id)*step(id,1.1);

    // Cave detector - computed early so it can be used for fog too
    float skyLightFactor = clamp(eyeBrightnessSmooth.y / 240.0, 0.0, 1.0);

    // Fog calculation - darken fog underground so cave fog isn't sky-bright
    float zSky = clamp(dot(normalize(vPos), normalize(upPosition)), 0.0, 1.0);
    vec3 skyFog = mix(fogColor, skyColor, zSky);
    skyFog = mix(vec3(0.0), skyFog, skyLightFactor);
    
    // texture sampling with optional voronoi distortion
    vec2 res = vec2(textureSize(texture,0));
    vec4 col;
    
    // Declare voronoi variables once at the top level
    #if ENABLE_VORONOI_DISTORTION == 1 && VORONOI_BLOCK_DISTORTION == 1
        vec2 cell = coord0*res;
        vec2 floo = floor(cell);
        vec2 mi = floor(floo/16.)*16.;
        vec2 ma = mi+15.;
        vec3 dif = floor(vert);
        vec2 shift = off(cell,dif);
        vec2 off1 = clamp(shift,mi,ma)/res;
        vec2 gx = dFdx(coord0);
        vec2 gy = dFdy(coord0);
        
        // Apply voronoi distortion
        col = textureGrad(texture,off1,gx,gy);
        if (id<1.5) col = mix(textureGrad(texture,coord0,gx,gy),col,col.a);
    #else
        // Standard texture sampling without distortion
        col = texture2D(texture, coord0);
    #endif
    
    // STORE ORIGINAL TEXTURE DATA FOR EMISSIVE DETECTION
    vec4 originalTexture = col; // Store the pure texture color
    float originalTextureBrightness = dot(originalTexture.rgb, vec3(0.299, 0.587, 0.114));
    
	bool isEmissive = isEmissiveBlock(originalTexture, id, emissiveFlag);
    
    #ifdef TEXTURE_PAPER_STYLE
    if (TEXTURE_PAPER_STYLE == 1) {
        // Store original color for outline detection and grayscale mixing
        vec4 originalCol = col;
        
        // Calculate texture brightness for paper processing
        float brightness = dot(col.rgb, vec3(0.299, 0.587, 0.114));
        
        // Create grayscale version
        vec3 grayscaleTexture = vec3(brightness);
        
        // Mix between original texture color and grayscale based on TEXTURE_GRAYSCALE_STRENGTH
        vec3 mixedTexture = mix(col.rgb, grayscaleTexture, TEXTURE_GRAYSCALE_STRENGTH);
        
        // Apply paper style with color preservation
        vec3 paperBase = vec3(TEXTURE_PAPER_WHITENESS);
        float mixedBrightness = dot(mixedTexture, vec3(0.299, 0.587, 0.114));
        
        // Create colored paper version (preserves hue) and grayscale paper version
        vec3 coloredPaper = mixedTexture * paperBase * (0.6 + 0.4 * mixedBrightness);
        vec3 grayscalePaper = paperBase * (0.6 + 0.4 * mixedBrightness);
        
        // Blend between colored and grayscale paper based on TEXTURE_GRAYSCALE_STRENGTH
        vec3 paperColor = mix(coloredPaper, grayscalePaper, TEXTURE_GRAYSCALE_STRENGTH);
        
        // Apply outlines only if ENABLE_TEXTURE_OUTLINES is enabled
        #if ENABLE_TEXTURE_OUTLINES == 1
            // Get TEXTURE texture outlines only (no block edges)
            float textureOutline = getTEXTURETextureOutline(coord0, originalCol);
            
            // Apply outline as much darker lines for dramatic effect
            float outline = textureOutline * TEXTURE_OUTLINE_STRENGTH;
            paperColor = mix(paperColor, vec3(0.05, 0.05, 0.05), outline); // Much darker outline color
        #endif
        
        col.rgb = paperColor;
    }
    #endif

    // Apply vertex color (biome tinting)
    col.rgb *= color.rgb;
    
    // Apply gray bias BEFORE lighting calculations
    #if TEXTURE_PAPER_STYLE == 1
        col.rgb = applyGrayBias(col.rgb, originalTexture.rgb, color.rgb);
    #endif

    // SHADOW CALCULATION
	vec3 sLight = vec3(1.0);
	#if ENABLE_SHADOWS == 1
		if(length(world) < shadowDistance && id != 1.0){
			if(length(world) < shadowDistance){
				vec3 sWorld = vec3(shadowModelView * vec4(world, 1.0));
				vec3 sClip = vec3(shadowProjection * vec4(sWorld, 1.0));

				// Apply distortion to shadow coordinates (your existing approach)
				sClip.xyz = distortShadow(sClip.xyz);

				// Improved bias calculation that accounts for distortion
				float sBias = 0.0;
				switch(shadowMapResolution){
					case 2048: sBias = 0.0047296875; break;
					case 4096: sBias = 0.004653125; break;
					case 8192: sBias = 0.004520703125; break;
					default: sBias = 0.00480625;
				}

				float dist = mix(1.0, length(sClip.xy), 0.85);
				sClip.xy /= dist;
				
				// Scale bias by distortion amount AND distance (improved for far distances)
				float distortScale = 1.0 + (dist - 1.0) * 0.5; // Base distortion scaling
				
				// Add additional scaling for far distances where vertex distortion is extreme
				float worldDistance = length(world);
				float farDistanceScale = 1.0;
				if (worldDistance > FAR_DISTANCE_BIAS_START) {
					farDistanceScale = 1.0 + (worldDistance - FAR_DISTANCE_BIAS_START) / FAR_DISTANCE_BIAS_RANGE;
					farDistanceScale = min(farDistanceScale, FAR_DISTANCE_BIAS_MAX);
				}
				
				sBias *= distortScale * farDistanceScale;
				
				sClip.z -= (dist * dist * sBias) / clamp(dot(normalize(shadowLightPosition), normalize(upPosition)), 0.0, 1.0);
				
				// normal-based bias with distance scaling
				float ndotl = max(dot(normalize(N.xyz), normalize(shadowLightPosition)), 0.001);
				float finalBias = sBias * (1.0 / ndotl) * (dist * 0.3) * farDistanceScale; // Apply distance scaling here too
				sClip.z -= finalBias;
				
				vec3 sSPos = sClip.xyz * 0.5 + 0.5;

				#if SOFT_SHADOWS == 1
					float bRad = PCF_BLUR_RADIUS / shadowMapResolution;
					float sMap0 = sPCF(shadowtex0, sSPos, bRad);
					float sMap1 = sPCF(shadowtex1, sSPos, bRad);
				#else
					float sMap0 = step(sSPos.z, texture(shadowtex0, sSPos.xy).r);
					float sMap1 = step(sSPos.z, texture(shadowtex1, sSPos.xy).r);
				#endif

				#if COLORED_SHADOWS == 1
					vec4 sMapC = texture2D(shadowcolor0, sSPos.xy);
					sLight = mix(sLight, sMapC.rgb, sqrt(sMapC.a * 2.0)) * (sMap1 - sMap0);
					sLight += sMap0;
				#else
					sLight = vec3(sMap1 + sMap0) * 0.5;
				#endif
			}

			// directional lighting
			if(N.a < 1.0){
				sLight = sLight * clamp(dot(normalize(shadowLightPosition), N.xyz), 0.0, 1.0) * 2.0;
			}
		}
	#endif

	// Fade out sun/moon light during the shadow-map transition at twilight.
	float sunlightFade;
	{
		// Sunset window: ramp down then back up symmetrically around 0.52
		float sunsetFade = 1.0 - smoothstep(0.49, 0.50, sunAngle) * (1.0 - smoothstep(0.507, 0.511, sunAngle));
		sunsetFade = clamp(sunsetFade, 0.0, 1.0);

		// Sunrise window: mirror the same shape around 0.0 / 1.0
		// Remap so that angles near 0 or 1 produce a 0..1..0 dip.
		float wrappedAngle = sunAngle > 0.5 ? sunAngle - 1.0 : sunAngle; // -0.5..0.5
		float sunriseFade = 1.0 - smoothstep(-0.03, 0.0, wrappedAngle) + smoothstep(0.0, 0.06, wrappedAngle);
		sunriseFade = clamp(sunriseFade, 0.0, 1.0);

		// Use whichever transition is currently active (they don't overlap)
		sunlightFade = min(sunsetFade, sunriseFade);
	}
	sLight = mix(vec3(1.0), sLight, sunlightFade);

	// LIGHTING CALCULATION
	float sBright = mix(SHADOW_BRIGHTNESS, 1.0, rainStrength);
	float lVis = texture2D(lightmap, vec2(0.0, coord1.y)).r;

    // Cave detector: use eye-position sky light, not surface sky light.
    // eyeBrightnessSmooth.y is 240 in open sky, 0 deep underground.
    // This correctly distinguishes caves from shadowed-but-outdoor surfaces.
    // (skyLightFactor already declared above for fog use)
    float sBrightForMap = mix(1.0, sBright, skyLightFactor);

	// Always sample at FULL block light coordinate to get the proper lightmap value
	vec3 lMapFull = texture2D(lightmap, vec2(coord1.x, coord1.y * sBrightForMap)).rgb;
	vec3 lMapAmbient = texture2D(lightmap, vec2(0.0, coord1.y * sBrightForMap)).rgb;

	// Calculate the block light contribution (difference between full and ambient)
	vec3 blockLightContribution = lMapFull - lMapAmbient;

	// Scale the contribution by intensity, then add back to ambient
	vec3 lMap = lMapAmbient + (blockLightContribution * BLOCK_LIGHT_INTENSITY);

	lMap *= color.a;
	// Apply cave darkness: fades in as skyLightFactor approaches 0 (underground)
	lMap *= mix(CAVE_DARKNESS, 1.0, skyLightFactor);
	sBright += (1.0 - lVis) * (1.0 - sBright);
	// Cave fix: blend sLight toward flat 1.0 when no sky light, so sun-angle
	// directionality doesn't affect caves.
	lMap += sLight * (1.0 - sBright) * skyLightFactor;

	// Apply cave darkness: underground has no ambient sky light, so darken accordingly.
	// Blended by skyLightFactor so it only kicks in when the eye is underground.
	lMap *= mix(CAVE_DARKNESS, 1.0, skyLightFactor);

    // Cave fix: neutralize skyColor tint when underground so time-of-day color
    // doesn't affect cave shading. Blend from neutral white to actual skyColor by lVis.
    vec3 effectiveSkyColor = mix(vec3(0.6), skyColor, skyLightFactor);

    // === HANDHELD LIGHT ===
    #if HANDHELD_LIGHT == 1
        lMap += getHandheldLight(vPos, heldBlockLightValue);
    #endif

    vec3 shad;
    if (isEmissive) {
        // Calculate emissive strength based on ORIGINAL brightness
        float emissiveStrength = getEmissiveStrength(originalTexture, emissiveFlag, id);
        emissiveStrength = clamp(emissiveStrength, 0.0, 1.0);
        
        // Create emissive lighting that scales with brightness
        float emissiveBrightness = 1.0 + (EMISSIVE_BOOST_STRENGTH * emissiveStrength);
        vec3 emissiveColor = normalize(originalTexture.rgb); // Use ORIGINAL texture color for emissive
        
        shad = mix(effectiveSkyColor*.5+.2, emissiveColor * emissiveBrightness, lambert) * 
               mix(lMap, vec3(emissiveBrightness), 0.3 + 0.4 * emissiveStrength);
    } else {
        // Non-emissive blocks: normal lighting
        #if ENABLE_DIRECTIONAL_LIGHTING == 1
            // Fade directional lighting out underground (no sun/moon = no direction)
            vec3 directionalShad = mix(effectiveSkyColor*.5+.2, vec3(1), lambert) * lMap;
            vec3 flatShad = vec3(1) * lMap;
            shad = mix(flatShad, directionalShad, skyLightFactor);
        #else
            shad = vec3(1) * lMap;
        #endif
    }
        
    col *= vec4(shad*(1.-blindness),1) + shine;

    // Apply entity color
    col.rgb = mix(col.rgb,entityColor.rgb,entityColor.a);
    
    // Apply emissive glow using original texture data
    if (isEmissive) {
        float emissiveStrength = getEmissiveStrength(originalTexture, emissiveFlag, id);
        emissiveStrength = clamp(emissiveStrength, 0.0, 1.0);
        
        float glowStrength = 0.2 + (0.3 * emissiveStrength);
        vec3 glowColor = normalize(originalTexture.rgb); // Original color for glow too
        col.rgb = mix(col.rgb, col.rgb + glowColor * glowStrength, 0.3 + 0.2 * emissiveStrength);
    }

    // Apply fog
    float farDist = far;
    float fogDist = clamp(length(world) / farDist, 0.0, 1.0);
        fogDist = (isEyeInWater == 1) ? fogDist : pow(fogDist, 3.0);
    col.rgb = mix(col.rgb, skyFog, fogDist);
	
    // Store ORIGINAL texture color (before lighting) in colortex4
    vec4 originalTextureColor = originalTexture * vec4(color.rgb, 1.0);
    
    // Apply lighting to get final shadowed color
    vec3 finalColor = col.rgb; // This already has shadows applied
    
    // Output final lit color to colortex0, original color to colortex4
    gl_FragData[0] = vec4(finalColor, col.a);
	gl_FragData[1] = vec4(originalTextureColor.rgb, 1.0);
}