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

    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

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


layout(location = 0) out vec4 compositeOutput1;
layout(location = 1) out vec4 compositeOutput2;


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

const bool 		shadowtex0Mipmap           = true;
const bool 		shadowtex0Nearest          = false;
const bool 		shadowtex1Mipmap           = true;
const bool 		shadowtex1Nearest          = false;
const bool 		shadowcolor0Mipmap         = true;
const bool 		shadowcolor0Nearest        = false;
const bool 		shadowcolor1Mipmap         = true;
const bool 		shadowcolor1Nearest        = false;
uniform sampler2D shadowtex0;
uniform sampler2D shadowtex1;
uniform sampler2D shadowcolor0;
uniform sampler2D shadowcolor1;

in vec4 texcoord;

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

vec4 GetViewPosition(in vec2 coord, in float depth)
{
	#ifdef TAA
		coord -= taaJitter * 0.5;
	#endif

	vec3 screenPos = vec3(coord, depth) * 2.0 - 1.0;

	vec4 fragposition = gbufferProjectionInverse * vec4(screenPos, 1.0f);
		 fragposition /= fragposition.w;

	return fragposition;
}

vec4 GetViewPositionRaw(in vec2 coord, in float depth)
{
	vec3 screenPos = vec3(coord, depth) * 2.0 - 1.0;

	vec4 fragposition = gbufferProjectionInverse * vec4(screenPos, 1.0f);
		 fragposition /= fragposition.w;

	return fragposition;
}

float ScreenToViewSpaceDepth(float depth) {
    depth = depth * 2.0 - 1.0;
    return 1.0 / (depth * gbufferProjectionInverse[2][3] + gbufferProjectionInverse[3][3]);
}

vec3 GetNormals(in vec2 coord, MaterialMask mask) {
	vec3 normal = DecodeNormal(texture(colortex3, coord.st).xy);

	return normal;
}

float GetDepth(in vec2 coord, MaterialMask mask) {
	float depth = texture(depthtex1, coord.st).x;
	if(mask.particle > 0.5 || mask.particlelit > 0.5)
	depth = texture(gdepthtex, coord.st).x;
	return depth;
}

vec4 GetScreenSpacePosition(in vec2 coord, MaterialMask mask) {
	float depth = GetDepth(coord, mask);
	vec4 fragposition = gbufferProjectionInverse * vec4(fma(coord.st, vec2(2.0f), vec2(-1.0f)), fma(depth, 2.0f, -1.0f), 1.0f);
		 fragposition /= fragposition.w;

	return fragposition;
}

vec4 GetScreenSpacePosition(in vec2 coord, in float depth) {
	vec4 fragposition = gbufferProjectionInverse * vec4(fma(coord.st, vec2(2.0f), vec2(-1.0f)), fma(depth, 2.0f, -1.0f), 1.0f);
		 fragposition /= fragposition.w;

	return fragposition;
}

vec3 CalculateNoisePattern1(vec2 offset, float size) {
	vec2 coord = texcoord.st;

	coord *= vec2(viewWidth, viewHeight);
	coord = mod(coord + offset, vec2(size));
	coord /= noiseTextureResolution;

	return texture(noisetex, coord).xyz;
}

vec2 DistortShadowSpace(in vec2 pos)
{
	vec2 signedPos = fma(pos, vec2(2.0f), vec2(-1.0f));

	float dist = sqrt(signedPos.x * signedPos.x + signedPos.y * signedPos.y);
	float distortFactor = fma(dist, SHADOW_MAP_BIAS, (1.0f - SHADOW_MAP_BIAS));
	signedPos.xy *= 0.95 / distortFactor;

	pos = fma(signedPos, vec2(0.5f), vec2(0.5f));

	return pos;
}

vec3 Contrast(in vec3 color, in float contrast)
{
	float colorLength = length(color);
	vec3 nColor = color / colorLength;

	colorLength = pow(colorLength, contrast);

	return nColor * colorLength;
}

float 	GetMaterialIDs(in vec2 coord) {		
	return texture(colortex6, coord).b;
}

float GetSkylight(in vec2 coord)
{
	return textureLod(colortex3, coord, 0).a;
}

float 	GetMaterialMask(in vec2 coord, const in int ID) {
	float matID = (GetMaterialIDs(coord) * 255.0f);

	matID = (matID > 254.0f) ? 0.0f : matID;

	return (matID == ID) ? 1.0f : 0.0f;
}

bool 	GetSkyMask(in vec2 coord)
{
	float matID = GetMaterialIDs(coord);
	matID = floor(matID * 255.0f);

	if (matID < 1.0f || matID > 254.0f)
	{
		return true;
	} else {
		return false;
	}
}

vec3 ProjectBack(vec3 cameraSpace)
{
    vec4 clipSpace = gbufferProjection * vec4(cameraSpace, 1.0);
    vec3 NDCSpace = clipSpace.xyz / clipSpace.w;
    vec3 screenSpace = fma(NDCSpace, vec3(0.5f), vec3(0.5f));
    return screenSpace;
}

float 	ExpToLinearDepth(in float depth)
{
	return 2.0f * near * far / fma((near - far), fma(depth, 2.0f, -1.0f), (far + near));
}

float GetAO(vec2 coord, vec3 normal, float dither, MaterialMask mask)
{
	const int numRays = 16;

	const float phi = 1.618033988;
	const float gAngle = phi * 3.14159265 * 1.0003;

	float depth = GetDepth(coord, mask);
	float linDepth = ExpToLinearDepth(depth);
	vec3 origin = GetScreenSpacePosition(coord, depth).xyz;

	float aoAccum = 0.0;

	float radius = 0.30 * -origin.z;
		  radius = mix(radius, 0.8, 0.5);
	float zThickness = 0.30 * -origin.z;
		  zThickness = mix(zThickness, 1.0, 0.5);

	float aoMul = 1.0;

	for (int i = 0; i < numRays; i++)
	{
		float fi = float(i) + dither;
		float fiN = fi / float(numRays);
		float lon = gAngle * fi * 6.0;
		float lat = asin(fma(fiN, 2.0f, -1.0f)) * 1.0;

		vec3 kernel;
		kernel.x = cos(lat) * cos(lon);
		kernel.z = cos(lat) * sin(lon);
		kernel.y = sin(lat);

		kernel.xyz = normalize(kernel.xyz + normal.xyz);

		float sampleLength = radius * mod(fiN, 0.02f) / 0.02;

		vec3 samplePos = fma(vec3(sampleLength), kernel, origin);

		vec3 samplePosProj = ProjectBack(samplePos);

		vec3 actualSamplePos = GetScreenSpacePosition(samplePosProj.xy, GetDepth(samplePosProj.xy, mask)).xyz;

		vec3 sampleVector = normalize(samplePos - origin);

		float depthDiff = actualSamplePos.z - samplePos.z;

		if (depthDiff > 0.0 && depthDiff < zThickness)
		{
			float aow = 1.35 * saturate(dot(sampleVector, normal));
			aoAccum += aow;
		}
	}

	aoAccum /= numRays;

	float ao = 1.0 - aoAccum;
	ao = pow(ao, 1.7);

	return ao;
}

vec4 WorldToShadowProjPos(in vec4 spacePosition){

	vec4 shadowposition = shadowModelView * spacePosition;	
	shadowposition = shadowProjection * shadowposition;

	shadowposition /= shadowposition.w;

	return shadowposition * 0.5f + 0.5f;
}

#include "/preparer2.csh"

#include "/preparer0.csh"

#ifdef DISTANT_HORIZONS
uniform vec3 dh_sunDirection;
uniform vec3 dh_sunColor;
uniform float dh_time;
uniform float dh_waveHeight;
uniform sampler2D dh_waveNormalMap;
#endif

vec3 GetWavesNormal(vec3 position) {

    float WAVE_HEIGHT = 1.5;

    const float sampleDistance = 11.0;

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

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

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

    wavesNormal.r *= 30.0 * WAVE_HEIGHT / sampleDistance;
    wavesNormal.g *= 30.0 * WAVE_HEIGHT / sampleDistance;

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

    return wavesNormal.rgb;
}

void main() {
    MaterialMask mask;

    vec3 noisePattern = CalculateNoisePattern1(vec2(0.0), 4);

    vec4 light = vec4(0.0, 0.0, 0.0, 1.0);

    #ifdef GI
        light = GetLight(GI_RENDER_RESOLUTION, vec2(0.0), 16.0, GI_QUALITY, noisePattern, mask);
        light.a = mix(light.a, 1.0, mask.hand);
    #endif

    vec3 screenCaustics = GetWavesNormal(vec3(texcoord.s * 50.0, 1.0, texcoord.t * 50.0)).xyz;
    vec2 causticsNormal = EncodeNormal(screenCaustics);

    #ifdef DISTANT_HORIZONS
        vec2 dh_uv = texcoord.xy * 10.0 + vec2(dh_time * 0.05, dh_time * 0.03);
        vec3 dhWaveNormal = texture(dh_waveNormalMap, dh_uv).rgb * 2.0 - 1.0;

        vec3 combinedNormal = normalize(screenCaustics + dhWaveNormal * dh_waveHeight);

        float lightIntensity = max(dot(combinedNormal, normalize(dh_sunDirection)), 0.0);
        vec3 dhLight = dh_sunColor * lightIntensity;

        light.rgb += dhLight;

        causticsNormal = EncodeNormal(combinedNormal);

        compositeOutput1 = vec4(LinearToGamma(light.rgb), light.a);
        compositeOutput2 = vec4(causticsNormal.xy, 0.0, 0.0);
    #else
        compositeOutput1 = vec4(LinearToGamma(light.rgb), light.a);
        compositeOutput2 = vec4(causticsNormal.xy, 0.0, 0.0);
    #endif
}

/* DRAWBUFFERS:12 */
