#version 460 compatibility

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

    Copyright (C) 2025 Pyvtron VX Shaders - Pyvtron

    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://pyvtron.pages.dev/minecraft-shaders/pyvtron-shaders-license
    And Read The Agreement: https://pyvtron.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.0f * n - m;
	float b = 2.0f * m - 3.0f * n;
	float t = x / m;

	return (a * t + b) * t * t + n;
}

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

	vec3 stepSize = vec3(0.6f * WATER_WAVE_HEIGHT, 0.6f * WATER_WAVE_HEIGHT, 0.6f);

	float waveHeight = GetWaves(position);

	vec3 pCoord = vec3(0.0f, 0.0f, 1.0f);

	vec3 step = viewVector * stepSize;
	float distAngleWeight = ((distance * 0.2f) * (2.1f - viewVector.z)) / 2.0f;
	distAngleWeight = 1.0f;
	step *= distAngleWeight;

	float sampleHeight = waveHeight;

	for (int i = 0; sampleHeight < pCoord.z && i < 120; ++i)
	{
		pCoord.xy = mix(pCoord.xy, pCoord.xy + step.xy, clamp((pCoord.z - sampleHeight) / (stepSize.z * 0.2f * distAngleWeight / (-viewVector.z + 0.05f)), 0.0f, 1.0f));
		pCoord.z += step.z;
		sampleHeight = GetWaves(position + vec3(pCoord.x, 0.0f, pCoord.y));
	}

	parallaxCoord = position.xyz + vec3(pCoord.x, 0.0f, pCoord.y);

	return parallaxCoord;
}

vec3 GetWavesNormal(vec3 position, in mat3 tbn) {

	vec4 modelView = viewPos;

	vec3 viewVector = normalize(modelView.xyz * tbn);

	viewVector = normalize(viewVector);

	#ifdef WATER_PARALLAX
		position = GetWaterParallaxCoord(position, viewVector);
	#endif

	const float sampleDistance = 13.0f;

	position -= vec3(0.005f, 0.0f, 0.005f) * sampleDistance;

	float wavesCenter = GetWaves(position);
	float wavesLeft = GetWaves(position + vec3(0.01f * sampleDistance, 0.0f, 0.0f));
	float wavesUp   = GetWaves(position + vec3(0.0f, 0.0f, 0.01f * sampleDistance));

	vec3 wavesNormal;
	wavesNormal.r = wavesCenter - wavesLeft;
	wavesNormal.g = wavesCenter - wavesUp;

	wavesNormal.r *= 20.0f * WATER_WAVE_HEIGHT / sampleDistance;
	wavesNormal.g *= 20.0f * WATER_WAVE_HEIGHT / sampleDistance;

	wavesNormal.b = 1.0;
	wavesNormal.rgb = normalize(wavesNormal.rgb);

	return wavesNormal.rgb;
}

float CurveBlockLightTorch(float blockLight)
{
	float falloff = 10.0;

	blockLight = exp(-(1.0 - blockLight) * falloff);
	blockLight = max(0.0, blockLight - exp(-falloff));

	return blockLight;
}

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

	vec2 uv =  (p.xy + p.z * vec2(17.0f, 37.0f)) + f.xy;
	vec2 uv2 = (p.xy + (p.z + 1.0f) * vec2(17.0f, 37.0f)) + f.xy;
	vec2 coord =  (uv  + 0.5f) / 64.0f;
	vec2 coord2 = (uv2 + 0.5f) / 64.0f;
	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.0f, 0.0f, 0.0f)).x * 2.0f - 1.0f;
	float left 	 = Get3DNoise(pos + vec3( 0.1f, 0.0f, 0.0f)).x * 2.0f - 1.0f;
	float up     = Get3DNoise(pos + vec3( 0.0f, 0.1f, 0.0f)).x * 2.0f - 1.0f;

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

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

	float t = 1.0f - 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.0f;
	pos.y *= 0.2f;

	vec3 p = pos;

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

		  n /= 7.0f;

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

	return n;
}

#include "/Include/WaterRipples.glsl"

float ReadSceneDepth(in vec2 uv)
{
#ifdef DISTANT_HORIZONS
    return texture(dhDepthTex0, uv).r;
#else
    return texture(gaux1, uv).r;
#endif
}

vec3 ComputeSSR(in vec3 viewDir, in vec3 worldNormal, in vec3 baseColor)
{
    const int MAX_SSR_STEPS = 20;
    const float STEP_SCALE = 0.02;
    
    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;

    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));
		uv *= textureSize(texture, 0);
		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)));
		tbn = mat3(T * invmax, B * invmax, 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;

    if (iswater > 0.5){
        waterNormal = GetWavesNormal(worldPosition, tbn);
    }

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

	waterNormal = tbn * waterNormal;

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

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

	vec2 normalEnc = EncodeNormal(waterNormal);

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

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

}

/* DRAWBUFFERS:54 */
