#version 460 compatibility

/*
====================================================================================================

    Copyright (C) 2025 Pyvtron VX Shaders - Pyvton

    All Rights Reserved unless otherwise explicitly stated.

    Before You Do Something With Pyvtron VX Shaders, You MUST Read This:

    You Must Read The License: https://pyvton.pages.dev/minecraft-shaders/pyvtron-shaders-license
    And Read The Agreement: https://pyvton.pages.dev/minecraft-shaders/pyvtron-shaders-agreement

====================================================================================================
*/

#define WATER_WAVE_HEIGHT 1.3 // [0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8 3.0]
#define WATER_PARALLAX
#define RAIN_SPLASH_EFFECT

//#define RAIN_SPLASH_BILATERAL
//#define WHITE_DEBUG_WORLD

#include "/Include/Settings.glsl"

uniform sampler2D texture;
uniform sampler2D normals;
uniform sampler2D specular;
uniform sampler2D noisetex;
uniform sampler2D gaux1;
uniform sampler2D gaux2;
uniform sampler2D gaux3;

uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferPreviousProjection;

uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferPreviousModelView;

uniform vec3 cameraPosition;
uniform vec3 previousCameraPosition;

uniform float frameTimeCounter;
uniform int isEyeInWater;
uniform int frameCounter;
uniform float wetness;

layout(location = 0) out vec4 fragData0;
layout(location = 1) out vec4 fragData1;

in vec4 color;
in vec4 texcoord;
in vec3 worldPosition;

in vec3 worldNormal;
in vec2 blockLight;
in vec4 viewPos;

in float iswater;
in float isice;
in float isStainedGlass;
in float isSlime;

in float distance;

in float materialIDs;

vec4 textureSmooth(in sampler2D tex, in vec2 coord)
{
    vec2 texSize = vec2(64.0, 64.0);

    vec2 coordTex = coord * texSize - 0.5;

    vec2 iCoord = floor(coordTex);
    vec2 fCoord = fract(coordTex);

    vec4 tex00 = texture(tex, (iCoord + vec2(0.0, 0.0) + 0.5) / texSize);
    vec4 tex10 = texture(tex, (iCoord + vec2(1.0, 0.0) + 0.5) / texSize);
    vec4 tex01 = texture(tex, (iCoord + vec2(0.0, 1.0) + 0.5) / texSize);
    vec4 tex11 = texture(tex, (iCoord + vec2(1.0, 1.0) + 0.5) / texSize);

    vec4 tex0 = mix(tex00, tex10, fCoord.x);
    vec4 tex1 = mix(tex01, tex11, fCoord.x);
    vec4 texFinal = mix(tex0, tex1, fCoord.y);

    return texFinal;
}

#include "/Include/Core/Core.glsl"
#include "/Include/WaterWaves.glsl"

#ifdef DISTANT_HORIZONS
uniform sampler2D dhDepthTex0;
uniform sampler2D dhDepthTex1;
uniform samplerCube dhEnvMap;
uniform mat4 dhProjection;
uniform mat4 dhProjectionInverse;
uniform mat4 dhPreviousProjection;
uniform mat4 dhModelViewInverse;
uniform vec3 dhCameraPosition;
uniform vec3 dhPreviousCameraPosition;
#define DH_BLOCK_WATER 6
#endif

float AlmostIdentity(in float x, in float m, in float n)
{
    if (x > m) return x;
    float a = 2.0 * n - m;
    float b = 2.0 * m - 3.0 * n;
    float t = x / m;
    return (a * t + b) * t * t + n;
}

vec3 GetWaterParallaxCoord(in vec3 position, in vec3 viewVector)
{
    vec3 parallaxCoord = position.xyz;

    const int MAX_STEPS = 8;
    float stepZ = 0.12;
    float waveHeight = clamp(GetWaves(position), -WATER_WAVE_HEIGHT, WATER_WAVE_HEIGHT);

    vec2 dirXZ = normalize(max(abs(viewVector.x), 1e-4) * vec2(viewVector.x, viewVector.z));
    float angleFactor = clamp(1.0 - viewVector.z, 0.25, 2.0);

    vec2 stepXZ = dirXZ * (0.25 * WATER_WAVE_HEIGHT) * angleFactor;
    float sampleZ = 0.0;

    for (int i = 0; i < MAX_STEPS; ++i)
    {
        vec3 testPos = position + vec3(stepXZ * float(i), sampleZ);
        float h = GetWaves(testPos);
        if (h >= sampleZ - 0.001)
        {
            parallaxCoord.x += stepXZ.x * float(i);
            parallaxCoord.z += stepXZ.y * float(i);
            break;
        }
        sampleZ += stepZ;
    }

    return parallaxCoord;
}

vec3 GetWavesNormal(vec3 position, in mat3 tbn)
{
    const float OFF = 0.015 * max(0.5, WATER_WAVE_HEIGHT);
    float c  = GetWaves(position);
    float lx = GetWaves(position + vec3(OFF, 0.0, 0.0));
    float rx = GetWaves(position + vec3(-OFF, 0.0, 0.0));
    float uy = GetWaves(position + vec3(0.0, 0.0, OFF));
    float dy = GetWaves(position + vec3(0.0, 0.0, -OFF));

    float gx = (rx - lx) * 0.5 / OFF;
    float gy = (dy - uy) * 0.5 / OFF;

    vec3 n = normalize(vec3(-gx * WATER_WAVE_HEIGHT, -gy * WATER_WAVE_HEIGHT, 1.0));
    return n;
}

#include "/Include/LightJitter.glsl"

vec3 Get3DNoise(in vec3 pos)
{
    pos.z += 0.0;
    vec3 p = floor(pos);
    vec3 f = fract(pos);
    f = f * f * (3.0 - 2.0 * f);

    vec2 uv =  (p.xy + p.z * vec2(17.0, 37.0)) + f.xy;
    vec2 uv2 = (p.xy + (p.z + 1.0) * vec2(17.0, 37.0)) + f.xy;
    vec2 coord =  (uv  + 0.5) / 64.0;
    vec2 coord2 = (uv2 + 0.5) / 64.0;
    vec3 xy1 = texture(noisetex, coord).xyz;
    vec3 xy2 = texture(noisetex, coord2).xyz;
    return mix(xy1, xy2, vec3(f.z));
}

vec3 Get3DNoiseNormal(in vec3 pos)
{
    float center = Get3DNoise(pos + vec3( 0.0, 0.0, 0.0)).x * 2.0 - 1.0;
    float left   = Get3DNoise(pos + vec3( 0.1, 0.0, 0.0)).x * 2.0 - 1.0;
    float up     = Get3DNoise(pos + vec3( 0.0, 0.1, 0.0)).x * 2.0 - 1.0;

    vec3 noiseNormal;
    noiseNormal.x = center - left;
    noiseNormal.y = center - up;

    noiseNormal.x *= 0.2;
    noiseNormal.y *= 0.2;

    float t = 1.0 - noiseNormal.x * noiseNormal.x - noiseNormal.y * noiseNormal.y;
    noiseNormal.b = (t > 0.0) ? sqrt(t) : 0.0;

    return noiseNormal.xyz;
}

float GetModulatedRainSpecular(in vec3 pos)
{
    pos.xz *= 1.0;
    pos.y *= 0.2;

    vec3 p = pos;

    float n = Get3DNoise(p).y;
          n += Get3DNoise(p / 2.0).x * 2.0;
          n += Get3DNoise(p / 4.0).x * 4.0;

          n /= 7.0;

    n = saturate(n * 0.8 + 0.5) * 0.97;

    return n;
}

#include "/Include/WaterRipples.glsl"

float ReadSceneDepth(in vec2 uv)
{
    vec2 clampedUV = clamp(uv, vec2(0.0), vec2(1.0));
#ifdef DISTANT_HORIZONS
    float d0 = texture(dhDepthTex0, clampedUV).r;
    if (d0 <= 0.0001)
    {
        float d1 = texture(dhDepthTex1, clampedUV).r;
        if (d1 <= 0.0001) return texture(gaux1, clampedUV).r;
        return clamp(d1, 0.0, 1.0);
    }
    return clamp(d0, 0.0, 1.0);
#else
    return clamp(texture(gaux1, clampedUV).r, 0.0, 1.0);
#endif
}

vec3 ComputeSSR(in vec3 viewDir, in vec3 worldNormal, in vec3 baseColor)
{
    const int MAX_SSR_STEPS = 18;
    const float STEP_SCALE = 0.018;

    vec3 refl = reflect(-viewDir, worldNormal);
    vec2 uv = texcoord.st;

    vec3 sampledColor = vec3(0.0);
    bool hit = false;

    for (int i = 1; i <= MAX_SSR_STEPS; ++i)
    {
        vec2 probeUV = uv + refl.xy * (float(i) * STEP_SCALE);

        if (probeUV.x < 0.0 || probeUV.x > 1.0 || probeUV.y < 0.0 || probeUV.y > 1.0) break;

        float sceneDepth = ReadSceneDepth(probeUV);

        if (sceneDepth < 0.999)
        {
            sampledColor = texture(gaux2, probeUV).rgb;
            hit = true;
            break;
        }
    }

    float fresnel = pow(1.0 - max(dot(viewDir, worldNormal), 0.0), 3.0) * 0.7 + 0.3;
    fresnel = saturate(fresnel);

    if (hit)
    {
        return mix(baseColor, sampledColor, fresnel);
    }
    else
    {
#ifdef DISTANT_HORIZONS
        #ifdef dhEnvMap
            vec3 env = texture(dhEnvMap, refl).rgb;
            return mix(baseColor, env, fresnel * 0.6);
        #else
            return baseColor;
        #endif
#else
        return baseColor;
#endif
    }
}

void main() {
    vec4 tex = texture(texture, texcoord.st);
    tex *= color;

    mat3 tbn;
    vec3 N;
    vec2 uv = texcoord.st;
    {
        vec3 dp1 = dFdx(viewPos.xyz);
        vec3 dp2 = dFdy(viewPos.xyz);
        vec2 duv1 = dFdx(uv);
        vec2 duv2 = dFdy(uv);
        N = normalize(cross(dp1, dp2));

        if (!all(isfinite(N))) N = worldNormal;

        vec3 dp2perp = cross(dp2, N);
        vec3 dp1perp = cross(N, dp1);
        vec3 T = normalize(dp2perp * duv1.x + dp1perp * duv2.x);
        vec3 B = normalize(dp2perp * duv1.y + dp1perp * duv2.y);
        float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
        T *= invmax;
        B *= invmax;

        tbn = mat3(T, B, N);
    }

    float wet = GetModulatedRainSpecular(worldPosition.xyz);
          wet *= saturate(worldNormal.y * 0.5 + 0.5);
          wet *= clamp(blockLight.y * 1.05 - 0.9, 0.0, 0.1) / 0.1;
          wet *= wetness;

    vec3 waterNormal = texture(normals, texcoord.st).rgb * 2.0 - 1.0;
    waterNormal = normalize(waterNormal);

    if (iswater > 0.5){
        vec3 wavesNormalTS = GetWavesNormal(worldPosition, tbn);
#ifdef WATER_PARALLAX
        vec3 viewVector = normalize((viewPos.xyz));
        vec3 parPos = GetWaterParallaxCoord(worldPosition, normalize(vec3(viewVector.x, viewVector.y, viewVector.z)));
        wavesNormalTS = GetWavesNormal(parPos, tbn);
#endif
        waterNormal = normalize(wavesNormalTS);
    }

    #ifdef RAIN_SPLASH_EFFECT
        vec3 rainNormal = GetRainNormal(worldPosition.xyz, wet) * wet * saturate(worldNormal.y);
        waterNormal = normalize(waterNormal + rainNormal * 0.8);
    #endif

    vec3 worldWaterNormal = normalize(tbn * waterNormal);

    vec3 viewDir = normalize(cameraPosition - worldPosition);
    vec3 baseColor = tex.rgb;

    vec3 finalColor = ComputeSSR(viewDir, normalize(worldWaterNormal), baseColor);

    float fresnel = pow(1.0 - saturate(dot(viewDir, worldWaterNormal)), 3.0) * 0.5;
    finalColor = mix(finalColor, finalColor + vec3(0.03) * wet, fresnel);

    vec2 normalEnc = EncodeNormal(worldWaterNormal);

    vec2 mcLightmap = blockLight;
    mcLightmap.x = CurveBlockLightTorch(mcLightmap.x);
    mcLightmap.x = pow(mcLightmap.x, 0.4);

    finalColor = clamp(finalColor, 0.0, 1.0);
    fragData0 = vec4(PackTwo8BitTo16Bit(finalColor.rg), PackTwo8BitTo16Bit(vec2(finalColor.b, 1.0)), clamp((materialIDs + 0.1) / 255.0, 0.0, 1.0), 1.0);
    fragData1 = vec4(normalEnc, mcLightmap);
}

/* DRAWBUFFERS:54 */
