// composite0.fsh - VXGI WITH COLORED GLOW TRACING

in vec2 screenCoord;

#include "/lib/Head/Common.inc"
#include "/lib/Head/Uniforms.inc"

uniform bool worldTimeChanged;

//============================================================================//
// VXGI CONFIGURATION
//============================================================================//

#define VXGI_ENABLED
#define VOXEL_RADIUS 64
#define VXGI_MODE 1

//============================================================================//
// VXGI BUFFERS
//============================================================================//

#ifdef VXGI_ENABLED
uniform sampler3D colortex8;  // Voxel Albedo
uniform sampler3D colortex9;  // Voxel GI (Direct + Traced)
#if VXGI_MODE == 2
uniform sampler3D colortex10; // Voxel GI Floodfill (only if MODE=2)
#endif
#endif

//============================================================================//
// OUTPUT BUFFERS
//============================================================================//

layout(location = 0) out vec4 indirectData;
#if defined IS_OVERWORLD
	layout(location = 1) out vec4 skyboxData;
	/* DRAWBUFFERS:01 */
#else
	/* DRAWBUFFERS:05 */
#endif

//============================================================================//
// INCLUDES
//============================================================================//

#include "/lib/Head/Functions.inc"

#ifdef SSAO_ENABLED
	#include "/lib/Lighting/AmbientOcclusion.glsl"
#endif

#ifdef GI_ENABLED
	#ifdef VXGI_ENABLED
		// VXGI dual-mode includes
		#include "/lib/Voxel/VoxelStructure.glsl"
		#include "/lib/Voxel/VoxelSampling.glsl"
		#include "/lib/Lighting/GlobalIllumination.glsl"
		
		// 🌟 НОВОЕ: Цветное свечение с voxel raytracing
		#include "/lib/Lighting/ColoredGlowTracing.glsl"
	#else
		// Old SSGI fallback
		#include "/lib/Lighting/GlobalIllumination.glsl"
	#endif
#endif

//============================================================================//
// TEMPORAL ACCUMULATION
//============================================================================//

const ivec2 checkerboardOffset[64] = ivec2[64](
    ivec2(0,0), ivec2(1,0), ivec2(2,0), ivec2(3,0), ivec2(4,0), ivec2(5,0), ivec2(6,0), ivec2(7,0),
    ivec2(0,1), ivec2(1,1), ivec2(2,1), ivec2(3,1), ivec2(4,1), ivec2(5,1), ivec2(6,1), ivec2(7,1),
    ivec2(0,2), ivec2(1,2), ivec2(2,2), ivec2(3,2), ivec2(4,2), ivec2(5,2), ivec2(6,2), ivec2(7,2),
    ivec2(0,3), ivec2(1,3), ivec2(2,3), ivec2(3,3), ivec2(4,3), ivec2(5,3), ivec2(6,3), ivec2(7,3),
    ivec2(0,4), ivec2(1,4), ivec2(2,4), ivec2(3,4), ivec2(4,4), ivec2(5,4), ivec2(6,4), ivec2(7,4),
    ivec2(0,5), ivec2(1,5), ivec2(2,5), ivec2(3,5), ivec2(4,5), ivec2(5,5), ivec2(6,5), ivec2(7,5),
    ivec2(0,6), ivec2(1,6), ivec2(2,6), ivec2(3,6), ivec2(4,6), ivec2(5,6), ivec2(6,6), ivec2(7,6),
    ivec2(0,7), ivec2(1,7), ivec2(2,7), ivec2(3,7), ivec2(4,7), ivec2(5,7), ivec2(6,7), ivec2(7,7)
);

vec4 TemporalLightAccumulation() {
	if (all(lessThan(screenCoord, vec2(0.5)))) {
		ivec2 texel = ivec2(gl_FragCoord.xy) * 2;
		float depth = GetDepth(texel);

		#if defined DISTANT_HORIZONS
			float dhDepth = GetDepthDH(texel);
			if (min(dhDepth, depth) >= 1.0) return vec4(0.0);
		#else
			if (depth >= 1.0) return vec4(0.0);
		#endif

		vec2 coord = screenCoord * 2.0;
		vec3 screenPos = vec3(coord, depth);
		vec3 viewPos = ScreenToViewSpace(screenPos);

		#if defined DISTANT_HORIZONS
			if (depth >= 1.0) {
				screenPos = vec3(coord, dhDepth);
				viewPos = ScreenToViewSpaceDH(screenPos);
			}
		#endif

		vec3 normal = GetNormals(texel);
		vec3 worldNormal = mat3(gbufferModelViewInverse) * normal;

		vec4 currLight = vec4(vec3(0.0), 1.0);

		//====================================================================//
		// SSAO
		//====================================================================//
		
		#ifdef SSAO_ENABLED
			float dither = texelFetch(noisetex, texel & 255, 0).a;
			dither = fract(dither + frameCounter * rPI);
			#if defined DISTANT_HORIZONS
				if (depth >= 1.0) currLight.a = SpiralAO_DH(coord, viewPos, normal, dither);
				else 
			#endif
			if (depth > 0.56) currLight.a = SpiralAO(coord, viewPos, normal, dither);
		#endif

		//====================================================================//
		// GI: RSM + VOXEL AO + COLORED BLOCKS + 🌟 GLOW TRACING
		//====================================================================//
		
		#ifdef GI_ENABLED
			vec3 worldPos = (gbufferModelViewInverse * vec4(viewPos, 1.0)).xyz + cameraPosition;
			
			#ifdef VXGI_ENABLED
				// Get RSM GI (reflective shadow maps)
				vec3 rsmGI = CalculateRSM(viewPos, worldNormal, RandNextF());
				
				// Get voxel AO (multiplicative - darkens occluded areas)
				vec3 voxelAO = CalculateVoxelAO_Only(
					worldPos,
					viewPos,
					worldNormal,
					colortex8   // Voxel albedo
				);
				
				// Get colored block light (additive - colored light from blocks)
				vec3 coloredLight = CalculateColoredBlockLight_Only(
					worldPos,
					viewPos,
					worldNormal,
					colortex8,  // Voxel albedo
					colortex0   // Block ID (placeholder)
				);
				
				// ================================================================
				// 🌟 НОВОЕ: Volumetric Colored Glow Tracing
				// Создаёт объёмное цветное свечение вокруг светящихся блоков
				// ================================================================
				vec3 glowTracing = TraceColoredGlow(
					worldPos,           // World position
					viewPos,            // View direction (from camera)
					worldNormal,        // Surface normal
					screenCoord,        // Screen UV for noise
					frameCounter,       // Temporal dithering
					colortex8           // Voxel albedo texture
				);
				
				// COMBINE ALL LIGHTING:
				// - RSM GI × voxel AO (GI darkened by occlusion)
				// - + colored block light (additive colored light)
				// - + glow tracing (additive volumetric glow)
				currLight.rgb = rsmGI * voxelAO + coloredLight + glowTracing;
			#else
				// Fallback: RSM only
				currLight.rgb = CalculateRSM(viewPos, worldNormal, RandNextF());
			#endif
		#endif

		//====================================================================//
		// TEMPORAL BLENDING
		//====================================================================//
		
		vec2 prevCoord = Reproject(screenPos).xy;

		#if defined DISTANT_HORIZONS
			if (depth >= 1.0) prevCoord = ReprojectDH(screenPos).xy;
		#endif
		if (clamp(prevCoord, screenPixelSize, 1.0 - screenPixelSize) != prevCoord || worldTimeChanged) return currLight;

		prevCoord *= 0.5;

		vec4 prevData = texture(colortex0, prevCoord + vec2(0.5, 0.0));

		float currDist = GetDepthLinear(depth);

		float cameraMovement = distance(cameraPosition, previousCameraPosition);

		float weight = step((distance(currDist, prevData.a) - cameraMovement) / abs(currDist), 0.1);
		weight *= step(0.5, dot(normal, prevData.xyz));

		vec4 blendWeight = vec4(vec3(0.97), 0.75) * weight;

		vec4 prevLight = texture(colortex0, prevCoord);

		return clamp16F(mix(currLight, prevLight, blendWeight));
	} else if (screenCoord.y < 0.5) {
		ivec2 texel = ivec2(gl_FragCoord.xy) * 2 - ivec2(viewWidth, 0);
		float depth = GetDepth(texel);

		#if defined DISTANT_HORIZONS
			float dhDepth = GetDepthDH(texel);
			if (min(dhDepth, depth) >= 1.0) return vec4(0.0);

			float dist = depth < 1.0 ? GetDepthLinear(depth) : GetDepthLinearDH(dhDepth);
		#else
			if (depth >= 1.0) return vec4(0.0);
			float dist = GetDepthLinear(depth);
		#endif

		vec3 normal = GetNormals(texel);

		return vec4(normal, dist);
	}
}

//============================================================================//
// CATMULL-ROM FILTERING
//============================================================================//

vec4 textureCatmullRom(sampler2D tex, vec2 uv) {
    vec2 texSize = textureSize(tex, 0);
    vec2 samplePos = uv * texSize;
    vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
    
    vec2 f = samplePos - texPos1;
    vec2 f2 = f * f;
    vec2 f3 = f2 * f;
    
    vec2 w0 = f2 - 0.5 * (f3 + f);
    vec2 w1 = 1.5 * f3 - 2.5 * f2 + 1.0;
    vec2 w3 = 0.5 * (f3 - f2);
    vec2 w2 = 1.0 - w0 - w1 - w3;
    
    vec2 w12 = w1 + w2;
    
    vec2 tc0 = (texPos1 - 1.0) / texSize;
    vec2 tc12 = (texPos1 + w2 / w12) / texSize;
    vec2 tc3 = (texPos1 + 2.0) / texSize;
    
    vec4 result = 
        texture(tex, vec2(tc0.x,  tc0.y))  * w0.x  * w0.y +
        texture(tex, vec2(tc12.x, tc0.y))  * w12.x * w0.y +
        texture(tex, vec2(tc3.x,  tc0.y))  * w3.x  * w0.y +
        texture(tex, vec2(tc0.x,  tc12.y)) * w0.x  * w12.y +
        texture(tex, vec2(tc12.x, tc12.y)) * w12.x * w12.y +
        texture(tex, vec2(tc3.x,  tc12.y)) * w3.x  * w12.y +
        texture(tex, vec2(tc0.x,  tc3.y))  * w0.x  * w3.y +
        texture(tex, vec2(tc12.x, tc3.y))  * w12.x * w3.y +
        texture(tex, vec2(tc3.x,  tc3.y))  * w3.x  * w3.y;
    
    return result;
}

//============================================================================//
// MAIN
//============================================================================//

void main() {
	#if defined GI_ENABLED || defined SSAO_ENABLED
		indirectData = TemporalLightAccumulation();
	#else
		indirectData = vec4(vec3(0.0), 1.0);
	#endif

	#if defined IS_OVERWORLD
		#if defined DISTANT_HORIZONS
			#define Reproject ReprojectDH
		#endif

		ivec2 texel = ivec2(gl_FragCoord.xy);

		vec2 previousCoord = Reproject(vec3(screenCoord, 1.0)).xy;
		vec2 historyData = texture(colortex0, previousCoord * 0.5 + 0.5).xy;

		if (all(greaterThanEqual(screenCoord, vec2(0.5)))) {
			indirectData.x = 1.0 + texture(colortex0, Reproject(vec3(screenCoord * 2.0 - 1.0, 1.0)).xy * 0.5 + 0.5).x;
			ivec2 texel = ivec2(gl_FragCoord.xy) * 2 - ivec2(screenSize);
			indirectData.y = 1.0 - GetDepth(texel);
		}

		if (saturate(previousCoord) != previousCoord
		 || GetDepth(texel) >= 1.0 && historyData.y > 1e-6
		 || worldTimeChanged) {
			skyboxData = textureBicubic(colortex2, min(screenCoord * rcp(float(TEMPORAL_UPSCALING)), rcp(TEMPORAL_UPSCALING) - screenPixelSize));
		} else {
			const int cloudsRenderFactor = TEMPORAL_UPSCALING * TEMPORAL_UPSCALING;

			ivec2 offset = checkerboardOffset[frameCounter % cloudsRenderFactor];
			ivec2 currentTexel = clamp((texel - offset) / TEMPORAL_UPSCALING, ivec2(0), ivec2(screenSize) / TEMPORAL_UPSCALING - 1);

			float cameraMovement = expf(-16.0 * distance(cameraPosition, previousCameraPosition));
			historyData.x = min(historyData.x, MAX_BLENDED_FRAMES);
			float blendWeight = 1.0 - rcp(max(historyData.x - cloudsRenderFactor, 1.0));
			blendWeight *= mix(1.0, cameraMovement, blendWeight);
			vec2 pixelVelocity = 1.0 - abs(fract(previousCoord * screenSize) * 2.0 - 1.0);
			blendWeight *= sqrt(pixelVelocity.x * pixelVelocity.y) * 0.75 + 0.25;
			blendWeight *= mix(1.0, sqrt(pixelVelocity.x * pixelVelocity.y), blendWeight);

			vec4 prevData = clamp16F(textureCatmullRom(colortex1, previousCoord));
			skyboxData = texelFetch(colortex2, currentTexel, 0);
			if (offset == texel % TEMPORAL_UPSCALING) {
				skyboxData = mix(skyboxData, prevData, blendWeight);
			} else skyboxData = prevData;
		}
	#endif
}
