#version 330

layout(std140) uniform Transformation {
	mat4 mView;
	mat4 mProjection;
	mat4 mViewProjection;
	mat4 mInverseViewProjection;
	vec3 vCameraPos;
	vec3 vCameraDir;
	float fNearClipPlane;
	vec3 vCameraUp;
	float fFarClipPlane;
};

layout(pixel_center_integer) in vec4 gl_FragCoord;
in vec3 vs_WorldPos;
uniform vec4 u_vParameter;		// (Voxel Density, Noise Threshold, 1-Noise Threshold, Scale)
//uniform float u_fVoxelDensity;
uniform vec2 u_fPlanes;			// Lower and upper volume limit
uniform vec2 u_vHighToVolume;	// MAD transformation from World.y to [-1, 1] in the volume
uniform vec3 u_vColorBright;
uniform vec3 u_vColorDark;
uniform vec3 u_vWind1;
uniform vec3 u_vWind2;
uniform sampler3D u_Noise;
uniform sampler2D u_Depth;

const vec3 c_vLight = vec3( 0.707106, 0.707106, 0.0 );
//const vec3 c_vColorBright = vec3(1.2, 1.152, 1.2);
//const vec3 c_vColorBright = vec3(1.0, 0.96, 1.0);

//const vec3 c_vColorDark = vec3(0.666666, 0.666666, 0.706);
//const vec3 c_vColorDark = vec3(0.5843, 0.557, 0.592);
//const vec3 c_vColorDark = vec3(0.4843, 0.457, 0.542);

out vec4 fs_Color;


// Density function. One part for the high, so that the clouds are not clipped
// and one is the perlin noise.
// TODO: inline?
float DensityFunction( vec3 _vPos )
{
	float fDensity = _vPos.y * u_vHighToVolume.x + u_vHighToVolume.y;
	fDensity = fDensity*fDensity;
	fDensity = - u_vParameter.y - fDensity*u_vParameter.z;
	float fAlpha = u_vParameter.x * max( 0, texture( u_Noise, _vPos * u_vParameter.w + u_vWind1 ).r + fDensity );
	fAlpha *= u_vParameter.x * max( 0, texture( u_Noise, _vPos.yxz * u_vParameter.w + u_vWind2 ).r + fDensity );
	return fAlpha;
}


// The ray marcher differs from the main one:
//	- stop after n steps, no depth test
//	- no alpha testing
//	- no call of lightocclusion
float lightocclusion( vec3 _vPos, vec3 _vDir )
{	
	float fDens = 1.0;

	for( int i=0; i<6; ++i)
	{
		// Next pos first - starting at the already sampled point.
		float fLen = 1.3;		// A little bit higher (light tends to produce less artifacts)
		_vPos += _vDir * fLen;
		
		// Density function. One part for the high, so that the clouds are not clipped
		// and one is the perlin noise.
	//	float fDensity = _vPos.y * u_vHighToVolume.x + u_vHighToVolume.y;
	//	fDensity = fDensity*fDensity;
		fDens += DensityFunction( _vPos );// u_vParameter.x * max( 0, texture( u_Noise, _vPos * u_vParameter.w ).r - u_vParameter.y - fDensity*u_vParameter.z );
	}
	return fDens;
}


// main ray marching function
// The step length is dynamic. The next position is always the start of the next voxel
// in the ray and therefore the end of the current voxel. So the correct distance through
// filled voxels is computed.
vec3 density( vec3 _vPos, vec3 _vDir, vec3 _vLight, float _fZ )
{
	vec3 vRes = vec3( 1, 0, length( _vPos - vCameraPos ) );	// density, light and final depth

	
//	float fStepWidth = 0.006 * vRes.z;
	float fRefY = _vDir.y;
	
	while( (_fZ > vRes.z) && ((_vPos.y-0.0001) < u_fPlanes.y) )//&& ((_vPos.y+0.0001) > u_fPlanes.x) && ((_vPos.y-0.0001) < u_fPlanes.y) )
	{
		// Dithered length - artifact reduction
		//float fLen = min( _fZ, texture( u_Noise, vec2(_vPos.x*_vPos.z+_vPos.y, _vPos.y*_vPos.z+_vPos.x) ).r*0.2+0.7 );

		float fStepWidth = 0.6 + 0.006 * vRes.z;
		float fLen = min( _fZ-vRes.z, fStepWidth );
		
		float fAlpha = DensityFunction( _vPos ) * fLen;
		// Make clouds more transparent in the depth (instead of depth fog)
		fAlpha *= 1.0 - vRes.z / (2*fFarClipPlane);
//		fAlpha = mix( fAlpha, 1, vRes.z / (2*fFarClipPlane));
		//float fA = max(0,vRes.z-fFarClipPlane) / (fFarClipPlane);
		//float fA = 0.025 * vRes.z / (2*fFarClipPlane);
		//fAlpha += fA;//0.05 * max(0,vRes.z-fFarClipPlane) / (fFarClipPlane);

		// Lightning
		if( fAlpha > 0.0001 )
		{
			vRes.y += fAlpha/lightocclusion( _vPos, _vLight ) * vRes.x;// * dot( _vLight, vTex.yzw );

			//vRes.x = min( 1, vRes.x+fAlpha );
			vRes.x *= 1 - fAlpha;
			
			// "Alphatesting"
			if( vRes.x < 0.004 ) return vec3( 0, vRes.y, vRes.z );
		}
				
		// 1 Use this line(s) to get a spherical effect
		_vDir.y = fRefY + 0.25 * max(0,vRes.z - fFarClipPlane) / fFarClipPlane;

		vRes.z += fLen;
		_vPos += _vDir * fLen;
	}
	
//	vRes.x += fDepth/fFarClipPlane;
	return vRes;
}



void main(void)
{
	// Calculate the ray from camera through the cloud volume
	vec3 vDir = vs_WorldPos - vCameraPos;
	vDir = normalize( vDir );

	// The position is at least 4 times fNearClipPlane away from the camera (stability of plane transformation)
	// But ray should start at nearplane.
	vec3 vStart = vs_WorldPos - 3.0f * fNearClipPlane * vDir;

	// Set ray lengthes such they produce less artifacts
	// Transform to grid space
	vec3 vLightN = normalize( c_vLight ) ;
			
	// Project into volume if outside y range
	float fMaxDist = fFarClipPlane-fNearClipPlane;
	// max( u_fPlanes.x-vStart.y, 0 )
	float fProjDist = ( - max( vStart.y-u_fPlanes.y, 0 )) / vDir.y;//(max(0.1, abs(vDir.y))*sign(vDir.y));
//	fProjDist = min( fMaxDist, fProjDist );
//	if( fProjDist > fMaxDist ) fProjDist = 0;
	if( u_fPlanes.x-vStart.y > 0 && vDir.y < -0.01 ) fProjDist = fMaxDist;
	vStart = vStart + fProjDist * vDir;

//	vStart = vStart + ( max( vStart.y-u_fPlanes.y, max(0, u_fPlanes.x-vStart.y ) ) / abs(vDir.y) ) * vDir;
	//float f = max(0,length( vStart-vCameraPos ) - fFarClipPlane);
//	vStart -= vDir * f;
	
	// Where to end?
	ivec2 vTex = ivec2(gl_FragCoord.x*4+1.5, gl_FragCoord.y*4+1.5);
	float fZ1, fZ2, fZ3, fZ4;
//	fZ1 = max( texelFetch( u_Depth, vTex+ivec2(-1,-1), 0 ).r, texelFetch( u_Depth, vTex+ivec2(2,-1), 0 ).r );
//	fZ1 = max( max( texelFetch( u_Depth, vTex+ivec2(-1,-1), 0 ).r, texelFetch( u_Depth, vTex+ivec2(0,-1), 0 ).r ), max( texelFetch( u_Depth, vTex+ivec2(1,-1), 0 ).r, texelFetch( u_Depth, vTex+ivec2(2,-1), 0 ).r ) );
//	fZ2 = max( max( texelFetch( u_Depth, vTex+ivec2(-1, 0), 0 ).r, texelFetch( u_Depth, vTex            , 0 ).r ), max( texelFetch( u_Depth, vTex+ivec2(1, 0), 0 ).r, texelFetch( u_Depth, vTex+ivec2(2, 0), 0 ).r ) );
//	fZ3 = max( max( texelFetch( u_Depth, vTex+ivec2(-1, 1), 0 ).r, texelFetch( u_Depth, vTex+ivec2(0, 1), 0 ).r ), max( texelFetch( u_Depth, vTex+ivec2(1, 1), 0 ).r, texelFetch( u_Depth, vTex+ivec2(2, 1), 0 ).r ) );
//	fZ4 = max( max( texelFetch( u_Depth, vTex+ivec2(-1, 2), 0 ).r, texelFetch( u_Depth, vTex+ivec2(0, 2), 0 ).r ), max( texelFetch( u_Depth, vTex+ivec2(1, 2), 0 ).r, texelFetch( u_Depth, vTex+ivec2(2, 2), 0 ).r ) );
//	fZ4 = max( texelFetch( u_Depth, vTex+ivec2(-1, 2), 0 ).r, texelFetch( u_Depth, vTex+ivec2(2, 2), 0 ).r );
//	float fZ = max( max(fZ1,fZ2), max(fZ3,fZ4) );
//	float fZ = max(fZ1,fZ4);
	float fZ = texelFetch( u_Depth, vTex, 0 ).r;
	float f_n = fFarClipPlane - fNearClipPlane;
	fZ = (- fFarClipPlane * fNearClipPlane / (fZ*f_n-fFarClipPlane));
	fZ = ( ( fZ >= fFarClipPlane ) ? (fFarClipPlane*2) : fZ );
	
	// if so follow the ray and cast an other one into light direction
	vec3 vRes = density( vStart, vDir, vLightN, fZ );
	
	fs_Color.r = vRes.y;
	fs_Color.g = (1-vRes.x);// * (1-vRes.z/fFarClipPlane);
	fs_Color.z = 0.5 * fZ / fFarClipPlane;
//	fs_Color.rgb = mix( u_vColorDark, u_vColorBright, vRes.y );
//	fs_Color.a = 1-vRes.x;
	//if( 1-vRes.x < 0.1 ) discard;
}
