/* 
NightBloom Shader by SillyyLily
*/ 

//Settings//
#include "/lib/settings.glsl"

//Fragment Shader///////////////////////////////////////////////////////////////////////////////////
#ifdef FSH

//Varyings//
varying float mat;
varying float dist;

varying vec2 texCoord, lmCoord;

varying vec3 normal, binormal, tangent;
varying vec3 sunVec, upVec, eastVec;
varying vec3 viewVector;

varying vec4 color;

#ifdef ADVANCED_MATERIALS
varying vec4 vTexCoord, vTexCoordAM;
#endif

//Uniforms//
uniform int frameCounter;
uniform int isEyeInWater;
uniform int worldTime;

uniform sampler2D tex;

uniform float blindFactor, darknessFactor, nightVision;
uniform float far, near;
uniform float frameTimeCounter;
uniform float rainStrength;
uniform float screenBrightness; 
uniform float shadowFade;
uniform float timeAngle, timeBrightness;
uniform float viewWidth, viewHeight;

uniform ivec2 eyeBrightnessSmooth;

uniform vec3 cameraPosition, previousCameraPosition;
uniform vec3 relativeEyePosition;

uniform mat4 gbufferProjection, gbufferPreviousProjection, gbufferProjectionInverse;
uniform mat4 gbufferModelView, gbufferPreviousModelView, gbufferModelViewInverse;
uniform mat4 shadowProjection;
uniform mat4 shadowModelView;

uniform sampler2D texture;
uniform sampler2D gaux2;
uniform sampler2D depthtex1;
uniform sampler2D depthtex2;
uniform sampler2D noisetex;

#ifdef ADVANCED_MATERIALS
uniform ivec2 atlasSize;

uniform sampler2D specular;
uniform sampler2D normals;

#ifdef REFLECTION_RAIN
uniform float wetness;
#endif
#endif

#ifdef DYNAMIC_HANDLIGHT
uniform int heldBlockLightValue;
uniform int heldBlockLightValue2;
#endif

#ifdef MULTICOLORED_BLOCKLIGHT
uniform sampler2D colortex8;
uniform sampler2D colortex9;
#endif

#if CLOUDS == 2
uniform sampler2D gaux1;
#endif

#ifdef DISTANT_HORIZONS
uniform float dhFarPlane;
#endif

//Common Variables//
float eBS = eyeBrightnessSmooth.y / 240.0;
float sunVisibility  = clamp((dot( sunVec, upVec) + 0.05) * 10.0, 0.0, 1.0);
float moonVisibility = clamp((dot(-sunVec, upVec) + 0.05) * 10.0, 0.0, 1.0);

#ifdef WORLD_TIME_ANIMATION
float frametime = float(worldTime) * 0.05 * ANIMATION_SPEED;
#else
float frametime = frameTimeCounter * ANIMATION_SPEED;
#endif

#ifdef ADVANCED_MATERIALS
vec2 dcdx = dFdx(texCoord);
vec2 dcdy = dFdy(texCoord);
#endif

vec3 lightVec = sunVec * ((timeAngle < 0.5325 || timeAngle > 0.9675) ? 1.0 : -1.0);

//Common Functions//
float GetLuminance(vec3 color) {
    return dot(color, vec3(0.299, 0.587, 0.114));
}

float GetWaterOpacity(float alpha, float difT, float fresnel, float lViewPos) {
    float waterFogDist = 1.0 - min(difT / WATER_FOG, 1.0);
    waterFogDist *= waterFogDist;
    alpha = mix(0.97, alpha, min(waterFogDist, 1.0 - fresnel));
    alpha = max(min(sqrt(lViewPos) * 0.075, 0.9), alpha);
    alpha = min(alpha, 1.0 - nightVision * 0.2);
    return alpha * 0.5;
}

//Includes//
#include "/lib/color/blocklightColor.glsl"
#include "/lib/color/dimensionColor.glsl"
#include "/lib/color/skyColor.glsl"
#include "/lib/color/specularColor.glsl"
#include "/lib/color/waterColor.glsl"
#include "/lib/util/dither.glsl"
#include "/lib/util/spaceConversion.glsl"
#include "/lib/atmospherics/weatherDensity.glsl"
#include "/lib/atmospherics/sky.glsl"
#include "/lib/atmospherics/clouds.glsl"
#include "/lib/atmospherics/fog.glsl"
#include "/lib/atmospherics/waterFog.glsl"
#include "/lib/lighting/mainLighting.glsl"
#include "/lib/reflections/raytrace.glsl"
#include "/lib/reflections/simpleReflections.glsl"
#include "/lib/surface/ggx.glsl"
#include "/lib/surface/hardcodedEmission.glsl"

#ifdef TAA
#include "/lib/util/jitter.glsl"
#endif

#ifdef ADVANCED_MATERIALS
#include "/lib/reflections/complexFresnel.glsl"
#include "/lib/surface/directionalLightmap.glsl"
#include "/lib/surface/materialGbuffers.glsl"
#include "/lib/surface/parallax.glsl"

#ifdef REFLECTION_RAIN
#include "/lib/reflections/rainPuddles.glsl"
#endif
#endif

#ifdef MULTICOLORED_BLOCKLIGHT
#include "/lib/lighting/coloredBlocklight.glsl"
#endif

void generateNormals(inout vec3 newNormal, vec3 albedo, vec3 viewPos, float water) {
    if (water < 0.5) return;

    const float normalThreshold = 0.05;
    const float normalClamp = 0.35;
    const float packSizeGN = 16.0;

    float NoU = clamp(dot(newNormal, upVec), 0.0, 1.0);
    float viewDistance = clamp(length(viewPos) * 0.01, 0.0, 1.0) * 0.10;
    float disableNormal = float(isEyeInWater == 2) + viewDistance * 10;

    if (disableNormal < 0.75) {
        float normalMult = (1.0 - disableNormal) * 1.8 * sqrt(0.2);  // NORMAL_MULTIPLIER as 0.2

        vec2 offsetR = (16.0 / atlasSize) / packSizeGN;
        #ifdef SAFE_GENERATED_NORMALS
            offsetR = max(vTexCoordAM.z, vTexCoordAM.w) * vec2(float(atlasSize.y) / float(atlasSize.x), 1.0) / packSizeGN;
        #endif

        vec2 midCoord = texCoord - vTexCoordAM.st * vTexCoordAM.pq;
        vec2 vTexCoordAM_M = vTexCoordAM.st * (0.5 - 0.5 / packSizeGN);

        vec3 normalMap = vec3(0.0, 0.0, 1.0);
        float lOriginalAlbedo = length(albedo);

        if (normalMult > 0.0) {
            for (int i = 0; i < 4; i++) {
                vec2 offsetCoord;
                if (i == 0) {
                    offsetCoord = texCoord + vec2(0.0, offsetR.y);
                    if (offsetCoord.y > midCoord.y + vTexCoordAM_M.y) continue;
                } else if (i == 1) {
                    offsetCoord = texCoord + vec2(offsetR.x, 0.0);
                    if (offsetCoord.x > midCoord.x + vTexCoordAM_M.x) continue;
                } else if (i == 2) {
                    offsetCoord = texCoord + vec2(0.0, -offsetR.y);
                    if (offsetCoord.y < midCoord.y - vTexCoordAM_M.y) continue;
                } else if (i == 3) {
                    offsetCoord = texCoord + vec2(-offsetR.x, 0.0);
                    if (offsetCoord.x < midCoord.x - vTexCoordAM_M.x) continue;
                }
                offsetCoord = clamp(offsetCoord, vTexCoordAM.st, vTexCoordAM.st + vTexCoordAM.pq);

                float lNearbyAlbedo = length(texture2D(texture, offsetCoord).rgb);
                float dif = lOriginalAlbedo - lNearbyAlbedo;
                if (dif > 0.0) dif = max(dif - normalClamp, 0.0);
                else dif = min(dif + normalClamp, 0.0);
                dif *= normalMult;

                if (i == 0) normalMap.y += dif;
                else if (i == 1) normalMap.x += dif;
                else if (i == 2) normalMap.y -= dif;
                else if (i == 3) normalMap.x -= dif;
            }
        }

        normalMap.xy = clamp(normalMap.xy * 0.2, -0.15, 0.15); // NORMAL_MULTIPLIER applied here

        if (normalMap.xy != vec2(0.0)) {
            mat3 tbnMatrix = mat3(
                tangent.x, binormal.x, normal.x,
                tangent.y, binormal.y, normal.y,
                tangent.z, binormal.z, normal.z
            );
            newNormal = clamp(normalize(normalMap * tbnMatrix), -1.0, 1.0);
        }
    }
}

const vec3 forwardVec = vec3(0.0, 0.0, 1.0);

//Program//
void main() {
    vec4 albedoP = texture2D(texture, texCoord);
    vec4 albedo = albedoP * vec4(color.rgb, 1.0);
    vec3 newNormal = normal;
    float smoothness = 0.0;
    vec3 lightAlbedo = vec3(0.0);
    
    #ifdef ADVANCED_MATERIALS
    vec2 newCoord = vTexCoord.st * vTexCoordAM.pq + vTexCoordAM.st;
    float surfaceDepth = 1.0;
    float parallaxFade = clamp((dist - PARALLAX_DISTANCE) / 32.0, 0.0, 1.0);
    float skipAdvMat = float(mat > 0.98 && mat < 1.02);
    #endif

    vec3 vlAlbedo = vec3(1.0);
    vec3 refraction = vec3(0.0);

    float cloudBlendOpacity = 1.0;

    if (albedo.a > 0.001) {
        vec2 lightmap = clamp(lmCoord, vec2(0.0), vec2(1.0));
        
        float water       = float(mat > 0.98 && mat < 1.02);
        float glass       = float(mat > 1.98 && mat < 2.02);
        float translucent = float(mat > 2.98 && mat < 3.02);
        float portal      = float(mat > 3.98 && mat < 4.02);
        
        float metalness       = 0.0;
        float emission        = portal;
        float subsurface      = 0.0;
        float basicSubsurface = water;
        vec3 baseReflectance  = vec3(0.04);

        emission *= GetHardcodedEmission(albedo.rgb);
        
        #ifndef REFLECTION_TRANSLUCENT
        glass = 0.0;
        translucent = 0.0;
        #endif

        vec3 screenPos = vec3(gl_FragCoord.xy / vec2(viewWidth, viewHeight), gl_FragCoord.z);
        #ifdef TAA
        vec3 viewPos = ToNDC(vec3(TAAJitter(screenPos.xy, -0.5), screenPos.z));
        #else
        vec3 viewPos = ToNDC(screenPos);
        #endif
        vec3 worldPos = ToWorld(viewPos);
        float lViewPos = length(viewPos);

        float dither = Bayer8(gl_FragCoord.xy);

        #if CLOUDS == 2
        float cloudViewLength = texture2D(gaux1, screenPos.xy).r * (far * 2.0);
        float viewLength = length(viewPos);
        cloudBlendOpacity = step(viewLength, cloudViewLength);
        if (cloudBlendOpacity == 0) {
            discard;
        }
        #endif

        vec3 normalMap = vec3(0.0, 0.0, 1.0);
        
        mat3 tbnMatrix = mat3(tangent.x, binormal.x, normal.x,
                              tangent.y, binormal.y, normal.y,
                              tangent.z, binormal.z, normal.z);

        #ifdef GENERATED_NORMALS
        if (water > 0.5) {
            generateNormals(newNormal, albedo.rgb, viewPos, water);
        }
        #endif
        #ifdef ADVANCED_MATERIALS
        float f0 = 0.0, porosity = 0.5, ao = 1.0, skyOcclusion = 0.0;
        if (water < 0.5) {
            GetMaterials(smoothness, metalness, f0, emission, subsurface, porosity, ao, normalMap,
                         newCoord, dcdx, dcdy);

            if ((normalMap.x > -0.999 || normalMap.y > -0.999) && viewVector == viewVector)
                newNormal = clamp(normalize(normalMap * tbnMatrix), vec3(-1.0), vec3(1.0));
        }
        #endif

        #if REFRACTION == 1
        refraction = vec3((newNormal.xy - normal.xy) * 0.5 + 0.5, float(albedo.a < 0.95) * water);
        #elif REFRACTION == 2
        refraction = vec3((newNormal.xy - normal.xy) * 0.5 + 0.5, float(albedo.a < 0.95));
        #endif
        
        #ifdef DYNAMIC_HANDLIGHT
        float heldLightValue = max(float(heldBlockLightValue), float(heldBlockLightValue2));
        vec3 heldLightPos = worldPos + relativeEyePosition + vec3(0.0, 0.5, 0.0);
        float handlight = clamp((heldLightValue - 2.0 * length(heldLightPos)) / 15.0, 0.0, 0.9333);
        lightmap.x = log2(exp2(lightmap.x * 8.0) + exp2(handlight * 8.0)) / 8.0;
        #endif

        #ifdef TOON_LIGHTMAP
        lightmap = floor(lightmap * 14.999 * (0.75 + 0.25 * color.a)) / 14.0;
        lightmap = clamp(lightmap, vec2(0.0), vec2(1.0));
        #endif

        albedo.rgb = pow(albedo.rgb, vec3(2.2));
        
        vlAlbedo = albedo.rgb;

        #ifdef MULTICOLORED_BLOCKLIGHT
        vec3 opaquelightAlbedo = texture2D(colortex8, screenPos.xy).rgb;
        if (water < 0.5) {
            opaquelightAlbedo *= vlAlbedo;
        }
        lightAlbedo = albedo.rgb + 0.00001;

        lightAlbedo = normalize(lightAlbedo + 0.00001) * emission;
        lightAlbedo = mix(opaquelightAlbedo, sqrt(lightAlbedo), albedo.a);
        #endif

        #ifdef WHITE_WORLD
        albedo.rgb = vec3(0.35);
        #endif
        
if (water > 0.5) {
    albedo.a *= WATER_OPACITY * 1;

    float albedoPRTX = pow(albedoP.r * albedoP.r, 2.0);
    albedo.rgb = mix(waterColor.rgb, vec3(1.0), 0.8) * albedoPRTX + 0.5 * mix(waterColor.rgb, vec3(1.0), 0.4) * albedoPRTX;
    albedo.rgb = mix(albedo.rgb, albedo.rgb * color.rgb, 1.3);
    if (WATER_OPACITY > 0.82) {
        albedo.rgb = min(albedo.rgb * (1.0 + length(albedo.rgb) * pow(WATER_OPACITY, 32.0) * 50.0), vec3(2.0));
    }
    if (isEyeInWater == 1) {
        albedo.a = 0.3;
    }

    vlAlbedo = sqrt(albedo.rgb);
    baseReflectance = vec3(0.0);
    
    #ifdef WATER_EDGES
    if (dot(normal, upVec) > 0.99) {
        float backgroundDepth = texture2D(depthtex1, screenPos.xy).r;
        vec3 backgroundScreenPos = vec3(screenPos.xy, backgroundDepth);
        vec3 backgroundViewPos = ToNDC(backgroundScreenPos);
        vec3 backgroundWorldPos = (gbufferModelViewInverse * vec4(backgroundViewPos, 1.0)).xyz;
        float verticalDepthDiff = backgroundWorldPos.y - worldPos.y;

        vec3 textureColor = pow(albedoP.rgb, vec3(2.0));
        float textureColorIntensity = dot(textureColor, textureColor);
        float edgeThreshold = min(pow(textureColorIntensity, 2.0) * 1.6, 1.2);

        float edgeIntensity = pow(clamp((edgeThreshold + verticalDepthDiff) / edgeThreshold, 0.0, 1.0), 2.0);
        edgeIntensity *= 0.4 + 0.5 * lightmap.y;
        edgeIntensity *= WATER_EDGES_INTENSITY * 0.001;

        vec4 edgeColor = vec4(1.2, 1.2, 1.2, 1.0);

        albedo.rgb = mix(albedo.rgb, edgeColor.rgb, edgeIntensity);
        albedo.a = mix(albedo.a, 1.0, edgeIntensity);
    }
    #endif

    #ifdef WATER_FOG
    if (isEyeInWater == 0) {
        float backgroundDepth = texture2D(depthtex1, screenPos.xy).r;
        vec3 backgroundScreenPos = vec3(screenPos.xy, backgroundDepth);
        vec3 backgroundViewPos = ToNDC(backgroundScreenPos);
        float depthDifference = length(backgroundViewPos) - lViewPos;
        
        float fresnelBase = clamp(1.0 + dot(newNormal, normalize(viewPos)), 0.0, 1.0);
        float fresnel4 = pow(fresnelBase, 4.0);
        float fresnel = fresnel4 * 0.95 + 0.05;
        albedo.a = GetWaterOpacity(albedo.a, depthDifference, fresnel, lViewPos);
        
        vec4 waterFog = GetWaterFog(viewPos.xyz - backgroundViewPos);
        albedo.rgb = mix(
            albedo.rgb, 
            mix(waterColor.rgb, vec3(1.0), 0.6), 
            waterFog.a * 0.15
        );
        albedo.rgb *= (1.0 - pow(waterFog.a, 3.0));

        albedo.a = clamp(
            albedo.a * mix(0.5, 0.8, waterFog.a),
            0.05, 
            0.8
        );
    }
    #endif
}
        
        vlAlbedo = mix(vec3(1.0), vlAlbedo, sqrt(albedo.a)) * (1.0 - pow(albedo.a, 64.0));
        
        #ifndef HALF_LAMBERT
        float NoL = clamp(dot(newNormal, lightVec), 0.0, 1.0);
        #else
        float NoL = clamp(dot(newNormal, lightVec) * 0.5 + 0.5, 0.0, 1.0);
        NoL *= NoL;
        #endif

        float NoU = clamp(dot(newNormal, upVec), -1.0, 1.0);
        float NoE = clamp(dot(newNormal, eastVec), -1.0, 1.0);
        float vanillaDiffuse = (0.25 * NoU + 0.75) + (0.667 - abs(NoE)) * (1.0 - abs(NoU)) * 0.15;
              vanillaDiffuse *= vanillaDiffuse;

        float parallaxShadow = 1.0;
        #ifdef ADVANCED_MATERIALS
        vec3 rawAlbedo = albedo.rgb * 0.999 + 0.001;
        albedo.rgb *= ao;

        #ifdef SELF_SHADOW
        if (lightmap.y > 0.0 && NoL > 0.0 && water < 0.5) {
            parallaxShadow = GetParallaxShadow(surfaceDepth, parallaxFade, newCoord, lightVec,
                                               tbnMatrix);
        }
        #endif

        #ifdef DIRECTIONAL_LIGHTMAP
        mat3 lightmapTBN = GetLightmapTBN(viewPos);
        lightmap.x = DirectionalLightmap(lightmap.x, lmCoord.x, newNormal, lightmapTBN);
        lightmap.y = DirectionalLightmap(lightmap.y, lmCoord.y, newNormal, lightmapTBN);
        #endif
        #endif

        #ifdef MULTICOLORED_BLOCKLIGHT
        blocklightCol = ApplyMultiColoredBlocklight(blocklightCol, screenPos);
        #endif
        
        vec3 shadow = vec3(0.0);
        GetLighting(albedo.rgb, shadow, viewPos, worldPos, newNormal, lightmap, 1.0, NoL, 
                    vanillaDiffuse, parallaxShadow, emission, subsurface, basicSubsurface);

        #ifdef ADVANCED_MATERIALS
        float puddles = 0.0;
        #ifdef REFLECTION_RAIN    
        if (water < 0.5) {
            puddles = GetPuddles(worldPos, newCoord, lightmap.y, NoU, wetness);
        }

        ApplyPuddleToMaterial(puddles, albedo, smoothness, f0, porosity);

        if (water < 0.5 && puddles > 0.001 && rainStrength > 0.001) {
            mat3 tbnMatrix = mat3(tangent.x, binormal.x, normal.x,
                                  tangent.y, binormal.y, normal.y,
                                  tangent.z, binormal.z, normal.z);

            vec3 puddleNormal = GetPuddleNormal(worldPos, viewPos, tbnMatrix);
            newNormal = normalize(
                mix(newNormal, puddleNormal, puddles * sqrt(1.0 - porosity) * rainStrength)
            );
        }
        #endif
        #endif
        
        float fresnelBase = clamp(1.0 + dot(newNormal, normalize(viewPos)), 0.0, 1.0);
        float fresnel4 = pow(fresnelBase, 4.0);
        float fresnel = fresnel4 * 0.95 + 0.05;

        if (water > 0.5 || ((translucent + glass) > 0.5 && albedo.a < 0.95) || portal > 0.5) {
            #if REFLECTION > 0
            vec4 reflection = vec4(0.0);
            vec3 skyReflection = vec3(0.0);
            float reflectionMask = 0.0;

            fresnel *= max(1.0 - isEyeInWater * 0.5 * water, 0.5);
            
            #if REFLECTION == 2
            reflection = SimpleReflection(viewPos, newNormal, dither, reflectionMask);
            reflection.rgb = pow(reflection.rgb * 2.0, vec3(8.0));
            #endif
            
            if (reflection.a < 1.0) {
                #ifdef OVERWORLD
                vec3 skyRefPos = reflect(normalize(viewPos), newNormal);
                vec3 reflectedAtmosphere = GetSkyColor(skyRefPos, true);
                skyReflection = reflectedAtmosphere;
                
                #ifdef AURORA
                skyReflection += DrawAurora(skyRefPos * 100.0, dither, 12);
                #endif

                #if CLOUDS == 2
                vec3 cameraPos = GetReflectedCameraPos(worldPos, newNormal);
                float cloudViewLength = 0.0;

                vec4 cloud = DrawCloudVolumetric(skyRefPos * 8192.0, cameraPos, 1.0, dither, mix(lightCol, reflectedAtmosphere * 2.0, 0.3), mix(ambientCol, reflectedAtmosphere * 2.0, 0.4), cloudViewLength, true);
                skyReflection = mix(skyReflection, cloud.rgb, cloud.a);
                #endif

                #ifdef CLASSIC_EXPOSURE
                skyReflection *= 4.0 - 3.0 * eBS;
                #endif
                
                float waterSkyOcclusion = lightmap.y;
                #if REFLECTION_SKY_FALLOFF > 1
                waterSkyOcclusion = clamp(1.0 - (1.0 - waterSkyOcclusion) * REFLECTION_SKY_FALLOFF, 0.0, 1.0);
                #endif
                waterSkyOcclusion *= waterSkyOcclusion;
                skyReflection *= waterSkyOcclusion;
                #endif

                #ifdef NETHER
                skyReflection = netherCol.rgb * 0.04;
                #endif

                #ifdef END
                skyReflection = endCol.rgb * 0.01;
                #endif

                skyReflection *= clamp(1.0 - isEyeInWater, 0.0, 1.0);
            }
            
            reflection.rgb = max(mix(skyReflection, reflection.rgb, reflection.a), vec3(0.0));

            #if (defined OVERWORLD || defined END) && SPECULAR_HIGHLIGHT == 2
            vec3 specularColor = GetSpecularColor(lightmap.y, 0.0, vec3(1.0));

            vec3 specular = GetSpecularHighlight(newNormal, viewPos, 0.9, vec3(0.02),
                                                specularColor, shadow, color.a);
            #if ALPHA_BLEND == 0
            float specularAlpha = pow(mix(albedo.a, 1.0, fresnel), 2.2) * fresnel;
            #else
            float specularAlpha = mix(albedo.a , 1.0, fresnel) * fresnel;
            #endif

            reflection.rgb += specular * (1.0 - reflectionMask) / specularAlpha;
            #endif
            
            if (portal > 0.5) {
                vec3[9] colors = vec3[9](
                    vec3(0.7, 0.2, 1.0) * 1.5, // Vibrant purple
                    vec3(0.6, 0.3, 0.9),       // Slightly muted purple
                    vec3(0.5, 0.2, 0.8),       // Deeper purple
                    vec3(0.8, 0.3, 1.2),       // Bright purple
                    vec3(0.7, 0.4, 0.9),       // Soft purple
                    vec3(0.6, 0.2, 0.8) * 0.9, // Darker purple
                    vec3(0.5, 0.3, 0.9) * 0.9, // Cool purple
                    vec3(0.4, 0.2, 0.7) * 0.7, // Subtle purple
                    vec3(0.3, 0.2, 0.6) * 0.5  // Faint purple
                );

                albedo.rgb = vec3(0.5, 0.2, 0.8) * 0;
                albedo.a = 0.5;
                float ditherPortal = Bayer8(gl_FragCoord.xy);
                #ifdef TAA
                    ditherPortal = fract(ditherPortal + frameTimeCounter * 16.0);
                    int repeat = 4;
                #else
                    int repeat = 8;
                #endif
                float dismult = 0.5;

                for (int j = 0; j < repeat; j++) {
                    float add = float(j + ditherPortal) * 0.0625 / float(repeat);
                    int samples = 9;
                    if (j > 2) samples = 6;
                    for (int i = 1; i <= samples; i++) {
                        float colormult = 0.9 / (30.0 + float(i));
                        vec2 offset = vec2(0.0, 1.0 / (3600.0 / 24.0)) * pow(16.0 - float(i), 2.0) * 0.004;
                        vec3 wpos = normalize((gbufferModelViewInverse * vec4(viewPos * (float(i) * dismult + 1.0), 1.0)).xyz);
                        if (abs(dot(normal, upVec)) > 0.9) { // Top/bottom portal
                            wpos.xz /= wpos.y;
                            wpos.xz *= 0.06 * sign(-worldPos.y);
                            wpos.xz *= abs(worldPos.y) + float(i) * dismult + add;
                            wpos.xz -= cameraPosition.xz * 0.05;
                        } else {
                            vec3 absPos = abs(worldPos);
                            if (abs(dot(normal, eastVec)) > 0.9) { // East/west portal
                                wpos.xz = wpos.yz / wpos.x;
                                wpos.xz *= 0.06 * sign(-worldPos.x);
                                wpos.xz *= abs(worldPos.x) + float(i) * dismult + add;
                                wpos.xz -= cameraPosition.yz * 0.05;
                            } else { // North/south portal
                                wpos.xz = wpos.yx / wpos.z;
                                wpos.xz *= 0.06 * sign(-worldPos.z);
                                wpos.xz *= abs(worldPos.z) + float(i) * dismult + add;
                                wpos.xz -= cameraPosition.yx * 0.05;
                            }
                        }
                        vec2 pos = wpos.xz;
                        vec2 wind = fract((frameTimeCounter + 984.0) * (float(i) + 8.0) * 0.125 * offset);
                        vec2 coord = pos + wind;
                        if (mod(float(i), 4.0) < 1.5) coord = coord.yx + vec2(-1.0, 1.0) * wind.y;
                        vec3 psample = pow(texture2D(noisetex, coord).rgb, vec3(1.3)) * colors[i-1] * colormult;
                        albedo.rgb += psample * length(psample.rgb) * (2500.0 / float(repeat));
                    }
                }

                // Apply slightly visible default texture
                vec4 defaultTexture = texture2D(tex, texCoord);
                albedo = mix(albedo, defaultTexture, 0.8); // Blend with 20% texture visibility

                emission = 0.25;

                albedo.rgb = mix(albedo.rgb, reflection.rgb, fresnel * 0.5); // Reduced fresnel for portal
                albedo.a = mix(albedo.a, 1.0, fresnel * 0.5);
            } else {
                albedo.rgb = mix(albedo.rgb, reflection.rgb, fresnel);
                albedo.a = mix(albedo.a, 1.0, fresnel);
            }
            #endif
        } else {
            #ifdef ADVANCED_MATERIALS
            skyOcclusion = lightmap.y;
            #if REFLECTION_SKY_FALLOFF > 1
            skyOcclusion = clamp(1.0 - (1.0 - skyOcclusion) * REFLECTION_SKY_FALLOFF, 0.0, 1.0);
            #endif
            skyOcclusion *= skyOcclusion;

            baseReflectance = mix(vec3(f0), rawAlbedo, metalness);

            #ifdef REFLECTION_SPECULAR
            vec3 fresnel3 = mix(baseReflectance, vec3(1.0), fresnel);
            #if MATERIAL_FORMAT == 0
            if (f0 >= 0.9 && f0 < 1.0) {
                baseReflectance = GetMetalCol(f0);
                fresnel3 = ComplexFresnel(pow(fresnel, 0.2), f0);
                #ifdef ALBEDO_METAL
                fresnel3 *= rawAlbedo;
                #endif
            }
            #endif
            
            float aoSquared = ao * ao;
            shadow *= aoSquared; fresnel3 *= aoSquared * smoothness * smoothness;

            if (smoothness > 0.0) {
                vec4 reflection = vec4(0.0);
                vec3 skyReflection = vec3(0.0);
                float reflectionMask = 0.0;
                
                float ssrMask = clamp(length(fresnel3) * 400.0 - 1.0, 0.0, 1.0);
                if(ssrMask > 0.0) reflection = SimpleReflection(viewPos, newNormal, dither, reflectionMask);
                reflection.rgb = pow(reflection.rgb * 2.0, vec3(8.0));
                reflection.a *= ssrMask;

                if (reflection.a < 1.0) {
                    #ifdef OVERWORLD
                    vec3 skyRefPos = reflect(normalize(viewPos.xyz), newNormal);
                    vec3 reflectedAtmosphere = GetSkyColor(skyRefPos, true);
                    skyReflection = reflectedAtmosphere;
                    
                    #ifdef AURORA
                    skyReflection += DrawAurora(skyRefPos * 100.0, dither, 12);
                    #endif

                    #if CLOUDS == 2
                    vec3 cameraPos = GetReflectedCameraPos(worldPos, newNormal);
                    float cloudViewLength = 0.0;

                    vec4 cloud = DrawCloudVolumetric(skyRefPos * 8192.0, cameraPos, 1.0, dither, mix(lightCol, reflectedAtmosphere * 2.0, 0.3), mix(ambientCol, reflectedAtmosphere * 2.0, 0.4), cloudViewLength, true);
                    skyReflection = mix(skyReflection, cloud.rgb, cloud.a);
                    #endif

                    #ifdef CLASSIC_EXPOSURE
                    skyReflection *= 4.0 - 3.0 * eBS;
                    #endif

                    skyReflection = mix(vanillaDiffuse * minLightCol, skyReflection, skyOcclusion);
                    #endif

                    #ifdef NETHER
                    skyReflection = netherCol.rgb * 0.04;
                    #endif

                    #ifdef END
                    skyReflection = endCol.rgb * 0.01;
                    #endif
                }

                reflection.rgb = max(mix(skyReflection, reflection.rgb, reflectionMask), vec3(0.0));

                albedo.rgb = albedo.rgb * (1.0 - fresnel3 * (1.0 - metalness)) +
                             reflection.rgb * fresnel3;
                albedo.a = mix(albedo.a, 1.0, GetLuminance(fresnel3));
            }
            #endif
            #endif

            #if (defined OVERWORLD || defined END) && SPECULAR_HIGHLIGHT == 2
            vec3 specularColor = GetSpecularColor(lightmap.y, metalness, baseReflectance);

            albedo.rgb += GetSpecularHighlight(newNormal, viewPos, smoothness, baseReflectance,
                                              specularColor, shadow * vanillaDiffuse, color.a);
            #endif
        }

        Fog(albedo.rgb, viewPos);

        #if ALPHA_BLEND == 0
        albedo.rgb = sqrt(max(albedo.rgb, vec3(0.0)));
        #endif
    }
    albedo.a *= cloudBlendOpacity;

    /* DRAWBUFFERS:01 */
    gl_FragData[0] = albedo;
    gl_FragData[1] = vec4(vlAlbedo, 1.0);

    #ifdef MULTICOLORED_BLOCKLIGHT
        /* DRAWBUFFERS:018 */
        gl_FragData[2] = vec4(lightAlbedo, 1.0);
        #if REFRACTION > 0
        /* DRAWBUFFERS:0186 */
        gl_FragData[3] = vec4(refraction, 1.0);
        #endif
    #else
        #if REFRACTION > 0
        /* DRAWBUFFERS:016 */
        gl_FragData[2] = vec4(refraction, 1.0);
        #endif
    #endif
}

#endif

//Vertex Shader/////////////////////////////////////////////////////////////////////////////////////
#ifdef VSH

//Varyings//
varying float mat;
varying float dist;

varying vec2 texCoord, lmCoord;

varying vec3 normal, binormal, tangent;
varying vec3 sunVec, upVec, eastVec;
varying vec3 viewVector;

varying vec4 color;

#ifdef ADVANCED_MATERIALS
varying vec4 vTexCoord, vTexCoordAM;
#endif

//Uniforms//
uniform int worldTime;

uniform float frameTimeCounter;
uniform float timeAngle;

uniform vec3 cameraPosition;

uniform mat4 gbufferModelView, gbufferModelViewInverse;

#ifdef TAA
uniform int frameCounter;

uniform float viewWidth, viewHeight;
#endif

//Attributes//
attribute vec4 mc_Entity;
attribute vec4 mc_midTexCoord;
attribute vec4 at_tangent;

//Common Variables//
#ifdef WORLD_TIME_ANIMATION
float frametime = float(worldTime) * 0.05 * ANIMATION_SPEED;
#else
float frametime = frameTimeCounter * ANIMATION_SPEED;
#endif

//Common Functions//
float WavingWater(vec3 worldPos) {
    // Disabled to keep water flat
    return 0.0;
}

//Includes//
#ifdef TAA
#include "/lib/util/jitter.glsl"
#endif

#ifdef WORLD_CURVATURE
#include "/lib/vertex/worldCurvature.glsl"
#endif

//Program//
void main() {
    texCoord = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
    
    lmCoord = (gl_TextureMatrix[1] * gl_MultiTexCoord1).xy;
    lmCoord = clamp((lmCoord - 0.03125) * 1.06667, vec2(0.0), vec2(0.9333, 1.0));
    
    int blockID = int(mod(max(mc_Entity.x - 10000, 0), 10000));

    normal   = normalize(gl_NormalMatrix * gl_Normal);
    binormal = normalize(gl_NormalMatrix * cross(at_tangent.xyz, gl_Normal.xyz) * at_tangent.w);
    tangent  = normalize(gl_NormalMatrix * at_tangent.xyz);
    
    mat3 tbnMatrix = mat3(tangent.x, binormal.x, normal.x,
                          tangent.y, binormal.y, normal.y,
                          tangent.z, binormal.z, normal.z);
                                  
    viewVector = tbnMatrix * (gl_ModelViewMatrix * gl_Vertex).xyz;
    
    dist = length(gl_ModelViewMatrix * gl_Vertex);

    #ifdef ADVANCED_MATERIALS
    vec2 midCoord = (gl_TextureMatrix[0] *  mc_midTexCoord).st;
    vec2 texMinMidCoord = texCoord - midCoord;

    vTexCoordAM.pq  = abs(texMinMidCoord) * 2;
    vTexCoordAM.st  = min(texCoord, midCoord - texMinMidCoord);
    
    vTexCoord.xy    = sign(texMinMidCoord) * 0.5 + 0.5;
    #endif
    
    color = gl_Color;

    if(color.a < 0.1) color.a = 1.0;
    
    mat = 0.0;
    
    if (blockID == 0) mat = 1.0; //water
    if (blockID >= 201 && blockID <= 216) mat = 2.0; //glass
    if (blockID == 1) mat = 3.0; //ice slime
    if (blockID == 31) mat = 4.0; //portal

    const vec2 sunRotationData = vec2(
         cos(sunPathRotation * 0.01745329251994),
        -sin(sunPathRotation * 0.01745329251994)
    );
    float ang = fract(timeAngle - 0.25);
    ang = (ang + (cos(ang * 3.14159265358979) * -0.5 + 0.5 - ang) / 3.0) * 6.28318530717959;
    sunVec = normalize((gbufferModelView * vec4(vec3(-sin(ang), cos(ang) * sunRotationData) * 2000.0, 1.0)).xyz);

    upVec = normalize(gbufferModelView[1].xyz);
    eastVec = normalize(gbufferModelView[0].xyz);

    vec4 position = gbufferModelViewInverse * gl_ModelViewMatrix * gl_Vertex;
    

    #ifdef WORLD_CURVATURE
    position.y -= WorldCurvature(position.xz);
    #endif

    gl_Position = gl_ProjectionMatrix * gbufferModelView * position;
    if (mat == 0.0) gl_Position.z -= 0.000000000001;
    
    #ifdef TAA
    gl_Position.xy = TAAJitter(gl_Position.xy, gl_Position.w);
    #endif
}

#endif