// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#include "./Flax/Common.hlsl"
#include "./Flax/Atmosphere.hlsl"

// Provides functions for atmospheric scattering and aerial perspective.
//
// Explanations:
// Scale Height = the altitude (height above ground) at which the average
//                atmospheric density is found.
// Optical Depth = also called optical length, airmass, etc.
//
// References:
// [GPUGems2] GPU Gems 2: Accurate Atmospheric Scattering by Sean O'Neil.
// [GPUPro3]  An Approximation to the Chapman Grazing-Incidence Function for
//            Atmospheric Scattering, GPU Pro3, pp. 105.
// Papers bei Bruneton, Nishita, etc.
//
// This code contains embedded portions of free sample source code from 
// http://www-evasion.imag.fr/Membres/Eric.Bruneton/PrecomputedAtmosphericScattering2.zip, Author: Eric Bruneton, 
// 08/16/2011, Copyright (c) 2008 INRIA, All Rights Reserved, which have been altered from their original version.

const static int TransmittanceIntegralSamples = 500;
const static int InscatterIntegralSamples = 50;
const static int IrradianceIntegralSamples = 32;
const static int IrradianceIntegralSamplesHalf = IrradianceIntegralSamples / 2;
const static int InscatterSphericalIntegralSamples = 16;
const static float IrradianceDeltaPhi = PI / float(IrradianceIntegralSamples);
const static float IrradianceDeltaTheta = PI / float(IrradianceIntegralSamples);
const static float InscatterDeltaPhi = PI / float(InscatterSphericalIntegralSamples);
const static float InscatterDeltaTheta = PI / float(InscatterSphericalIntegralSamples);

META_CB_BEGIN(0, Data)
float First;
float AtmosphereR;
int AtmosphereLayer;
float Dummy0;
float4 dhdh;
META_CB_END

Texture2D AtmosphereDeltaETexture  : register(t3);
Texture3D AtmosphereDeltaSRTexture : register(t4);
Texture3D AtmosphereDeltaSMTexture : register(t5);
Texture3D AtmosphereDeltaJTexture  : register(t6);

float GetOpticalDepth(float h, float radius, float mu) 
{
    float result = 0.0f;
    float ti = Limit(radius, mu) / float(TransmittanceIntegralSamples);
    float xi = 0.0f;
    float yi = exp(-(radius - RadiusGround) / h);
	LOOP
    for (int i = 1; i <= TransmittanceIntegralSamples; i++) 
	{
        float xj = float(i) * ti;
        float yj = exp(-(sqrt(radius * radius + xj * xj + 2.0f * xj * radius * mu) - RadiusGround) / h);
        result += (yi + yj) * 0.5f * ti;
        xi = xj;
        yi = yj;
    }
    return mu < -sqrt(1.0f - (RadiusGround / radius) * (RadiusGround / radius)) ? 1e9 : result;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Transmittance(Quad_VS2PS input) : SV_Target0
{
	float radius, mus;
	GetTransmittanceRMuS(input.TexCoord, radius, mus);
	float3 depth = BetaRayleighScattering * GetOpticalDepth(HeightScaleRayleigh, radius, mus) + BetaMieExtinction * GetOpticalDepth(HeightScaleMie, radius, mus);
	return float4(exp(-depth), 0.0f);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Irradiance1(Quad_VS2PS input) : SV_Target0
{
	float radius, mus;
	GetIrradianceRMuS(input.TexCoord, radius, mus);
	return float4(Transmittance(radius, mus) * max(mus, 0.0f), 0.0f);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_IrradianceN(Quad_VS2PS input) : SV_Target0
{
	float radius, mus;
	GetIrradianceRMuS(input.TexCoord, radius, mus);
	float3 s = float3(sqrt(max(1.0f - mus * mus, 0.0f)), 0.0f, mus);
	float3 result = float3(0, 0, 0);
	for (int iPhi = 0; iPhi < 4 * IrradianceIntegralSamplesHalf; iPhi++) 
	{
		float phi = (float(iPhi) + 0.5f) * IrradianceDeltaPhi;
		for (int iTheta = 0; iTheta < IrradianceIntegralSamplesHalf; iTheta++) 
		{
			float theta = (float(iTheta) + 0.5f) * IrradianceDeltaTheta;
			float dw = IrradianceDeltaTheta * IrradianceDeltaPhi * sin(theta);
			float3 w = float3(cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta));
			float nu = dot(s, w);

			if (First == 1.0f)
			{
				float pr1 = PhaseFunctionR(nu);
				float pm1 = PhaseFunctionM(nu);
				float3 ray1 = Texture4DSample(AtmosphereDeltaSRTexture, radius, w.z, mus, nu).xyz;
				float3 mie1 = Texture4DSample(AtmosphereDeltaSMTexture, radius, w.z, mus, nu).xyz;
				result += (ray1 * pr1 + mie1 * pm1) * w.z * dw;
			}
			else 
			{
				result += Texture4DSample(AtmosphereDeltaSRTexture, radius, w.z, mus, nu).xyz * w.z * dw;
			}
		}
	}
	return float4(result, 0.0);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_CopyIrradiance1(Quad_VS2PS input) : SV_Target0
{
	return AtmosphereDeltaETexture.Sample(SamplerLinearClamp, input.TexCoord);
}

void Integrand(float radius, float mu, float mus, float nu, float t, out float3 ray, out float3 mie) 
{
    ray = float3(0, 0, 0);
    mie = float3(0, 0, 0);
    float ri = sqrt(radius * radius + t * t + 2.0f * radius * mu * t);
    float musi = (nu * t + mus * radius) / ri;
	ri = max(RadiusGround, ri);
	if (musi >= -sqrt(1.0 - RadiusGround * RadiusGround / (ri * ri)) ) 
	{
		float3 ti = TransmittanceWithDistance(radius, mu, t) * Transmittance(ri, musi);
		ray = exp(-(ri - RadiusGround) / HeightScaleRayleigh) * ti;
		mie = exp(-(ri - RadiusGround) / HeightScaleMie) * ti;
	}
}

void Inscatter(float radius, float mu, float mus, float nu, out float3 ray, out float3 mie)
{
    ray = float3(0, 0, 0);
    mie = float3(0, 0, 0);
    float dx = Limit(radius, mu) / float(InscatterIntegralSamples);
    float xi = 0.0f;
    float3 rayi;
    float3 miei;
    Integrand(radius, mu, mus, nu, 0.0f, rayi, miei);
    for (int i = 1; i <= InscatterIntegralSamples; i++) 
	{
        float xj = float(i) * dx;
        float3 Rayj;
        float3 Miej;
        Integrand(radius, mu, mus, nu, xj, Rayj, Miej);
        ray += (rayi + Rayj) * 0.5f * dx;
        mie += (miei + Miej) * 0.5f * dx;
        xi = xj;
        rayi = Rayj;
        miei = Miej;
    }
    ray *= BetaRayleighScattering;
    mie *= BetaMieScattering;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Inscatter1_A(Quad_VS2PS input) : SV_Target
{
    float3 ray;
    float3 mie;
    float mu, mus, nu;
    GetMuMuSNu(input.TexCoord, AtmosphereR, dhdh, mu, mus, nu);
    Inscatter(AtmosphereR, mu, mus, nu, ray, mie);
    return float4(ray, 1);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_CopyInscatter1(Quad_VS2PS input) : SV_Target0
{
	float3 uvw = float3(input.TexCoord, (float(AtmosphereLayer) + 0.5f) / float(AtmosphericFogInscatterAltitudeSampleNum));
    float4 ray = AtmosphereDeltaSRTexture.Sample(SamplerLinearClamp, uvw);
    float4 mie = AtmosphereDeltaSMTexture.Sample(SamplerLinearClamp, uvw);
	return float4(ray.xyz, mie.x);
}

void Inscatter(float radius, float mu, float mus, float nu, out float3 rayMie) 
{
	radius = clamp(radius, RadiusGround, RadiusAtmosphere);
	mu = clamp(mu, -1.0f, 1.0f);
	mus = clamp(mus, -1.0f, 1.0f);
	float variation = sqrt(1.0f - mu * mu) * sqrt(1.0f - mus * mus);
	nu = clamp(nu, mus * mu - variation, mus * mu + variation);
	float cThetaMin = -sqrt(1.0f - (RadiusGround / radius) * (RadiusGround / radius));
	float3 v = float3(sqrt(1.0f - mu * mu), 0.0f, mu);
	float sx = v.x == 0.0f ? 0.0f : (nu - mus * mu) / v.x;
	float3 s = float3(sx, sqrt(max(0.0f, 1.0f - sx * sx - mus * mus)), mus);
	rayMie = float3(0, 0, 0);

	for (int iTheta = 0; iTheta < InscatterSphericalIntegralSamples; iTheta++)
	{
		float theta = (float(iTheta) + 0.5f) * InscatterDeltaTheta;
		float cTheta = cos(theta);

		float ground = 0.0f;
		float3 transmittance = float3(0, 0, 0);
		float reflectance = 0.0f;
		if (cTheta < cThetaMin)
		{
			ground = -radius * cTheta - sqrt(radius * radius * (cTheta * cTheta - 1.0f) + RadiusGround * RadiusGround);
			transmittance = TransmittanceWithDistance(RadiusGround, -(radius * cTheta + ground) / RadiusGround, ground);
			reflectance = AverageGroundRelectance / PI;
		}
		
		for (int iPhi = 0; iPhi < 2 * InscatterSphericalIntegralSamples; iPhi++)
		{
			float phi = (float(iPhi) + 0.5) * InscatterDeltaPhi;
			float dw = InscatterDeltaTheta * InscatterDeltaPhi * sin(theta);
			float3 w = float3(cos(phi) * sin(theta), sin(phi) * sin(theta), cTheta);
			float nu1 = dot(s, w);
			float nu2 = dot(v, w);
			float pr2 = PhaseFunctionR(nu2);
			float pm2 = PhaseFunctionM(nu2);
			float3 normal = (float3(0.0f, 0.0f, radius) + ground * w) / RadiusGround;
			float3 irradiance = Irradiance(AtmosphereDeltaETexture, RadiusGround, dot(normal, s));
			float3 rayMie1 = reflectance * irradiance * transmittance;

			if (First == 1.0f)
			{
				float pr1 = PhaseFunctionR(nu1);
				float pm1 = PhaseFunctionM(nu1);
				float3 ray1 = Texture4DSample(AtmosphereDeltaSRTexture, radius, w.z, mus, nu1).xyz;
				float3 mie1 = Texture4DSample(AtmosphereDeltaSMTexture, radius, w.z, mus, nu1).xyz;
				rayMie1 += ray1 * pr1 + mie1 * pm1;
			}
			else
			{
				rayMie1 += Texture4DSample(AtmosphereDeltaSRTexture, radius, w.z, mus, nu1).xyz;
			}

			rayMie += rayMie1 * (BetaRayleighScattering * exp(-(radius - RadiusGround) / HeightScaleRayleigh) * pr2 + BetaMieScattering * exp(-(radius - RadiusGround) / HeightScaleMie) * pm2) * dw;
		}
	}
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_InscatterS(Quad_VS2PS input) : SV_Target0
{
	float3 rayMie;
	float mu, mus, nu;
	GetMuMuSNu(input.TexCoord, AtmosphereR, dhdh, mu, mus, nu);
	Inscatter(AtmosphereR, mu, mus, nu, rayMie);
	return float4(rayMie, 0);
}

float3 Integrand(float radius, float mu, float mus, float nu, float t) 
{
    float ri = sqrt(radius * radius + t * t + 2.0 * radius * mu * t);
    float mui = (radius * mu + t) / ri;
    float musi = (nu * t + mus * radius) / ri;
    return Texture4DSample(AtmosphereDeltaJTexture, ri, mui, musi, nu).xyz * TransmittanceWithDistance(radius, mu, t);
}

float3 Inscatter(float radius, float mu, float mus, float nu) 
{
	float3 rayMie = float3(0, 0, 0);
	float dx = Limit(radius, mu) / float(InscatterIntegralSamples);
	float xi = 0.0f;
	float3 raymiei = Integrand(radius, mu, mus, nu, 0.0f);
	for (int i = 1; i <= InscatterIntegralSamples; i++) 
	{
		float xj = float(i) * dx;
		float3 RayMiej = Integrand(radius, mu, mus, nu, xj);
		rayMie += (raymiei + RayMiej) * 0.5f * dx;
		xi = xj;
		raymiei = RayMiej;
	}
	return rayMie;
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_InscatterN(Quad_VS2PS input) : SV_Target0
{
	float mu, mus, nu;
	GetMuMuSNu(input.TexCoord, AtmosphereR, dhdh, mu, mus, nu);
	return float4(Inscatter(AtmosphereR, mu, mus, nu), 0);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_CopyInscatterN(Quad_VS2PS input) : SV_Target0
{
    float mu, mus, nu;
    GetMuMuSNu(input.TexCoord, AtmosphereR, dhdh, mu, mus, nu);
	float3 uvw = float3(input.TexCoord, (float(AtmosphereLayer) + 0.5f) / float(AtmosphericFogInscatterAltitudeSampleNum));
	float4 ray = AtmosphereDeltaSRTexture.Sample(SamplerLinearClamp, uvw) / PhaseFunctionR(nu);
	return float4(ray.xyz, 0);
}

META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Inscatter1_B(Quad_VS2PS input) : SV_Target
{
    float3 ray;
    float3 mie;
    float mu, mus, nu;
    GetMuMuSNu(input.TexCoord, AtmosphereR, dhdh, mu, mus, nu);
    Inscatter(AtmosphereR, mu, mus, nu, ray, mie);
    return float4(mie, 1);
}
