#ifndef VOLUMETRIC_FOG_INC
#define VOLUMETRIC_FOG_INC
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" 
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl"
#include "/Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
//#include "..//Char/HLSLToon_Common.hlsl"


TEXTURE3D(_volumetric_fog_noiseTexture);		SAMPLER(sampler_volumetric_fog_noiseTexture);
TEXTURE2D(_volumetric_fog_densityTexture);		SAMPLER(sampler_volumetric_fog_densityTexture);
TEXTURE2D(_volumetric_fog_lightingTexture);		SAMPLER(sampler_volumetric_fog_lightingTexture);
TEXTURE2D(_volumetric_fog_IntegratedTexture);		SAMPLER(sampler_volumetric_fog_IntegratedTexture);

static const half _volumetric_fog_texture3d_width = 32;;
static const half _volumetric_fog_texture3d_height = 32;;
static const half _volumetric_fog_texture3d_depth = 16;;
static const float _volumetric_fog_texture3d_inv_width = 1.0 / _volumetric_fog_texture3d_width;;
static const float _volumetric_fog_texture3d_inv_height = 1.0 / _volumetric_fog_texture3d_height;;
static const half _volumetric_fog_texture3d_inv_depth = 1.0 / _volumetric_fog_texture3d_depth;
;
static const float _volumetric_fog_texture2d_width = _volumetric_fog_texture3d_width * _volumetric_fog_texture3d_height;;
static const half _volumetric_fog_texture2d_height = _volumetric_fog_texture3d_depth;;
static const float2 _volumetric_fog_texture2d_next_depth_uv_offset = float2(0, 1.0 / _volumetric_fog_texture2d_height);;

static const half _volumetric_fog_inv_depth_max_index = 1.0 / _volumetric_fog_texture3d_depth; //< Depth 
static const half _volumetric_fog_depth_range = 128.0;	//< Depth range R
static const half _volumetric_fog_curve_power = 2.0; 
static const half _volumetric_fog_inv_curve_power = 1.0 / _volumetric_fog_curve_power; //< Inv . power ( 1 . 0 / C)

CBUFFER_START(UnityPerMaterial)
	half _scatteringCoef;
	half _absorptionCoef;
	half _phaseAnisotropy;
	half _density_attenuates;
	half _density_distance_attenuates;

	uniform half _ShadowFalloff;

	half4 _density_color;

	half3 _density_uv_scale;
	half3 _density_move_direction;
	

	float4 _cameraWorldPos;
	float3 _cameraWorldForwardDir;

	float4x4 _clipToWorld;
	float4x4 _density_worldToLocal;

CBUFFER_END



struct VSIn {
	float4 vertex	: POSITION;
	float2 uv		: TEXCOORD0;
};

struct VSOut {
	float4 vertex	: SV_POSITION;
	float2 uv		: TEXCOORD0;
};

VSOut MainVS(in VSIn i)
{
	VSOut Out = (VSOut)0;
	Out.vertex = float4(i.vertex.xyz, 1.0);
#if UNITY_UV_STARTS_AT_TOP
	Out.uv = i.uv;
#else
	Out.uv = float2(i.uv.x, 1.0 - i.uv.y);
#endif
	return Out;
}


//------------------------------------------------------------------------------------------


float GetPhaseFunction(float3 viewDirection, float3 sunDirection, float g)
{
	float cosAngle = dot(viewDirection, sunDirection);
	float gMulg = g * g;
	float a = 1.0 - gMulg;
	float b = 4.0 * 3.1415926 * pow((1.0 + gMulg - 2.0 * g * cosAngle), 3.0 / 2.0);
	return a / b;
}

float SliceIndexToNormalize(float index)
{
	float a = pow(index, _volumetric_fog_curve_power);
	return a;
}

float SliceIndexToRange(float index)
{
	float a = SliceIndexToNormalize(index);
	float b = a * _volumetric_fog_depth_range;
	return b;
}

float3 CalcWorldPositionFromCoords(float2 uv)
{
	//  Z = 0 plane
	float2 posInCcameraZeroPlane = 0;
	// because
	//	float x = fmod(uv.x, _volumetric_fog_texture3d_width / (_volumetric_fog_texture3d_width * _volumetric_fog_texture3d_height));
	//	float x = fmod(uv.x, 1.0 / _volumetric_fog_texture3d_height);
	// so
	posInCcameraZeroPlane.x = fmod(uv.x, _volumetric_fog_texture3d_inv_height);
	posInCcameraZeroPlane.y = uv.x - posInCcameraZeroPlane.x;
	posInCcameraZeroPlane.x *= _volumetric_fog_texture3d_height;
	posInCcameraZeroPlane.xy *= 2.0;
	posInCcameraZeroPlane.xy -= float2(1, 1);

	
	float3 vpos = float3(posInCcameraZeroPlane.xy, 0);

	//// test only
	////float4 vp = float4(0.4268848, 0, 0, 1);
	////vpos = vp.xyz;
	//// end

	
	float4 worldPos = mul(_clipToWorld, float4(vpos, 1));
	float3 vViewDir = worldPos.xyz - _cameraWorldPos.xyz;
	vViewDir = normalize(vViewDir);
	float distance = SliceIndexToRange(uv.y);
	float c = dot(vViewDir, _cameraWorldForwardDir);
	distance /= c;
	worldPos.xyz = vViewDir * distance + _cameraWorldPos.xyz;

	//// test only
	////worldPos.xyz = vViewDir * 31.62277 + _cameraWorldPos.xyz;
	//// end


	return worldPos.xyz;

}

half ComputeLayerThickness(half index)
{
	half2 i = half2(index, index + _volumetric_fog_inv_depth_max_index);
	half2 d = pow(i, _volumetric_fog_curve_power);
	half b = (d.y - d.x) * _volumetric_fog_depth_range;
	// TODO : how to cal 
	return  b * 0.2;
}

half4 CalculateDensityFunction(float2 uv)
{
	half4 d0 = SAMPLE_TEXTURE2D(_volumetric_fog_densityTexture, sampler_volumetric_fog_densityTexture, uv);
	return d0;
}

float CalculateDensityFunctionFromWorldPos(float3 worldPos)
{
	float distance = length(worldPos.xyz - _cameraWorldPos.xyz);
	float3 uv = worldPos / (2.0 * distance);
	uv.x += _Time.y * 0.1;
	uv.z += _Time.y * 0.1;

	half4 density = SAMPLE_TEXTURE3D(_volumetric_fog_noiseTexture, sampler_volumetric_fog_noiseTexture, uv);

	return density.a * _density_attenuates;
}


half3 GetSunLightingRadiance(Light light, float absorption)
{
	//half4 density = CalculateDensityFunction(uv);
	//half absorb = exp(-density.a);
	//return  _MainLightColor.rgb * absorb;

	//return light.color * absorb;

	return light.color * exp(-absorption);

}

half3 GetLocalLightRadiance(Light light)
{
	half3 lightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
	return lightColor;
}

half3 GetAmbientConvolvedWithPhaseFunction(float3 worldPos, float3 viewDirection, float g)
{
	return UNITY_LIGHTMODEL_AMBIENT.xyz;
}

half3 GetShadow(float3 worldPos, Light light)
{
	//float4 posW = VertCalaShadow(float4(worldPos.xyz, 1.0), _DirX, _DirY);
	//light.shadowAttenuation = FragCalaShadowAtten(light.shadowAttenuation, posW, _Time.x, _Speed, _IsRotate, _ShadowAlphaRatio, _ShadowTex);
	//half3 shadowColor = lerp(_env_shadow_color.rgb, half3(1, 1, 1), _ShadowFalloff);
	//half3 shadowFactored = lerp(shadowColor.rgb, half3(1, 1, 1), light.shadowAttenuation);
	//return shadowFactored;
    return 1;
}

Light GetSun(float3 worldPosition)
{
#ifdef _MAIN_LIGHT_SHADOWS
	#if defined(MAIN_LIGHT_CALCULATE_SHADOWS)
		float4 shadowCoord = TransformWorldToShadowCoord(float4(worldPosition.xyz, 1.0));
	#else
		float4 shadowCoord = float4(0, 0, 0, 0);
	#endif
		Light light = GetMainLight(shadowCoord);
	#else
	Light light = GetMainLight();
#endif
	return light;
}

void PS_Lighting(VSOut IN, out float4 OutColor : SV_Target0)
{
	// World - space position of volumetric texture texel
	float3 worldPosition = CalcWorldPositionFromCoords(IN.uv.xy);
	// Thickness of slice -- non - constant due to exponential slice
	// distribution
	float layerThickness = ComputeLayerThickness(IN.uv.y);
	// Estimated density of participating medium at given point
	half4 dustDensity = CalculateDensityFunction(IN.uv.xy);
	//float dustDensity = CalculateDensityFunctionFromWorldPos(worldPosition);

	// Scattering coefficient
	float scattering = _scatteringCoef * dustDensity.a * layerThickness;
	// Absorption coefficient
	float absorption = _absorptionCoef * dustDensity.a * layerThickness;
	
	// Normalized view direction
	float3 viewDirection = normalize(worldPosition - _cameraWorldPos.xyz);

	Light light = GetSun(worldPosition);

	half3 lighting = 0;
	// Lighting section BEGIN
	// Adding all contributing lights radiance and multiplying it by
	// a phase function -- volumetric fog equivalent of BRDFs
	lighting += GetSunLightingRadiance(light, absorption)
		* GetPhaseFunction(viewDirection, _MainLightPosition.xyz, _phaseAnisotropy);

	// Ambient
	lighting += GetAmbientConvolvedWithPhaseFunction(worldPosition, viewDirection, _phaseAnisotropy);

#ifdef _ADDITIONAL_LIGHTS
	uint pixelLightCount = GetAdditionalLightsCount();
	for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
	{
		Light localLight = GetAdditionalLight(lightIndex, worldPosition);
		lighting += GetLocalLightRadiance(localLight)
			* GetPhaseFunction(viewDirection, localLight.direction,
				_phaseAnisotropy);
	}
#endif

	// How to add point light???
	//// test only
	//{
	//	float3 testLightPos = float3(-47.60426, 90.79, -313.4499);
	//	float3 testLightColor = float3(1, 0, 0);
	//	float3 testLightDir = worldPosition - testLightPos;
	//	float testLightAtten = exp(-length(testLightDir)) * 100000;
	//	lighting += (testLightColor * testLightAtten * GetPhaseFunction(viewDirection, normalize(testLightDir),
	//		_phaseAnisotropy));
	//}
	//// test end

	// How to add shadow???
	//lighting *= GetShadow(worldPosition, light);

	// Lighting section END
	// Finally , we apply some potentially non - white fog scattering albedo
	lighting *= dustDensity.rgb;

	// Final in - scattering is product of outgoing radiance and scattering
	// coefficients , while extinction is sum of scattering and absorption
	OutColor = float4 (lighting * scattering, scattering + absorption);
}


//------------------------------------------------------------------------------------------

half CalculateDensityFromNoiseTexture(float3 localPosition)
{
	half d = length(localPosition - float3(0.5, 0.5, 0.5));
	d /= 1.4422;
	d = clamp(1.0 - d, 0, 1);
	d *= d;
	d *= _density_distance_attenuates;
	d = clamp(d, 0, 1);

	float3 uv = localPosition *  _density_uv_scale;
	uv -= (_density_move_direction * _Time.y);
	half4 density = SAMPLE_TEXTURE3D(_volumetric_fog_noiseTexture, sampler_volumetric_fog_noiseTexture, uv);

	return density.a * _density_attenuates * d;
}


void PS_Density(VSOut IN, out float4 OutColor : SV_Target0)
{
	OutColor = 0;
	float3 worldPosition = CalcWorldPositionFromCoords(IN.uv.xy);
	float3 localPosition = mul(_density_worldToLocal, float4(worldPosition.xyz, 1.0));
	localPosition += float3(0.5, 0.5, 0.5);


	if (localPosition.x >= 0 && localPosition.x <= 1 &&
		localPosition.y >= 0 && localPosition.y <= 1 &&
		localPosition.z >= 0 && localPosition.z <= 1)
	{
		half density = CalculateDensityFromNoiseTexture(localPosition);
		OutColor = half4(_density_color.rgb, density);
	}
	else
	{
		//float3 worldPosition2 = CalcWorldPositionFromCoords(IN.uv.xy + float2(0, _volumetric_fog_texture3d_inv_depth));
		//float3 vDir = worldPosition2 - worldPosition;
		//float len = length(vDir);
		//vDir /= len;
		//int bFind = 0;

		//for (int i = 1; i < 32; i++)
		//{
		//	float3 localPosition2 = mul(_density_worldToLocal, float4(worldPosition.xyz + vDir * i * len / 32.0, 1.0));
		//	localPosition2 += float3(0.5, 0.5, 0.5);

		//	
		//	if (localPosition2.x >= 0 && localPosition2.x <= 1 &&
		//		localPosition2.y >= 0 && localPosition2.y <= 1 &&
		//		localPosition2.z >= 0 && localPosition2.z <= 1)
		//	{
		//		half density2 = CalculateDensityFromNoiseTexture(localPosition2);
		//		OutColor = half4(_density_color.rgb, density2);
		//		bFind = 1;
		//		break;
		//	}
		//}

		//if(bFind == 0)
			discard;
	}
	
}

//------------------------------------------------------------------------------------------

// One step of numerical solution to the light
// scattering equation
float4 AccumulateScattering(in float4 colorAndDensityFront,
	in float4 colorAndDensityBack)
{
	// rgb = in - scattered light accumulated so far ,
	// a = accumulated scattering coefficient
	float3 light = colorAndDensityFront.rgb + saturate(
		exp(-colorAndDensityFront.a)) * colorAndDensityBack.rgb;
	return float4 (light.rgb, colorAndDensityFront.a +
		colorAndDensityBack.a);
}

half4 GetSliceValue(float2 uv)
{
	half4 a = SAMPLE_TEXTURE2D(_volumetric_fog_lightingTexture, sampler_volumetric_fog_lightingTexture, uv);
	half4 b = SAMPLE_TEXTURE2D(_volumetric_fog_lightingTexture, sampler_volumetric_fog_lightingTexture, uv + float2(_volumetric_fog_texture3d_inv_height, 0));
	return lerp(a, b, 0.5);

}

void PS_Integrated(VSOut IN, out float4 OutColor : SV_Target0)
{
	float2 nextUV = float2(IN.uv.x, 0);
	half4 currentSliceValue = GetSliceValue(IN.uv);
	uint maxIndex = IN.uv.y * _volumetric_fog_texture3d_depth;
	[loop]
	for (uint z = 1; z < _volumetric_fog_texture3d_depth; z++)
	{
		nextUV += _volumetric_fog_texture2d_next_depth_uv_offset;
		float4 nextValue = GetSliceValue(nextUV);
		currentSliceValue = AccumulateScattering(currentSliceValue, nextValue); 
		if (nextUV.y >= 1)
			break;
		if (z > maxIndex)
			break;
	}

	// final value rgb = in - scattered light accumulated so far ,
	// a = scene light transmittance
	OutColor = half4(currentSliceValue.rgb, exp(-currentSliceValue.a));

}
//------------------------------------------------------------------------------------------

#endif // VOLUMETRIC_FOG_INC