#include "Mix/Private/Scene/Common/SpotLight.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* SpotLight::FAILED_CREATE = L"スポットライトの作成に失敗";

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

SpotLight::SpotLight( void ) :
m_bEnabled( True ),
m_InnerAngle( MIX_TO_RAD( 10.0f ) ),
m_OuterAngle( MIX_TO_RAD( 30.0f ) ),
m_pOctObj( NULL )
{
	m_Param.pos.Set( 0.0f, 0.0f, 0.0f );
	m_Param.dir.Set( 0.0f, 0.0f, 1.0f );
	m_Param.atten.x = 1.0f;
	m_Param.atten.y = 0.0f;
	m_Param.atten.z = 0.0f;
	m_Param.color.Set( 1.0f, 1.0f, 1.0f, 1.0f );
	m_Param.range = 50.0f;
	m_Param.innerCos = ::cosf( m_InnerAngle );
	m_Param.outerCos = ::cosf( m_OuterAngle );
	m_Param.exp = 4.0f;
}

SpotLight::~SpotLight( void )
{
	MIX_ASSERT( m_pOctObj == NULL );
}

void SpotLight::UpdateBounds( void )
{
	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 全体を包むバウンズを求める
	////////////////////////////////////////////////////////////////////////////////////////////////////

	Float32 angle = MIX_FLOAT_DIV( m_OuterAngle, MIX_2PI );
	Float32 radius = MIX_2PI * m_Param.range * angle;

	Mix::Vector2 p0( 0.0f, -m_Param.range );
	Mix::Vector2 p1( -radius, 0.0f );
	Mix::Vector2 p2( radius, 0.0f );

	Float32 d1 = p0.GetLengthSqr();
	Float32 d2 = p1.GetLengthSqr();
	Float32 d3 = p2.GetLengthSqr();
	Float32 u = MIX_FLOAT_DIV( 0.5f, ( p0.x * p1.y - p1.x * p0.y + p1.x * p2.y - p2.x * p1.y + p2.x * p0.y - p0.x * p2.y ) );
	Float32 a = u * ( d1 * p1.y - d2 * p0.y + d2 * p2.y - d3 * p1.y + d3 * p0.y - d1 * p2.y );
	Float32 b = u * ( p0.x * d2 - p1.x * d1 + p1.x * d3 - p2.x * d2 + p2.x * d1 - p0.x * d3 );

	Float32 halfRange = m_Param.range * 0.5f;

	m_Bounds.radius = ::sqrtf( ( a - p0.x ) * ( a - p0.x ) + ( b - p0.y ) * ( b - p0.y ) );
	m_Bounds.center = ( m_Param.pos + ( m_Param.dir * ( halfRange - ( halfRange - m_Bounds.radius ) ) ) );
}

void SpotLight::Attach( Mix::Scene::Common::LocalLightObject* pOctObj )
{
	MIX_ASSERT( pOctObj != NULL );
	MIX_ASSERT( m_pOctObj == NULL );

	m_pOctObj = pOctObj;

	RendererObject::SetRendering( True );
}

void SpotLight::Detach( void )
{
	if( m_pOctObj != NULL )
	{
		m_pOctObj->Destroy();
		m_pOctObj = NULL;
	}

	RendererObject::SetRendering( False );
}

const SpotLight::PARAM& SpotLight::GetParam( void ) const
{
	return m_Param;
}

#ifdef _DEBUG

void SpotLight::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_SPOTLIGHT_SHAPE ) == Mix::Scene::DDF_SPOTLIGHT_SHAPE )
	{
		pPerspectiveRenderer->SetMatrix( Mix::Matrix4x4::Identity() );
		pPerspectiveRenderer->SetColor( Mix::Scene::Common::Debug::GetDrawColor( Mix::Scene::DDC_LIGHT_SHAPE ) );
		pPerspectiveRenderer->AddCone( m_Param.pos, m_Param.dir * m_Param.range, m_OuterAngle, 8 );
	}

	if( MIX_TESTBIT( flags, Mix::Scene::DDF_SPOTLIGHT_BOUNDS ) == Mix::Scene::DDF_SPOTLIGHT_BOUNDS )
	{
		pPerspectiveRenderer->SetMatrix( Mix::Matrix4x4::Identity() );
		pPerspectiveRenderer->SetColor( Mix::Scene::Common::Debug::GetDrawColor( Mix::Scene::DDC_BOUNDS ) );
		pPerspectiveRenderer->AddSphere( m_Bounds );
	}

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

#endif //_DEBUG

const Mix::Vector3& SpotLight::GetDirection( void ) const
{
	return m_Param.dir;
}

void SpotLight::SetDirection( const Mix::Vector3& dir )
{
	m_Param.dir = dir.ToNormalize();

	UpdateBounds();
}

Float32 SpotLight::GetRange( void ) const
{
	return m_Param.range;
}

void SpotLight::SetRange( Float32 range )
{
	m_Param.range = range;

	UpdateBounds();
}

void SpotLight::SetCone( Float32 innerAngle, Float32 outerAngle )
{
	m_InnerAngle = innerAngle;
	m_OuterAngle = outerAngle;

	m_Param.innerCos = ::cosf( m_InnerAngle );
	m_Param.outerCos = ::cosf( m_OuterAngle );

	UpdateBounds();
}

Float32 SpotLight::GetConeInnerAngle( void ) const
{
	return m_InnerAngle;
}

Float32 SpotLight::GetConeOuterAngle( void ) const
{
	return m_OuterAngle;
}

Float32 SpotLight::GetExponent( void ) const
{
	return 	m_Param.exp;
}

void SpotLight::SetExponent( Float32 exp )
{
	m_Param.exp = exp;
}

const Mix::Vector4& SpotLight::GetColor( void ) const
{
	return m_Param.color;
}

void SpotLight::SetColor( const Mix::Vector4& color )
{
	m_Param.color = color;
}

Float32 SpotLight::GetLinearAttenuation( void ) const
{
	return m_Param.atten.x;
}

Float32 SpotLight::GetQuadraticAttenuation( void ) const
{
	return m_Param.atten.y;
}

Float32 SpotLight::GetExponentAttenuation( void ) const
{
	return m_Param.atten.z;
}

void SpotLight::SetAttenuation( Float32 l, Float32 q, Float32 e )
{
	m_Param.atten.x = max( 0.0f, l );
	m_Param.atten.y = max( 0.0f, q );
	m_Param.atten.z = max( 0.0f, e );
}

const Mix::Vector3& SpotLight::GetPosition( void ) const
{
	return m_Param.pos;
}

void SpotLight::SetPosition( const Mix::Vector3& pos )
{
	m_Param.pos = pos;

	UpdateBounds();
}

void SpotLight::Refresh( void )
{
	if( m_pOctObj != NULL )
	{
		m_pOctObj->Refresh();
	}
}

const Mix::Geometry::Sphere& SpotLight::GetBounds() const
{
	return m_Bounds;
}

Boolean SpotLight::IsIllegal( void ) const
{
	return ( m_pOctObj != NULL )? m_pOctObj->IsIllegal() : False;
}

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

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

Mix::Scene::IRendererObject::TYPE SpotLight::GetType( void ) const
{
	return Mix::Scene::IRendererObject::SPOT_LIGHT;
}

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

}}}
