#include "Mix/Private/Scene/Common/AtmosphereScatter.h"

#include "Mix/Graphics/IDevice.h"
#include "Mix/Graphics/IVertexBuffer.h"
#include "Mix/Graphics/IIndexBuffer.h"
#include "Mix/Graphics/ITexture.h"
#include "Mix/Scene/ICamera.h"

namespace Mix{ namespace Scene{ namespace Common{

const Mix::Vector3 AtmosphereScatter::Y_AXIS( 0.0f, 1.0f, 0.0 );
const Mix::Vector3 AtmosphereScatter::BLACK( 0.0f, 0.0f, 0.0f );

const Float32 AtmosphereScatter::DEF_SD_RADIUS = 1000.0f;
const Float32 AtmosphereScatter::DEF_SD_HEIGHT = 150.0f;

AtmosphereScatter* AtmosphereScatter::CreateInstance( void )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_SCENE, AtmosphereScatter );
}

AtmosphereScatter::AtmosphereScatter( void ) :
m_OuterRadius( 0.0f ),
m_InnerRadius( 0.0f ),
m_SunPos( 0.0f, 1.0f, 0.0f )
{
	const Mix::Vector3 lambda( 1.0f / 650e-9f, 1.0f / 570e-9f, 1.0f / 475e-9f );
	const Mix::Vector3 lambda2 = lambda * lambda;
	const Mix::Vector3 lambda4 = lambda2 * lambda2;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// Rayleigh
	////////////////////////////////////////////////////////////////////////////////////////////////////

	const Float32 n = 1.0003f;
	const Float32 pn = 0.035f;
	const Float32 N = 2.545e25f;

	Float32 tempRay = MIX_PI * MIX_PI * ( n * n - 1.0f ) * ( n * n - 1.0f ) * ( 6.0f + 3.0f * pn ) / ( 6.0f - 7.0f * pn ) / N;
	Float32 beta = 8.0f * tempRay * MIX_PI / 3.0f;
	Float32 betaDash = tempRay / 2.0f;

	m_BetaR = lambda4 * beta;
	m_BetaDashR = lambda4 * betaDash;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// Mie
	////////////////////////////////////////////////////////////////////////////////////////////////////

	const Float32 T = 2.0f;
	const Float32 c = ( 6.544f * T - 6.51f ) * 1e-17f;
	const Mix::Vector3 K( 0.685f, 0.679f, 0.670f );

	Float32 tempMie0 = 0.434f * c * MIX_PI * ( 2.0f * MIX_PI ) * ( 2.0f * MIX_PI );
	Float32 tempMie1 = 0.434f * c * ( 2.0f * MIX_PI ) * ( 2.0f * MIX_PI ) * 0.5f;

	m_BetaM = K * lambda2 * tempMie0;
	m_BetaDashM = lambda2 * tempMie1;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// スキャッタリング : パラメータ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_DaySettings.sunIntensity = 0.2f;
	m_DaySettings.sunTurbidity = 0.6f;
	m_DaySettings.henyeyGreenstein = 0.93f;
	m_DaySettings.rayleigh = 80.0f;
	m_DaySettings.mie = 0.00060000003f;
	m_DaySettings.lin = 0.909999967f;
	m_DaySettings.fex.Set( 0.138f, 0.113f, 0.08f, 0.1f );

	UpdateSun();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 夜 : パラメータ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_NightSettings.color.Set( 0.02f, 0.02f, 0.04f, 1.0f );
	m_NightSettings.threshold = 0.0f;
	m_NightSettings.offset = 0.45f;

	m_InvNPOffset = 1.0f / m_NightSettings.offset;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// レンダラーオブジェクト
	////////////////////////////////////////////////////////////////////////////////////////////////////

	RendererObject::SetRendering( True );
}

AtmosphereScatter::~AtmosphereScatter( void )
{
}

void AtmosphereScatter::Dispose( void )
{
	RendererObject::SetRendering( False );
}

void AtmosphereScatter::UpdateSun( void )
{
	Mix::Vector3 sunPos = m_SunPos.ToNormalize() * m_OuterRadius;
	Mix::Vector3 habitatPos( 0.0f, m_InnerRadius, 0.0f );

	m_SunDir = ( sunPos - habitatPos ).ToNormalize();

	UpdateSunColor();
}

void AtmosphereScatter::UpdateSunColor( void )
{
	static const Mix::Vector3 LAMDA( 0.65f, 0.57f, 0.475f );
	static const Float32 ALPHA = 1.3f;

	Float32 beta = 0.04608365822050f * m_DaySettings.sunTurbidity - 0.04586025928522f;
	Float32 baseTheta = Mix::Vector3::Dot( m_SunDir, AtmosphereScatter::Y_AXIS );

	Float32 theta;
	Float32 deg;
	Float32 m;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 太陽の色
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( baseTheta >= 0.0f )
	{
		theta = baseTheta;
		deg = ::acos( theta ) / MIX_PI * 180.0f;
	}
	else
	{
		theta = 0.0f;
		deg = 90.0f;
	}

	m = 1.0f / ( theta + 0.15f * ::powf( 93.885f - deg, -1.253f ) );

	for( UInt32 i = 0; i < 3; ++i )
	{
		Float32 tauR = ::expf( -m * 0.008735f * ::powf( LAMDA.data[i], -4.08f ) );
		Float32 tauA = ::expf( -m * beta * ::powf( LAMDA.data[i], -ALPHA ) );

		m_SunColor.data[i] = tauR * tauA; 
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 太陽によるライティングの色
	// 夜への移行
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( m_NightSettings.threshold < baseTheta )
	{
		//昼
		m_SunColor = m_SunColor;
		m_NightColor.Set( 0.0f, 0.0f, 0.0f, 0.0f );
	}
	else
	{
		const Mix::Vector4& nightColor = m_NightSettings.color;
		Float32 diffCos = m_NightSettings.threshold - baseTheta;

		m_NightColor.r = nightColor.r * nightColor.a;
		m_NightColor.g = nightColor.g * nightColor.a;
		m_NightColor.b = nightColor.b * nightColor.a;

		if( diffCos < m_NightSettings.offset )
		{
			//昼→夜へ移行中
			Float32 ratio = MIX_CLAMP( diffCos * m_InvNPOffset, 0.0f, 1.0f );

			m_SunColor = Mix::Vector4::Lerp( m_SunColor, AtmosphereScatter::BLACK, ratio );
			m_NightColor.a = ratio;
		}
		else
		{
			//夜
			m_SunColor = AtmosphereScatter::BLACK;
			m_NightColor.a = 1.0f;
		}
	}

	m_SunLightColor = m_SunColor;
	m_SunLightColor.a = MIX_CLAMP( m_SunColor.GetLength(), 0.0f, 1.0f );
}

void AtmosphereScatter::UpdateSunPosition( const Mix::Vector3& pos )
{
	m_SunPos = pos;

	UpdateSun();
}

void AtmosphereScatter::UpdateHemisphere( Float32 radius, Float32 height, Float32 outerRadius, Float32 innerRadius )
{
	Mix::Vector3 corePos( 0.0f, height - ( outerRadius - height ), 0.0f );
	Mix::Vector3 horizPos( radius, 0.0f, 0.0f );
	Mix::Vector3 horizVec = horizPos - corePos;

	m_OuterRadius = outerRadius;
	m_InnerRadius = innerRadius;

	UpdateSun();
}

const Mix::Vector3& AtmosphereScatter::GetSunDir( void ) const
{
	return m_SunDir;
}

AtmosphereScatter::INTERNAL_SETTINGS AtmosphereScatter::GetInternalSettings( void ) const
{
	const Mix::Vector4& fex = m_DaySettings.fex;

	Float32 hg = m_DaySettings.henyeyGreenstein;
	Float32 rayleigh = m_DaySettings.rayleigh;
	Float32 mie = m_DaySettings.mie;

	AtmosphereScatter::INTERNAL_SETTINGS ret;

	ret.sunColor = m_SunColor * 100.0f * m_DaySettings.sunIntensity;
	ret.sunColor.a = 1.0f;

	ret.nightColor = m_NightColor;

	ret.hg.Set(	1.0f - hg * hg, 1.0f + hg * hg, 2.0f * hg );

	ret.betaDashR = m_BetaDashR * rayleigh;
	ret.betaDashM = m_BetaDashM * mie;
	ret.betaRM = ( m_BetaR * rayleigh ) + ( m_BetaM * mie );

	ret.oneOverBetaRM.Set(	MIX_FLOAT_RECIPROCAL( ret.betaRM.x ),
							MIX_FLOAT_RECIPROCAL( ret.betaRM.y ),
							MIX_FLOAT_RECIPROCAL( ret.betaRM.z ),
							1.0f );

	ret.mulitpliers.Set( m_DaySettings.lin, fex.x * fex.w, fex.y * fex.w, fex.z * fex.w );

	return ret;
}

const Mix::Vector4& AtmosphereScatter::GetInternalNightColor( void ) const
{
	return m_NightColor;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IAtmosphereScatter
////////////////////////////////////////////////////////////////////////////////////////////////////

const Mix::Scene::IAtmosphereScatter::DAY_SETTINGS& AtmosphereScatter::GetDaySettings( void ) const
{
	return m_DaySettings;
}

void AtmosphereScatter::SetDaySettings( const Mix::Scene::IAtmosphereScatter::DAY_SETTINGS& settings )
{
	m_DaySettings.sunIntensity = max( 0.0f, settings.sunIntensity );
	m_DaySettings.sunTurbidity = MIX_CLAMP( settings.sunTurbidity, 0.0f, 4.0f );
	m_DaySettings.henyeyGreenstein = max( 0.0f, settings.henyeyGreenstein );
	m_DaySettings.rayleigh = max( 0.0f, settings.rayleigh );
	m_DaySettings.mie = max( 0.0f, settings.mie );
	m_DaySettings.lin = max( 0.0f, settings.lin );
	m_DaySettings.fex = Mix::Vector4::Max( Mix::Vector4( 0.0f, 0.0f, 0.0f, 0.0f ), settings.fex );

	UpdateSun();
}

const Mix::Scene::IAtmosphereScatter::NIGHT_SETTINGS& AtmosphereScatter::GetNightSettings( void ) const
{
	return m_NightSettings;
}

void AtmosphereScatter::SetNightSettings( const Mix::Scene::IAtmosphereScatter::NIGHT_SETTINGS& settings )
{
	m_NightSettings.color = settings.color;
	m_NightSettings.threshold = MIX_CLAMP( settings.threshold, -1.0f, +1.0f );
	m_NightSettings.offset = max( 0.00001f, settings.offset );

	m_InvNPOffset = MIX_FLOAT_RECIPROCAL( m_NightSettings.offset );
}

const Mix::Vector3& AtmosphereScatter::GetSunColor( void ) const
{
	return m_SunColor;
}

const Mix::Vector4& AtmosphereScatter::GetSunLightColor( void ) const
{
	return m_SunLightColor;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IRendererObject
////////////////////////////////////////////////////////////////////////////////////////////////////

Mix::Scene::IRendererObject::TYPE AtmosphereScatter::GetType( void ) const
{
	return Mix::Scene::IRendererObject::ATMOSPHERE_SCATTER;
}

Boolean AtmosphereScatter::IsRendering( void ) const
{
	return RendererObject::IsRendering();
}

}}}
