/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/
#version 330

#pragma tdm_include "tdm_utils.glsl"
#pragma tdm_include "tdm_transform.glsl"
#pragma tdm_include "tdm_lightproject.glsl"
#pragma tdm_include "tdm_shadowmaps.glsl"

uniform sampler2D u_lightProjectionTexture;
uniform sampler2D u_lightFalloffTexture;
uniform sampler2D u_depthTexture;
uniform sampler2D u_shadowMap;

uniform vec3 u_viewOrigin;
uniform mat4 u_lightProject;
uniform vec4 u_lightFrustum[6];
uniform vec4 u_lightTextureMatrix[2];
uniform vec4 u_shadowRect;
uniform vec3 u_lightOrigin;
uniform int u_sampleCount;
uniform vec4 u_lightColor;
uniform int u_shadows;
uniform float u_dust;
uniform int u_randomize;
uniform int u_alphaMode;

in vec4 worldPosition;

out vec4 fragColor;

// 8x8 Bayer matrix
float DITHER_MATRIX[64] = float[](
	0, 32, 8, 40, 2, 34, 10, 42,
	48, 16, 56, 24, 50, 18, 58, 26,
	12, 44, 4, 36, 14, 46, 6, 38,
	60, 28, 52, 20, 62, 30, 54, 22,
	3, 35, 11, 43, 1, 33, 9, 41,
	51, 19, 59, 27, 49, 17, 57, 25,
	15, 47, 7, 39, 13, 45, 5, 37,
	63, 31, 55, 23, 61, 29, 53, 21
);

float ditherFraction() {
	int x = int(gl_FragCoord.x), y = int(gl_FragCoord.y);
	return DITHER_MATRIX[8 * (x&7) + (y&7)] / 64.0;
}

// get N samples from the fragment-view ray inside the frustum
vec3 calcWithSampling(vec3 rayStart, vec3 rayVec, float minParam, float maxParam, int samplesNum) {
	vec3 color = vec3(0.0);
	for (int i = 0; i < samplesNum; i++) { 
		float frac = 0.5;
		if (u_randomize != 0)
			frac = ditherFraction();
		float ratio = (i + frac) / samplesNum;
		vec3 samplePos = rayStart + rayVec * mix(minParam, maxParam, ratio);
		// shadow test
		vec3 light2fragment = samplePos - u_lightOrigin;
		float lit = 1;
		if (u_shadows != 0) {
			float depth = ShadowAtlasForVector(u_shadowMap, u_shadowRect, light2fragment);
			vec3 absL = abs(light2fragment);
			float maxAbsL = max(absL.x, max(absL.y, absL.z));
			lit = float(maxAbsL < depth);
		}
		vec4 texCoord = computeLightTex(u_lightProject, vec4(samplePos, 1));
		vec3 texColor = projFalloffOfNormalLight(u_lightProjectionTexture, u_lightFalloffTexture, u_lightTextureMatrix, texCoord);
		color += lit * texColor;
	}
	return color / samplesNum;
}

vec3 calcAverage(vec3 rayStart, vec3 rayVec, float minParam, float maxParam) {
	// 1. suppose that nothing is shadowed
	// 2. take average of both projection and falloff textures
	vec4 avgProjection = textureLod(u_lightProjectionTexture, vec2(0.5), 10000);
	vec4 avgFalloff = textureLod(u_lightFalloffTexture, vec2(0.5), 10000);
	return avgProjection.xyz * avgFalloff.xyz;
}

void main() {
	//cast segment from viewer eye to the fragment
	vec3 rayStart = u_viewOrigin;
	vec3 rayVec = worldPosition.xyz - u_viewOrigin;
	
	//intersect the segment with light polytope
	float minParam = 0.0;
	float maxParam = 1.0;
	for (int i = 0; i < 6; i++) {
		float dotnp = dot(u_lightFrustum[i], vec4(rayStart, 1.0));
		float dotnv = dot(u_lightFrustum[i].xyz, rayVec);
		float param = -dotnp / dotnv;
		if (dotnv > 0)
			maxParam = min(maxParam, param);
		else
			minParam = max(minParam, param);
	}

	//only consider visible part (not occluded by opaque geometry)
	vec2 depthTexCoord = gl_FragCoord.xy / textureSize(u_depthTexture, 0);
	float depth = texture2D(u_depthTexture, depthTexCoord).r;
	float solidParam = depthToZ(u_projectionMatrix, depth) / depthToZ(u_projectionMatrix, gl_FragCoord.z);
	maxParam = min(maxParam, solidParam);

	if (minParam >= maxParam)
		discard;    //no intersection
	float litDistance = (maxParam - minParam) * length(rayVec);
	
	vec3 avgColor;
	if (u_sampleCount > 0)
		avgColor = calcWithSampling(rayStart, rayVec, minParam, maxParam, u_sampleCount);
	else
		avgColor = calcAverage(rayStart, rayVec, minParam, maxParam);
	fragColor.rgb = u_lightColor.rgb * avgColor;

	if (u_alphaMode == 0) {
		//volumetric light: uncapped linear growth
		fragColor.a = u_dust * litDistance;
	}
	else if (u_alphaMode == 1) {
		//fog light: exponential light with forced cap at distance 1 / u_dust = cap
		float q = u_dust * litDistance;
		//start with exponential attenuation: 0 at D = 0, (1 - e^-5) at D = cap
		float exponential = 1.0 - exp(-q * 5.0);
		//then force fog to be opaque at D = cap
		float transition = clamp((q - 0.9) / 0.1, 0, 1);
		//exponential up to 90% of cap, perfectly opaque fog at 100% of cap (linear interpolation in-between)
		float alpha = mix(exponential, 1, transition);
		//add a bit of unscientific dithering to smoothen out color banding
		if (u_randomize != 0)
			alpha = min(alpha + 1e-2 * ditherFraction(), 1.0);
		fragColor.a = alpha;
	}
}
