#include "Mix/Private/Scene/Common/DirectionalLight.h"
#include "Mix/Private/Scene/Common/Camera.h"

#ifdef _DEBUG
	#include "Mix/Graphics/Utility/IPerspectiveRenderer.h"
	#include "Mix/Private/Scene/Common/Debug.h"
#endif //_DEBUG

namespace Mix{ namespace Scene{ namespace Common{

const wchar_t* DirectionalLight::FAILED_CREATE = L"ディレクショナルライトの作成に失敗";

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::Common::DirectionalLight
////////////////////////////////////////////////////////////////////////////////////////////////////
		
DirectionalLight* DirectionalLight::CreateInstance( void )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_SCENE, DirectionalLight );
}

DirectionalLight::DirectionalLight( void ) : 
m_Dir( 0.0f, -1.0f, 0.0f ),
m_Color( 1.0f, 1.0f, 1.0f, 1.0f ),
m_bCast( False ),
m_bEnabled( True )
{
	m_Proj.nearClip = 0.001f;
	m_Proj.castPadding = 0.001f;
	m_Proj.errValue = 0.01f;

#ifdef _DEBUG
	m_DebDrawLength = 1000.0f;
#endif //_DEBUG
}

DirectionalLight::~DirectionalLight( void )
{
}

void DirectionalLight::Attach( void )
{
	RendererObject::SetRendering( True );
}

void DirectionalLight::Detach( void )
{
	RendererObject::SetRendering( False );
}

void DirectionalLight::Projection_CalcPSM( Mix::Scene::Common::Camera* pCamera, Mix::Matrix4x4& lightMat )
{
	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 変数宣言
	////////////////////////////////////////////////////////////////////////////////////////////////////

	Mix::Vector3 dummyVec;
	Mix::Vector3 upVec;
	Mix::Vector3 target;
	Mix::Matrix4x4 viewMat;
	Mix::Matrix4x4 projMat;
	Mix::Geometry::AABB bounding;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// シャドウ用ビュー行列を算出
	////////////////////////////////////////////////////////////////////////////////////////////////////

	//上方ベクトルを算出
	upVec = ComputeUpVector( pCamera->GetViewForward(), m_Dir );
//	Mix::PlaneSpace( m_Dir, upVec, dummyVec );

	//ターゲット
	target = ( m_CastBounds.min + m_CastBounds.max ) * 0.5f;

	//行列を算出
	Mix::Matrix4x4::LookAtLH( target - m_Dir * pCamera->GetVisiblitySettings().sdwLimitDistance, target, upVec, viewMat );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// シャドウ用射影行列を算出
	////////////////////////////////////////////////////////////////////////////////////////////////////

	//カメラを内包するAABBを作成( ビュー空間 )
	bounding = m_CastBounds;
	bounding.ComputeMinMax( viewMat );

	//行列を算出
	Mix::Matrix4x4::OrthoOffCenterLH( bounding.min, bounding.max, projMat );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ライト行列を求める
	////////////////////////////////////////////////////////////////////////////////////////////////////

	lightMat = viewMat * projMat;
}

void DirectionalLight::Projection_CalcLiSPSM( Mix::Scene::Common::Camera* pCamera, Float32 EdotL, Mix::Matrix4x4& lightMat )
{
	const Mix::Vector3& eyePos = pCamera->GetEye();
	const Mix::Vector3& viewDir = pCamera->GetViewForward();

	Mix::Vector3 dummyVec;
	Mix::Vector3 upVec;
	Mix::Vector3 lightPos;
	Mix::Matrix4x4 viewMat;
	Mix::Matrix4x4 projMat;
	Mix::Matrix4x4 lispMat;
	Mix::Matrix4x4 lispViewMat;
	Mix::Geometry::AABB bounding;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// カメラを内包する AABB の頂点座標を算出
	////////////////////////////////////////////////////////////////////////////////////////////////////

	bounding = m_CastBounds;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ビュー行列を作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

#if 1
	Mix::Vector3 newDir;

	for( UInt32 i = 0; i < 8; i++ )
	{
		newDir += ( bounding.points[i] - eyePos );
	}

	newDir.Normalize();

	upVec = ComputeUpVector( newDir, m_Dir );
#else
	Mix::PlaneSpace( m_Dir, upVec, dummyVec );
#endif

	Mix::Matrix4x4::LookAtLH( eyePos, ( eyePos + m_Dir ), upVec, viewMat );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// カメラを内包する AABB の最小値、最大値を算出
	////////////////////////////////////////////////////////////////////////////////////////////////////

	bounding.ComputeMinMax( viewMat );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 視錐台を算出
	////////////////////////////////////////////////////////////////////////////////////////////////////

	const Float32 nearZ = m_Proj.nearClip;
	const Float32 sinGamma = sqrtf( 1.0f - EdotL * EdotL );

	const Float32 factor = MIX_FLOAT_DIV( 1.0f, sinGamma );
	const Float32 z_n = factor * nearZ;
	const Float32 d = abs( bounding.max.y - bounding.min.y );

#if 1
	const Float32 z0 = -z_n;
	const Float32 z1 = -( z_n + d * sinGamma );
	const Float32 n = MIX_FLOAT_DIV( d, ( sqrtf( MIX_FLOAT_DIV( z1, z0 ) ) - 1.0f ) );
#else
	const Float32 z_f = z_n + d * sinGamma;
	const Float32 n = ( z_n + sqrtf( z_f * z_n ) ) * factor;
#endif

	const Float32 f = n + d;

	//ライトの位置( 視錐台の視点 )を算出
//	lightPos = eyePos + upVec * ( n - nearZ );
	lightPos = ( ( m_CastBounds.min + m_CastBounds.max ) * 0.5f ) + upVec * ( n - nearZ );

	//ライトビュー行列を算出
	Mix::Matrix4x4::LookAtLH( lightPos, ( lightPos + m_Dir ), upVec, viewMat );

	//ライト空間の射影行列を算出
	lispMat.m11 = MIX_FLOAT_DIV( f, ( f - n ) );
	lispMat.m31 = -f * MIX_FLOAT_DIV( n, ( f - n ) );

	//ライト空間の透視変換行列を算出
	lispViewMat = lispMat;
	lispViewMat *= viewMat;

	//ライト空間での最小値、最大値を算出
	bounding.ComputeMinMax( lispViewMat );

	//ライト射影行列を算出
	Mix::Matrix4x4::OrthoOffCenterLH( bounding.min, bounding.max, projMat );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ライト行列を求める
	////////////////////////////////////////////////////////////////////////////////////////////////////

	lightMat = viewMat * projMat;
}

Mix::Vector3 DirectionalLight::ComputeUpVector( const Mix::Vector3& v0, const Mix::Vector3& v1 )
{
	Mix::Vector3 ret;

	ret = Mix::Vector3::Cross( v0, v1 );
	ret = Mix::Vector3::Cross( v1, ret );
	ret.Normalize();

	return ret;
}

void DirectionalLight::Projection_Reset( Mix::Scene::Common::Camera* pCamera )
{
	MIX_ASSERT( pCamera != NULL );

	// クリップを求める
	m_ClipBounds.center = pCamera->GetEye();
	m_ClipBounds.radius = pCamera->GetVisiblitySettings().sdwLimitDistance;

	// キャストをクリア
	m_CastBounds.min = Mix::Vector3( +MIX_FLOAT_MAX, +MIX_FLOAT_MAX, +MIX_FLOAT_MAX );
	m_CastBounds.max = Mix::Vector3( -MIX_FLOAT_MAX, -MIX_FLOAT_MAX, -MIX_FLOAT_MAX );

	m_bCast = False;
}

void DirectionalLight::Projection_AppendCast( const Mix::Geometry::Sphere& bounds )
{
	m_CastBounds += bounds;
	m_bCast = True;
}

void DirectionalLight::Projection_AppendCast( const Mix::Geometry::AABB& bounds )
{
	m_CastBounds += bounds;
	m_bCast = True;
}

Mix::Matrix4x4 DirectionalLight::Projection_CalculateLightMatrix( Mix::Scene::Common::Camera* pCamera )
{
	MIX_ASSERT( pCamera != NULL );

	if( m_bCast == False )
	{
		return Mix::Matrix4x4::Identity();
	}

	Mix::Matrix4x4 lightMat;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ビューバウンディングの８つの点を求める
	////////////////////////////////////////////////////////////////////////////////////////////////////

	Float32 pad = m_Proj.castPadding;
	Mix::Vector3 padVec( pad, pad, pad );

	m_CastBounds.min -= padVec;
	m_CastBounds.max += padVec;

	m_CastBounds.ComputePoints();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ライト行列を求める
	////////////////////////////////////////////////////////////////////////////////////////////////////

	Float32 EdotL = Mix::Vector3::Dot( pCamera->GetViewForward(), m_Dir );
	Float32 errCos = 1.0f - m_Proj.errValue;

	if( ( EdotL >= +errCos ) || ( EdotL <= -errCos ) )
	{
		//投影方向と視線が重なる場合は PSM を使用する
		Projection_CalcPSM( pCamera, lightMat );
	}
	else
	{
		Projection_CalcLiSPSM( pCamera, EdotL, lightMat );
	}

	return lightMat;
}

const Mix::Geometry::Sphere& DirectionalLight::Projection_GetClipBounds( void ) const
{
	return m_ClipBounds;
}

Boolean DirectionalLight::Projection_IsCast( void ) const
{
	return m_bCast;
}

const Mix::Geometry::AABB& DirectionalLight::Projection_GetCastBounds( void ) const
{
	return m_CastBounds;
}

#ifdef _DEBUG

void DirectionalLight::Debug_Draw( Mix::Graphics::Utility::IPerspectiveRenderer* pPerspectiveRenderer, UInt32 flags )
{
	Mix::Matrix4x4 oldMat = pPerspectiveRenderer->GetMatrix();
	Mix::Vector4 oldColor = pPerspectiveRenderer->GetColor();

	if( MIX_TESTBIT( flags, Mix::Scene::DDF_DIRLIGHT_SHAPE ) == Mix::Scene::DDF_DIRLIGHT_SHAPE )
	{
		Mix::Vector3 pos = m_Dir * -m_DebDrawLength;

		pPerspectiveRenderer->SetMatrix( Mix::Matrix4x4::Identity() );
		pPerspectiveRenderer->SetColor( Mix::Scene::Common::Debug::GetDrawColor( Mix::Scene::DDC_LIGHT_SHAPE ) );
		pPerspectiveRenderer->AddLine( Mix::Vector3::Zero(), pos );
	}

	pPerspectiveRenderer->SetColor( oldColor );
	pPerspectiveRenderer->SetMatrix( oldMat );
}

#endif //_DEBUG

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IDirectionalLight
////////////////////////////////////////////////////////////////////////////////////////////////////

const Mix::Vector3& DirectionalLight::GetDirection( void ) const
{
	return m_Dir;
}

void DirectionalLight::SetDirection( const Mix::Vector3& dir )
{
	m_Dir = dir.ToNormalize();
}

const Mix::Vector4& DirectionalLight::GetColor( void ) const
{
	return m_Color;
}

void DirectionalLight::SetColor( const Mix::Vector4& color )
{
	m_Color = color.ToSaturate();
}

const Mix::Scene::IDirectionalLight::PROJECTION& DirectionalLight::GetProjection( void ) const
{
	return m_Proj;
}

void DirectionalLight::SetProjection( const Mix::Scene::IDirectionalLight::PROJECTION& proj )
{
	m_Proj.nearClip = max( 0.0001f, proj.nearClip );
	m_Proj.castPadding = max( 0.0f, proj.castPadding );
	m_Proj.errValue = MIX_CLAMP( proj.errValue, 0.0f, 1.0f );
}

Float32 DirectionalLight::Debug_GetDrawLength( void ) const
{
#ifdef _DEBUG
	return m_DebDrawLength;
#else //_DEBUG
	return 0.0f;
#endif //_DEBUG
}
void DirectionalLight::Debug_SetDrawLength( Float32 len )
{
#ifdef _DEBUG
	m_DebDrawLength = max( 0.0f, len );
#endif //_DEBUG
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::ILight
////////////////////////////////////////////////////////////////////////////////////////////////////

Boolean DirectionalLight::IsEnabled( void ) const
{
	return m_bEnabled;
}

void DirectionalLight::SetEnabled( Boolean state )
{
	m_bEnabled = state;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IRendererResource
////////////////////////////////////////////////////////////////////////////////////////////////////

Mix::Scene::IRendererObject::TYPE DirectionalLight::GetType( void ) const
{
	return Mix::Scene::IRendererObject::DIRECTIONAL_LIGHT;
}

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

}}}
