#include "DataPacking.inc"
#include "TAA.inc"






float saturate(float x)
{
	return clamp(x, 0.0, 1.0);
}

vec3 saturate(vec3 x)
{
	return clamp(x, vec3(0.0), vec3(1.0));
}

vec2 saturate(vec2 x)
{
	return clamp(x, vec2(0.0), vec2(1.0));
}

// polynomial smooth min (k = 0.1); https://www.iquilezles.org/www/articles/smin/smin.htm
float SmoothMin( float a, float b, float k )
{
    float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
    return mix( b, a, h ) - k*h*(1.0-h);
}

float SmoothMax( float a, float b, float k )
{
    return -SmoothMin(-a, -b, k);
}

vec3 nozero(vec3 x)
{
	if (abs(x.x) < 0.001)
		x.x = 0.001;

	if (abs(x.y) < 0.001)
		x.y = 0.001;

	if (abs(x.z) < 0.001)
		x.z = 0.001;

	return x;
}

vec3 Fract01(vec3 pos)
{













    return fract(pos - vec3(0.00001)) + vec3(0.00001);

}

vec3 ClipCheck(vec3 color)
{
	color.r = color.r > 1.0 ? 0.0 : color.r;
	color.g = color.g > 1.0 ? 0.0 : color.g;
	color.b = color.b > 1.0 ? 0.0 : color.b;

	return color;
}






vec2 EncodeNormal(vec3 normal)
{
	float p = sqrt(normal.z * 8.0 + 8.0);
	return vec2(normal.xy / p + 0.5);
}

vec3 DecodeNormal(vec2 enc)
{
	vec2 fenc = enc * 4.0 - 2.0;
	float f = dot(fenc, fenc);
	float g = sqrt(1.0 - f / 4.0);
	vec3 normal;
	normal.xy = fenc * g;
	normal.z = 1.0 - f / 2.0;
	return normal;
}






vec4 GetViewPosition(in vec2 coord, in float depth) 
{	
	vec2 tcoord = coord;
	TemporalJitterProjPosInv01(tcoord);

	vec4 fragposition = gbufferProjectionInverse * vec4(tcoord.s * 2.0f - 1.0f, tcoord.t * 2.0f - 1.0f, 2.0f * depth - 1.0f, 1.0f);
		 fragposition /= fragposition.w;
	
	return fragposition;
}

vec4 GetViewPositionNoJitter(in vec2 coord, in float depth) 
{	
	vec4 tcoord = vec4(coord.xy, 0.0, 0.0);

	vec4 fragposition = gbufferProjectionInverse * vec4(tcoord.s * 2.0f - 1.0f, tcoord.t * 2.0f - 1.0f, 2.0f * depth - 1.0f, 1.0f);
		 fragposition /= fragposition.w;

	
	return fragposition;
}

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


vec3 WorldPosToShadowProjPos(vec3 worldPos, out float dist, out float distortFactor)
{
	vec4 sp = (shadowModelView * vec4(worldPos, 1.0));
	sp = shadowProjection * sp;
	sp /= sp.w;

	dist = sqrt(sp.x * sp.x + sp.y * sp.y);
	distortFactor = (1.0f - SHADOW_MAP_BIAS) + dist * SHADOW_MAP_BIAS;
	sp.xy *= 0.95f / distortFactor;
	sp.z = mix(sp.z, 0.5, 0.8);
	sp = sp * 0.5f + 0.5f;		//Transform from shadow space to shadow map coordinates


	//move to quadrant
	sp.xy *= 0.5;
	sp.xy += 0.5;

	return sp.xyz;
}



float 	ExpToLinearDepth(in float depth)
{






		vec2 a = vec2(depth * 2.0 - 1.0, 1.0);

		// vec2 d = vec2(dot(vec2(gbufferProjectionInverse[2].z, gbufferProjectionInverse[3].z), a),
		// 			  dot(vec2(gbufferProjectionInverse[2].w, gbufferProjectionInverse[3].w), a));

		mat2 ipm = mat2(gbufferProjectionInverse[2].z, gbufferProjectionInverse[2].w,
						gbufferProjectionInverse[3].z, gbufferProjectionInverse[3].w);

		vec2 d = ipm * a;

		d.x /= d.y;

		return -d.x;










}

float GetDepthLinear(in vec2 coord) 
{					
	// return (near * far) / (texture2D(depthtex1, coord).x * (near - far) + far);
	return ExpToLinearDepth(texture2D(depthtex1, coord).x);
}

float GetDepth(vec2 coord)
{
	return texture2D(depthtex1, coord).x;
}

vec3 GetNormals(vec2 coord)
{
	return DecodeNormal(texture2D(colortex2, coord).xy);
}









vec4 SampleLinear(sampler2D tex, vec2 coord)
{
	return pow(texture2D(tex, coord), vec4(2.2));
}

vec3 LinearToGamma(vec3 c)
{
	return pow(c, vec3(1.0 / 2.2));
}

vec3 GammaToLinear(vec3 c)
{
	return pow(c, vec3(2.2));
}





float curve(float x)
{
	return x * x * (3.0 - 2.0 * x);
}

float Luminance(in vec3 color)
{
	return dot(color.rgb, vec3(0.2125f, 0.7154f, 0.0721f));
}




















vec3 rand(vec2 coord)
{
	float noiseX = saturate(fract(sin(dot(coord, vec2(12.9898, 78.223))) * 43758.5453));
	float noiseY = saturate(fract(sin(dot(coord, vec2(12.9898, 78.223)*2.0)) * 43758.5453));
	float noiseZ = saturate(fract(sin(dot(coord, vec2(12.9898, 78.223)*3.0)) * 43758.5453));

	return vec3(noiseX, noiseY, noiseZ);
}

vec3 BlueNoiseTemporal(vec2 coord)
{
	vec2 noiseCoord = vec2(coord.st * vec2(viewWidth, viewHeight)) / 64.0;
	// //noiseCoord += vec2(frameCounter, frameCounter);
	// //noiseCoord += mod(frameCounter, 16.0) / 16.0;
	// //noiseCoord += rand(vec2(mod(frameCounter, 16.0) / 16.0, mod(frameCounter, 16.0) / 16.0) + 0.5).xy;
	// noiseCoord += vec2(sin(frameCounter * 0.75), cos(frameCounter * 0.75));

	// noiseCoord = (floor(noiseCoord * 64.0) + 0.5) / 64.0;

	vec3 irrationals = vec3(sqrt(1.0 / 5.0), sqrt(2.0), 1.61803398) * 1.0;

	vec3 n = texture2D(noisetex, noiseCoord).rgb;

	n = mod(n + irrationals * mod(frameCounter, 64.0f), vec3(1.0));

	return n;
}

float Get2DNoise(in vec2 pos)
{
	pos.xy += 0.5f;

	vec2 p = floor(pos);
	vec2 f = fract(pos);

	f.x = f.x * f.x * (3.0f - 2.0f * f.x);
	f.y = f.y * f.y * (3.0f - 2.0f * f.y);

	vec2 uv =  p.xy + f.xy;

	// uv -= 0.5f;
	// uv2 -= 0.5f;

	vec2 coord =  (uv  + 0.5f) / 64.0;
	float xy1 = texture2D(noisetex, coord).x;
	return xy1;
}

float Get3DNoise(in vec3 pos)
{
	pos.z += 0.0f;

	pos.xyz += 0.5f;

	vec3 p = floor(pos);
	vec3 f = fract(pos);

	// f.x = f.x * f.x * (3.0f - 2.0f * f.x);
	// f.y = f.y * f.y * (3.0f - 2.0f * f.y);
	// f.z = f.z * f.z * (3.0f - 2.0f * f.z);

	vec2 uv =  (p.xy + p.z * vec2(17.0f)) + f.xy;
	vec2 uv2 = (p.xy + (p.z + 1.0f) * vec2(17.0f)) + f.xy;

	// uv -= 0.5f;
	// uv2 -= 0.5f;

	vec2 coord =  (uv  + 0.5f) / 64.0;
	vec2 coord2 = (uv2 + 0.5f) / 64.0;
	float xy1 = texture2D(noisetex, coord).x;
	float xy2 = texture2D(noisetex, coord2).x;
	return mix(xy1, xy2, f.z);
}


/* https://www.shadertoy.com/view/XsX3zB
 *
 * The MIT License
 * Copyright © 2013 Nikita Miropolskiy
 * 
 * ( license has been changed from CCA-NC-SA 3.0 to MIT
 *
 *   but thanks for attributing your source code when deriving from this sample 
 *   with a following link: https://www.shadertoy.com/view/XsX3zB )
 *
 * ~
 * ~ if you're looking for procedural noise implementation examples you might 
 * ~ also want to look at the following shaders:
 * ~ 
 * ~ Noise Lab shader by candycat: https://www.shadertoy.com/view/4sc3z2
 * ~
 * ~ Noise shaders by iq:
 * ~     Value    Noise 2D, Derivatives: https://www.shadertoy.com/view/4dXBRH
 * ~     Gradient Noise 2D, Derivatives: https://www.shadertoy.com/view/XdXBRH
 * ~     Value    Noise 3D, Derivatives: https://www.shadertoy.com/view/XsXfRH
 * ~     Gradient Noise 3D, Derivatives: https://www.shadertoy.com/view/4dffRH
 * ~     Value    Noise 2D             : https://www.shadertoy.com/view/lsf3WH
 * ~     Value    Noise 3D             : https://www.shadertoy.com/view/4sfGzS
 * ~     Gradient Noise 2D             : https://www.shadertoy.com/view/XdXGW8
 * ~     Gradient Noise 3D             : https://www.shadertoy.com/view/Xsl3Dl
 * ~     Simplex  Noise 2D             : https://www.shadertoy.com/view/Msf3WH
 * ~     Voronoise: https://www.shadertoy.com/view/Xd23Dh
 * ~ 
 *
 */

/* discontinuous pseudorandom uniformly distributed in [-0.5, +0.5]^3 */
vec3 random3(vec3 c) {
  float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0)));
  vec3 r;
  r.z = fract(512.0*j);
  j *= .125;
  r.x = fract(512.0*j);
  j *= .125;
  r.y = fract(512.0*j);
  return r-0.5;
}

/* skew constants for 3d simplex functions */
const float F3 =  0.3333333;
const float G3 =  0.1666667;

/* 3d simplex noise */
float simplex3d(vec3 p) {
   /* 1. find current tetrahedron T and it's four vertices */
   /* s, s+i1, s+i2, s+1.0 - absolute skewed (integer) coordinates of T vertices */
   /* x, x1, x2, x3 - unskewed coordinates of p relative to each of T vertices*/
   
   /* calculate s and x */
   vec3 s = floor(p + dot(p, vec3(F3)));
   vec3 x = p - s + dot(s, vec3(G3));
   
   /* calculate i1 and i2 */
   vec3 e = step(vec3(0.0), x - x.yzx);
   vec3 i1 = e*(1.0 - e.zxy);
   vec3 i2 = 1.0 - e.zxy*(1.0 - e);
    
   /* x1, x2, x3 */
   vec3 x1 = x - i1 + G3;
   vec3 x2 = x - i2 + 2.0*G3;
   vec3 x3 = x - 1.0 + 3.0*G3;
   
   /* 2. find four surflets and store them in d */
   vec4 w, d;
   
   /* calculate surflet weights */
   w.x = dot(x, x);
   w.y = dot(x1, x1);
   w.z = dot(x2, x2);
   w.w = dot(x3, x3);
   
   /* w fades from 0.6 at the center of the surflet to 0.0 at the margin */
   w = max(0.6 - w, 0.0);
   
   /* calculate surflet components */
   d.x = dot(random3(s), x);
   d.y = dot(random3(s + i1), x1);
   d.z = dot(random3(s + i2), x2);
   d.w = dot(random3(s + 1.0), x3);
   
   /* multiply d by w^4 */
   w *= w;
   w *= w;
   d *= w;
   
   /* 3. return the sum of the four surflets */
   return dot(d, vec4(52.0));
}





















vec4 ToSH(float value, vec3 dir)
{
	const float PI = 3.14159265359;
	const float N1 = sqrt(4 * PI / 3);
	const float transferl1 = (sqrt(PI) / 3.0) * N1;
	const float transferl0 = PI;

	const float sqrt1OverPI = sqrt(1.0 / PI);
	const float sqrt3OverPI = sqrt(3.0 / PI);

	vec4 coeffs;

	coeffs.x = 0.5 * sqrt1OverPI * value * transferl0;
	coeffs.y = -0.5 * sqrt3OverPI * dir.y * value * transferl1;
	coeffs.z = 0.5 * sqrt3OverPI * dir.z * value * transferl1;
	coeffs.w = -0.5 * sqrt3OverPI * dir.x * value * transferl1; //TODO: Vectorize the math so it's faster

	return coeffs;
}


vec3 FromSH(vec4 cR, vec4 cG, vec4 cB, vec3 lightDir)
{
	const float PI = 3.14159265;

	const float N1 = sqrt(4 * PI / 3);
	const float transferl1 = (sqrt(PI) / 3.0) * N1;
	const float transferl0 = PI;

	const float sqrt1OverPI = sqrt(1.0 / PI);
	const float sqrt3OverPI = sqrt(3.0 / PI);

	vec4 sh;

	sh.x = 0.5 * sqrt1OverPI;
	sh.y = -0.5 * sqrt3OverPI * lightDir.y;
	sh.z = 0.5 * sqrt3OverPI * lightDir.z;
	sh.w = -0.5 * sqrt3OverPI * lightDir.x;

	vec3 result;
	result.r = sh.x * cR.x;
	result.r += sh.y * cR.y;
	result.r += sh.z * cR.z;
	result.r += sh.w * cR.w;

	result.g = sh.x * cG.x;
	result.g += sh.y * cG.y;
	result.g += sh.z * cG.z;
	result.g += sh.w * cG.w;

	result.b = sh.x * cB.x;
	result.b += sh.y * cB.y;
	result.b += sh.z * cB.z;
	result.b += sh.w * cB.w;

	return result.rgb;
}






struct Ray 
{
    vec3 origin;
    vec3 direction;
    vec3 inv_direction;
    ivec3 sign;
};

Ray MakeRay(vec3 origin, vec3 direction) 
{
    vec3 inv_direction = vec3(1.0) / direction;
    return Ray(
        origin,
        direction,
        inv_direction,
        ivec3((inv_direction.x < 0) ? 1 : 0,
            (inv_direction.y < 0) ? 1 : 0,
            (inv_direction.z < 0) ? 1 : 0
        )
    );
}







vec3 DoNightEyeAtNight(in vec3 color, float timeFactor)
{
	float luminance = Luminance(color);

	float rodFactor = 1.0f / (luminance * 1000.0 + 1.0);

	color = mix(color, luminance * vec3(0.2, 0.4, 0.9) * 1.5, vec3(0.6 * rodFactor * timeFactor));

	return color;
}

vec3 GetWaterFogColor()
{
	// return vec3(0.2, 0.8, 1.0);
	// return vec3(0.8, 0.9, 1.0) * 0.7;
	return vec3(0.8, 0.9, 1.0) * 0.5;
	// return vec3(1.0, 1.9, 0.4);
}

vec3 GetWaterAbsorption()
{
	// return vec3(1.0, 0.22, 0.005);
	// return vec3(1.0, 0.32, 0.05);

	// return vec3(0.25, 0.03, 0.01);// * 0.0;
	return vec3(0.25, 0.04, 0.01);// * 0.0;
}

vec3 TintUnderwaterDepth(vec3 color)
{
	// return color;
	if (isEyeInWater > 0)
	{
		float underwaterDepth = 1.0 - (eyeBrightnessSmooth.y / 240.0);
		color *= exp((-GetWaterAbsorption()) * (underwaterDepth * 8.0 + 0.0));
	}

	return color;
}

vec3 TintUnderwaterMCSkylight(vec3 color, float mcSkylight)
{
	return color;
	if (isEyeInWater > 0)
	{
		float underwaterDepth = 1.0 - mcSkylight;
		color *= exp(-GetWaterAbsorption() * underwaterDepth * 1.0);
	}

	return color;
}



void UnderwaterFog(inout vec3 color, float eyeLength, vec3 eyeDir, vec3 skyColor, vec3 sunColor)
{
	if (isEyeInWater == 0)
	return;

	vec3 fogColor = GetWaterFogColor() * 0.05;
	// fogColor *= exp(-GetWaterAbsorption() * ((1.0 - (eyeBrightnessSmooth.y / 240.0)) * 4.0 + (-eyeDir.y * 0.5 + 0.5) * 3.0));
	fogColor *= exp(-GetWaterAbsorption() * ((-eyeDir.y * 0.5 + 0.5) * 3.0));
	fogColor *= pow(curve(saturate(mix(eyeDir.y, 1.0, 0.5))), 2.0) * 0.6 + 0.4;

	// fogColor *= (skyColor * 0.1 + sunColor * 2.0);
	fogColor *= (skyColor * 1.0 + sunColor * 2.0) * 1.4;
	fogColor = TintUnderwaterDepth(fogColor);
	// fogColor *= eyeBrightnessSmooth.y / 240.0;

	float fogFactor = (exp(-eyeLength * 0.03));
	fogFactor = saturate(fogFactor * 1.1);
	fogFactor = 1.0 - pow(1.0 - fogFactor, 2.0);

	color *= exp(-GetWaterAbsorption() * eyeLength);

	// fogColor *= exp(-GetWaterAbsorption() * min(eyeLength * 1.0, 30.2));
	// fogColor *= exp(-GetWaterAbsorption() * 30.2);
	fogColor *= exp(-GetWaterAbsorption() * (1.0 - fogFactor) * 20.0);
	color = mix(fogColor, color, vec3(fogFactor));
}







vec4 GetCausticsTexComposite(vec2 coord)
{
	vec2 lookupCoord = vec2(coord.x, (coord.y - floor(mod(FRAME_TIME * 60.0f, 60.0f))) / 60.0f);
	return texture2DLod(colortex4, lookupCoord.st, 0);
}

float GetCausticsComposite(vec3 worldPos, vec3 worldLightVector, float waterDepth)
{
	vec3 causticPos = worldPos.xyz + cameraPosition.xyz;
	vec3 refractLightVector = refract(worldLightVector, vec3(0.0, 1.0, 0.0), 1.0 / 1.333);
	causticPos += refractLightVector * ((worldPos.y + cameraPosition.y) / (refractLightVector.y));

	vec4 causticsTex = GetCausticsTexComposite(mod((causticPos.xz / 2.0), vec2(1.0)));

	float depthBlend = pow(waterDepth / 2.0, 0.5);

	float caustics = pow(causticsTex.r, saturate(depthBlend * 0.5 + 0.5));
	caustics = mix(caustics, causticsTex.g, saturate(depthBlend - 1.0));
	caustics = mix(caustics, causticsTex.b, saturate(depthBlend - 2.0));
	caustics = mix(caustics, causticsTex.a, saturate(depthBlend - 3.0));

	return caustics * 10.0;
}

float GetCausticsDeferred(vec3 worldPos, vec3 worldLightVector, float waterDepth)
{
	vec3 causticPos = worldPos.xyz + cameraPosition.xyz;
	vec3 refractLightVector = refract(worldLightVector, vec3(0.0, 1.0, 0.0), 1.0 / 1.333);
	causticPos += refractLightVector * ((worldPos.y + cameraPosition.y) / (refractLightVector.y));
	// causticPos.x += 0.3;

	float caustic0 = texture2D(colortex6, mod((causticPos.xz / 2.0) * 0.25, vec2(0.249))).a;
	float caustic1 = texture2D(colortex6, mod((causticPos.xz / 2.0) * 0.25, vec2(0.249)) + vec2(0.25, 0.0)).a;
	float caustic2 = texture2D(colortex6, mod((causticPos.xz / 2.0) * 0.25, vec2(0.249)) + vec2(0.5, 0.0)).a ;
	float caustic3 = texture2D(colortex6, mod((causticPos.xz / 2.0) * 0.25, vec2(0.249)) + vec2(0.75, 0.0)).a;

	// float depthBlend = pow(waterDepth / 4.0, 1.0 / 1.5);
	float depthBlend = pow(waterDepth / 2.0, 0.5);

	// float caustics = mix(1.0, caustic0, saturate(depthBlend));

	// float caustics = mix(caustic0, caustic1, saturate(depthBlend));
	// caustics = mix(caustics, caustic2, saturate(depthBlend - 1.0));
	// caustics = mix(caustics, caustic3, saturate(depthBlend - 2.0));

	float caustics = pow(caustic0, saturate(depthBlend * 0.5 + 0.5));
	caustics = mix(caustics, caustic1, saturate(depthBlend - 1.0));
	caustics = mix(caustics, caustic2, saturate(depthBlend - 2.0));
	caustics = mix(caustics, caustic3, saturate(depthBlend - 3.0));

	return caustics * 10.0;
}







vec2 IntersectSphere( in vec3 ro, in vec3 rd, in vec3 ce, float ra )
{
    vec3 oc = ro - ce;
    float b = dot( oc, rd );
    float c = dot( oc, oc ) - ra*ra;
    float h = b*b - c;
    if( h < 0.0 ) return vec2(-1.0); // no intersection
    h = sqrt( h );
    return vec2( -b-h, -b+h );
}















// Mie
// g : ( -0.75, -0.999 )
//      3 * ( 1 - g^2 )               1 + c^2
// F = ----------------- * -------------------------------
//      2 * ( 2 + g^2 )     ( 1 + g^2 - 2 * g * c )^(3/2)
float PhaseMie( float g, float LdotV, float LdotV2 ) {
	float gg = g * g;
	
	float a = ( 1.0 - gg ) * ( 1.0 + LdotV2 );

	float b = 1.0 + gg - 2.0 * g * LdotV;
	b *= sqrt( b );
	b *= 2.0 + gg;	
	
	return 1.5 * a / b;
}

// Reyleigh
// g : 0
// F = 3/4 * ( 1 + c^2 )
float PhaseRayleigh( float LdotV2 ) 
{
	return 0.75 * ( 1.0 + LdotV2 );
}


float TotalAtmosphereDensity(float dirY)
{
	// return 1.0 / (max(0.0, dirY + 0.06) * 3.0 + 0.00001);
	return 1.0 / (SmoothMax(0.0, dirY + 0.03, 0.1) * 3.0 + 0.00001);
}

// const vec3 Rayleigh = pow(vec3(0.17, 0.44, 0.99), vec3(1.2));
const vec3 Rayleigh = pow(vec3(0.17, 0.46, 0.99), vec3(1.0));

// const vec3 Rayleigh = pow(vec3(0.17, 0.41, 0.99), vec3(1.2));

// const vec3 Rayleigh = vec3( 3.8, 13.5, 33.1 ) * 0.05;

vec3 SunlightColorFromSunVector(vec3 worldSunVector)
{

	float thickness = TotalAtmosphereDensity(worldSunVector.y);

	vec3 color = exp(-thickness * 1.0 * Rayleigh);

	color *= pow(saturate(worldSunVector.y), 0.5);

	return color;
}

vec3 Atmosphere(vec3 worldViewVector, vec3 worldSunVector, float mieAmount)
{
	// worldViewVector.y = abs(worldViewVector.y) * 0.99 + 0.01;
	worldViewVector.y = SmoothMax(0.0, worldViewVector.y, 0.1);
	// worldViewVector.y = max(0.0, worldViewVector.y);

	// return vec3(1.0);

	float LdotV = dot(worldViewVector, worldSunVector);
	float LdotV2 = LdotV * LdotV;

	// vec3 localRayleigh = pow(Rayleigh, vec3(saturate(worldViewVector.y * 0.5 + 0.5)));
	vec3 localRayleigh = pow(Rayleigh, vec3(saturate(worldViewVector.y * 0.3 + 0.7)));
	// vec3 localRayleigh = Rayleigh;


	vec3 color = vec3(0.0, 0.0, 0.0);


	// float sunThroughAtmosphereDensity = pow(saturate(dot(normalize(worldViewVector + vec3(0.0, 55.0 * worldViewVector.y, 0.0)), worldSunVector) * 0.6 + 0.4), 1.0);
	float sunThroughAtmosphereDensity = TotalAtmosphereDensity(worldSunVector.y);


	// float thickness = 1.0 / (saturate(worldViewVector.y) * 5.0 + 0.2);
	float thickness = 1.0 / ((worldViewVector.y) * (1.0 + sunThroughAtmosphereDensity * 4.0) + 0.35);
	// float rawDensity = 1.0 / ((worldViewVector.y) * 2.0 + 0.15);
	float density = pow(thickness, 1.3);
	thickness *= sunThroughAtmosphereDensity * 1.45;
	thickness *= 1.0 + (-(LdotV) * 0.5 + 0.5) * 0.025 / (exp(-sunThroughAtmosphereDensity) + 0.01);

	float phaseRayleigh = PhaseRayleigh(LdotV2);
	color = exp(-thickness * localRayleigh) * localRayleigh * density * phaseRayleigh;
	// color += pow(localRayleigh, vec3(1.0)) * 0.38 * exp(-sunThroughAtmosphereDensity * 0.3) * (1.0 - exp(-rawDensity * 0.45));
	// color += localRayleigh * 0.7768 * exp(-sunThroughAtmosphereDensity * 0.3 ) * (1.0 - exp(-rawDensity * 0.45))
			 // * exp(-sunThroughAtmosphereDensity * localRayleigh * 0.35);

	// color += localRayleigh * 0.4768 * exp(-sunThroughAtmosphereDensity * 0.3 )
	// 	 * exp(-sunThroughAtmosphereDensity * localRayleigh * 0.35);	 

	color += localRayleigh * 0.268 * exp(-TotalAtmosphereDensity(worldSunVector.y + 0.02) * 0.3 );
		 // * exp(-sunThroughAtmosphereDensity * localRayleigh * 0.35);	


	float phaseMie = PhaseMie(0.4, LdotV, LdotV2);
	float phaseMie2 = PhaseMie(0.78, LdotV, LdotV2);
	color += phaseMie * 0.185 
	// * exp(-localRayleigh * TotalAtmosphereDensity(worldViewVector.y) * 0.15)
	// * exp(-localRayleigh * sunThroughAtmosphereDensity * 0.15)
	* exp(-thickness * localRayleigh)
	* density
	* mieAmount;

	color += phaseMie2 * 0.136 
	* exp(-localRayleigh * TotalAtmosphereDensity(worldViewVector.y) * sunThroughAtmosphereDensity * 0.3) 
	* density
	* mieAmount;




	color *= 3.0 / SUNLIGHT_BRIGHTNESS;


	// color = vec3(0.1, 0.3, 1.0);

	// color *= SunlightColorFromSunVector(worldSunVector);

	return color;
}


vec3 AtmosphereAbsorption(vec3 worldViewVector)
{
	worldViewVector.y = max(worldViewVector.y, 0.0);

	float thickness = 1.0 / ((worldViewVector.y) * (1.0) + 0.15);
	float thicknessHaze = 1.0 / ((worldViewVector.y) * (10.0));

	return exp(-thickness * (Rayleigh) * 0.7) * exp(-thicknessHaze);
}


// Main sky shading function
vec3 SkyShading(vec3 worldViewVector, vec3 worldSunVector, float rainStrength)
{
	vec3 atmosphere = Atmosphere(worldViewVector, worldSunVector, 1.0);
	atmosphere += Atmosphere(worldViewVector, -worldSunVector, 1.0) * NIGHT_BRIGHTNESS;

	// rainy
	vec3 rainSky = vec3(0.5) * (exp(-TotalAtmosphereDensity(worldSunVector.y)) + 1.0 * NIGHT_BRIGHTNESS);
	atmosphere = mix(atmosphere, rainSky, vec3(rainStrength));

	// return vec3(1.0);
	return atmosphere;
}

vec3 AtmosphericScattering(vec3 rayDir, vec3 lightVector, float mieAmount)
{
	return Atmosphere(rayDir, lightVector, mieAmount);
}

vec3 AtmosphericScattering(vec3 rayDir, vec3 lightVector, float mieAmount, float depth)
{
	float LdotV = dot(lightVector, rayDir);
	float LdotV2 = LdotV * LdotV;

	rayDir.y = rayDir.y * 0.5 + 0.5;
	rayDir = normalize(rayDir);

	// vec3 atmosphere = Atmosphere(rayDir, lightVector, 0.0);
	vec3 atmosphere = Rayleigh * 1.268 * exp(-TotalAtmosphereDensity(lightVector.y + 0.02) * 0.4 );
	atmosphere += PhaseMie(0.78, LdotV, LdotV2) * SunlightColorFromSunVector(lightVector) * 1.0;

	atmosphere *= 1.0 - exp(-depth * 500.0);

	return atmosphere;
	// return vec3(0.0);
}

vec3 AtmosphericScatteringSingle(vec3 rayDir, vec3 lightVector, float mieAmount)
{
	return AtmosphereAbsorption(rayDir);
}

vec3 ModulateSkyForRain(vec3 skyColor, vec3 colorSkylight, float rainStrength)
{
	skyColor = mix(skyColor, vec3(1.0) * Luminance(colorSkylight), vec3(rainStrength * 0.95));

	return skyColor;
}

float RenderSunDisc(vec3 worldDir, vec3 sunDir)
{
	float d = dot(worldDir, sunDir);

	float disc = 0.0;

	//if (d > 0.99)
	//	disc = 1.0;

	float size = 0.00195;
	float hardness = 1000.0;

	disc = pow(curve(saturate((d - (1.0 - size)) * hardness)), 2.0);

	float visibility = curve(saturate(worldDir.y * 30.0));

	disc *= visibility;

	return disc;
}




















void LandAtmosphericScattering(inout vec3 color, in vec3 viewPos, in vec3 viewDir, 
	vec3 worldDir, vec3 worldSunVector, float rayFactor)
{
	float dist = length(viewPos);

	#ifdef FADE_TO_ATMOSPHERE
		float depthFactor = length(viewPos) / far;
		depthFactor = pow(depthFactor, 6.0) * 0.07 + pow(depthFactor, 2.0) * 0.003;
	#else
		float depthFactor = length(viewPos) / 300.0;
		depthFactor = pow(depthFactor, 3.0) * 0.002;
	#endif

		depthFactor *= pow(eyeBrightnessSmooth.y / 240.0f, 6.0f);


	depthFactor = clamp(depthFactor, 0.0, 0.03);
	depthFactor /= saturate(worldDir.y) * 2.0 + 1.0;

	vec3 absorption = vec3(0.2, 0.45, 1.0);

	color *= exp(-depthFactor * absorption * 100.67);
	// color += max(vec3(0.0), vec3(1.0) - exp(-fogFactor * absorption)) * mix(colorSunlight, vec3(dot(colorSunlight, vec3(0.33333))), vec3(0.9)) * 7.0;

	// float VdotL = dot(viewDir, sunVector);

	// float g = 0.77;
	// 			//float g = 0.9;
	// float g2 = g * g;
	// float theta = VdotL * 0.5 + 0.5;
	// float anisoFactor = 1.5 * ((1.0 - g2) / (2.0 + g2)) * ((1.0 + theta * theta) / (1.0 + g2 - 2.0 * g * theta)) + g * theta;

	

	// color += colorSunlight * fogFactor * 0.2 * anisoFactor;

	// float depthFactor = length(viewPos) / far;
	// depthFactor = pow(depthFactor, 2.0) * 0.04;

	// depthFactor = min(depthFactor, 0.04);

	color += AtmosphericScattering(normalize(worldDir), worldSunVector, 1.0, depthFactor) * 0.9 * mix(rayFactor, 1.0, 0.5) * mix(1.0, 0.35, wetness) * 0.1 * (1.0 - rainStrength);


}



















vec3 KelvinToRGB(float k)
{
	// float t = k / 100.0;

	// vec3 color = vec3(0.0);

	// if (t <= 66.0)
	// {
	// 	color.r = 1.0;
	// }
	// else
	// {
	// 	color.r = saturate((pow(t - 60.0, -0.133204) * 329.698) / 255.0);
	// }

	// if (t <= 66.0)
	// {
	// 	color.g = saturate((log(t) * 99.4708 - 161.119) / 255.0);
	// }
	// else
	// {
	// 	color.g = saturate((pow(t - 60.0, -0.075514) * 288.1221) / 255.0);
	// }

	// if (t >= 66.0)
	// {
	// 	color.b = 1.0;
	// }
	// else
	// {
	// 	color.b = saturate((log(t - 10.0) * 138.51773 - 305.044) / 255.0);
	// }

	// return color;

	const vec3 c = pow(vec3(0.1, 0.4, 0.98), vec3(0.9));

	float x = k - 6500.0;
	float xc = pow(abs(x), 1.1) * sign(x);

	return (exp(xc * 0.00045 * c) / exp(xc * 0.00017)) * 0.5;
}







// Lighting colors and data
vec3 GetColorSunlight(vec3 worldSunVector, float rainStrength)
{
	vec3 color = SunlightColorFromSunVector(worldSunVector);

	color *= 1.0 - rainStrength;

	color += SunlightColorFromSunVector(-worldSunVector) * NIGHT_BRIGHTNESS;

	// return vec3(1.0);
	return color;
}


vec3 GetColorTorchlight()
{
	// vec3 colorTorchlight = vec3(0.0);

	// if (TORCHLIGHT_COLOR_TEMPERATURE == 2000)
	// 	//2000k
	// 	colorTorchlight = pow(vec3(255, 141, 11) / 255.0, vec3(2.2));
	// else if (TORCHLIGHT_COLOR_TEMPERATURE == 2300)
	// 	//2300k
	// 	colorTorchlight = pow(vec3(255, 152, 54) / 255.0, vec3(2.2));
	// else if (TORCHLIGHT_COLOR_TEMPERATURE == 2500)
	// 	//2500k
	// 	colorTorchlight = pow(vec3(255, 166, 69) / 255.0, vec3(2.2));
	// else
	// 	//3000k
	// 	colorTorchlight = pow(vec3(255, 180, 107) / 255.0, vec3(2.2));

	// return colorTorchlight;

	return KelvinToRGB(float(TORCHLIGHT_COLOR_TEMPERATURE));
	// return KelvinToRGB((sin(FRAME_TIME) * 0.5 + 0.5) * 10000 + 1000);
}

void GetSkylightData(vec3 worldSunVector, float rainStrength,
	out vec4 skySHR, out vec4 skySHG, out vec4 skySHB, 
	out vec3 colorSkylight, out vec3 colorSkyUp)
{
	colorSkylight = vec3(0.0);

	const int latSamples = 5;
	const int lonSamples = 5;

	skySHR = vec4(0.0);
	skySHG = vec4(0.0);
	skySHB = vec4(0.0);

	for (int i = 0; i < latSamples; i++)
	{
		float latitude = (float(i) / float(latSamples)) * 3.14159265;
			  latitude = latitude;
		for (int j = 0; j < lonSamples; j++)
		{
			float longitude = (float(j) / float(lonSamples)) * 3.14159265 * 2.0;

			vec3 rayDir;
			rayDir.x = cos(latitude) * cos(longitude);
			rayDir.z = cos(latitude) * sin(longitude);
			rayDir.y = sin(latitude);

			vec3 skyCol = SkyShading(rayDir, worldSunVector, rainStrength);
			colorSkylight += skyCol;

			skySHR += ToSH(skyCol.r, rayDir);
			skySHG += ToSH(skyCol.g, rayDir);
			skySHB += ToSH(skyCol.b, rayDir);
		}
	}

	skySHR /= latSamples * lonSamples;
	skySHG /= latSamples * lonSamples;
	skySHB /= latSamples * lonSamples;

	colorSkylight /= latSamples * lonSamples;
	colorSkyUp = SkyShading(vec3(0.0, 1.0, 0.0), worldSunVector, rainStrength);
}





// Time data
float GetTimeMidnight(vec3 worldSunVector)
{
	return saturate(-worldSunVector.y * 10.0);
}
