//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "ai_spotlight.h"
#include "ai_basenpc.h"
#include "spotlightend.h"
#include "beam_shared.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// Parameters for how the scanner relates to citizens.
//-----------------------------------------------------------------------------
#define	SPOTLIGHT_WIDTH					96


//-----------------------------------------------------------------------------
// Save/load 
//-----------------------------------------------------------------------------
BEGIN_SIMPLE_DATADESC( CAI_Spotlight )

	// Robin: Don't save, recreated after restore/transition.
	//DEFINE_FIELD( m_hSpotlight,				FIELD_EHANDLE ),
	//DEFINE_FIELD( m_hSpotlightTarget,		FIELD_EHANDLE ),
	DEFINE_FIELD( m_vSpotlightTargetPos,	FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vSpotlightDir,			FIELD_VECTOR ),
	DEFINE_FIELD( m_flSpotlightCurLength,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flSpotlightMaxLength,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flConstraintAngle,		FIELD_FLOAT ),
	DEFINE_FIELD( m_nHaloSprite,			FIELD_MODELINDEX ),
	DEFINE_FIELD( m_nSpotlightAttachment,	FIELD_INTEGER ),
	DEFINE_FIELD( m_nFlags,					FIELD_INTEGER ),
	DEFINE_FIELD( m_vAngularVelocity,		FIELD_QUATERNION ),

END_DATADESC()



//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CAI_Spotlight::CAI_Spotlight()
{
#ifdef _DEBUG
	m_vSpotlightTargetPos.Init();
	m_vSpotlightDir.Init();
#endif
}


CAI_Spotlight::~CAI_Spotlight()
{
	SpotlightDestroy();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_Spotlight::Precache(void)
{
	// Sprites
	m_nHaloSprite = GetOuter()->PrecacheModel("sprites/light_glow03.vmt");

	GetOuter()->PrecacheModel( "sprites/glow_test02.vmt" );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_Spotlight::Init( CAI_BaseNPC *pOuter, int nFlags, float flConstraintAngle, float flMaxLength )
{
	SetOuter( pOuter );
	m_nFlags = nFlags;
	m_flConstraintAngle = flConstraintAngle; 
	m_flSpotlightMaxLength = flMaxLength;

	// Check for user error
	if (m_flSpotlightMaxLength <= 0)
	{
		DevMsg("ERROR: Invalid spotlight length <= 0, setting to 500\n");
		m_flSpotlightMaxLength = 500;
	}

	Precache();

	m_vSpotlightTargetPos	= vec3_origin;
	m_hSpotlight			= NULL;
	m_hSpotlightTarget		= NULL;

	AngleVectors( GetAbsAngles(), &m_vSpotlightDir );
	m_vAngularVelocity.Init( 0, 0, 0, 1 );
	m_flSpotlightCurLength	= m_flSpotlightMaxLength;
}


//------------------------------------------------------------------------------
// Computes the spotlight endpoint
//------------------------------------------------------------------------------
void CAI_Spotlight::ComputeEndpoint( const Vector &vecStartPoint, Vector *pEndPoint )
{
	// Create the endpoint
	trace_t tr;
	AI_TraceLine( vecStartPoint, vecStartPoint + m_vSpotlightDir * 2 * m_flSpotlightMaxLength, MASK_OPAQUE, GetOuter(), COLLISION_GROUP_NONE, &tr );
	*pEndPoint = tr.endpos;
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_Spotlight::SpotlightCreate( int nAttachment, const Vector &vecInitialDir )
{
	// Make sure we don't already have one
	if ( m_hSpotlight != NULL )
		return;

	m_vSpotlightDir	= vecInitialDir;
	VectorNormalize( m_vSpotlightDir );
	m_nSpotlightAttachment = nAttachment;

	CreateSpotlightEntities();
}

//-----------------------------------------------------------------------------
// Purpose: Create the beam & spotlight end for this spotlight.
//			Will be re-called after restore/transition
//-----------------------------------------------------------------------------
void CAI_Spotlight::CreateSpotlightEntities( void )
{
	m_vAngularVelocity.Init( 0, 0, 0, 1 );

	// Create the endpoint
	// Get the initial position...
	Vector vecStartPoint;
	if ( m_nSpotlightAttachment == 0 ) 
	{
		vecStartPoint = GetOuter()->GetAbsOrigin();
	}
	else
	{
		GetOuter()->GetAttachment( m_nSpotlightAttachment, vecStartPoint );
	}

	Vector vecEndPoint;
	ComputeEndpoint( vecStartPoint, &vecEndPoint );

	m_hSpotlightTarget = (CSpotlightEnd*)CreateEntityByName( "spotlight_end" );
	m_hSpotlightTarget->Spawn();
	m_hSpotlightTarget->SetAbsOrigin( vecEndPoint );
	m_hSpotlightTarget->SetOwnerEntity( GetOuter() );
	m_hSpotlightTarget->SetRenderColor( 255, 255, 255 );
	m_hSpotlightTarget->m_Radius = m_flSpotlightMaxLength;
	if ( FBitSet (m_nFlags, AI_SPOTLIGHT_NO_DLIGHTS) )
	{
		m_hSpotlightTarget->m_flLightScale = 0.0;
	}
	else
	{
		m_hSpotlightTarget->m_flLightScale = SPOTLIGHT_WIDTH;
	}

	// Create the beam
	m_hSpotlight = CBeam::BeamCreate( "sprites/glow_test02.vmt", SPOTLIGHT_WIDTH );
	// Set the temporary spawnflag on the beam so it doesn't save (we'll recreate it on restore)
	m_hSpotlight->AddSpawnFlags( SF_BEAM_TEMPORARY );
	m_hSpotlight->SetColor( 255, 255, 255 ); 
	m_hSpotlight->SetHaloTexture( m_nHaloSprite );
	m_hSpotlight->SetHaloScale( 32 );
	m_hSpotlight->SetEndWidth( m_hSpotlight->GetWidth() );
	m_hSpotlight->SetBeamFlags( (FBEAM_SHADEOUT|FBEAM_NOTILE) );
	m_hSpotlight->SetBrightness( 32 );
	m_hSpotlight->SetNoise( 0 );
	m_hSpotlight->EntsInit( GetOuter(), m_hSpotlightTarget );
	m_hSpotlight->SetStartAttachment( m_nSpotlightAttachment );
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_Spotlight::SpotlightDestroy(void)
{
	if ( m_hSpotlight )
	{
		UTIL_Remove(m_hSpotlight);
		m_hSpotlight = NULL;
		
		UTIL_Remove(m_hSpotlightTarget);
		m_hSpotlightTarget = NULL;
	}
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_Spotlight::SetSpotlightTargetPos( const Vector &vSpotlightTargetPos )
{
	m_vSpotlightTargetPos = vSpotlightTargetPos;
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_Spotlight::SetSpotlightTargetDirection( const Vector &vSpotlightTargetDir )
{
	if ( !m_hSpotlight )
	{
		CreateSpotlightEntities();
	}

	VectorMA( m_hSpotlight->GetAbsStartPos(), 1000.0f, vSpotlightTargetDir, m_vSpotlightTargetPos );
}


//------------------------------------------------------------------------------
// Constrain to cone
//------------------------------------------------------------------------------
bool CAI_Spotlight::ConstrainToCone( Vector *pDirection )
{
	Vector vecOrigin, vecForward;
	if ( m_nSpotlightAttachment == 0 ) 
	{
		QAngle vecAngles;
		vecAngles = GetOuter()->GetAbsAngles();
		AngleVectors( vecAngles, &vecForward );
	}
	else
	{
		GetOuter()->GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward );
	}


	if ( m_flConstraintAngle == 0.0f )
	{
		*pDirection = vecForward;
		return true;
	}

	float flDot = DotProduct( vecForward, *pDirection );
	if ( flDot >= cos( DEG2RAD( m_flConstraintAngle ) ) )
		return false;

	Vector vecAxis;
	CrossProduct( *pDirection, vecForward, vecAxis );
	VectorNormalize( vecAxis );

	Quaternion q;
	AxisAngleQuaternion( vecAxis, -m_flConstraintAngle, q );

	Vector vecResult;
	matrix3x4_t rot;
	QuaternionMatrix( q, rot );
	VectorRotate( vecForward, rot, vecResult );
	VectorNormalize( vecResult );

	*pDirection = vecResult;

	return true;
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
#define QUAT_BLEND_FACTOR	0.4f

void CAI_Spotlight::UpdateSpotlightDirection( void )
{
	if ( !m_hSpotlight )
	{
		CreateSpotlightEntities();
	}

	// Compute the current beam direction
	Vector vTargetDir;
	VectorSubtract( m_vSpotlightTargetPos, m_hSpotlight->GetAbsStartPos(), vTargetDir ); 
	VectorNormalize(vTargetDir);
	ConstrainToCone( &vTargetDir );

	// Compute the amount to rotate
	float flDot = DotProduct( vTargetDir, m_vSpotlightDir );
	flDot = clamp( flDot, -1.0f, 1.0f );
	float flAngle = AngleNormalize( RAD2DEG( acos( flDot ) ) );
	float flClampedAngle = clamp( flAngle, 0.0f, 45.0f );
	float flBeamTurnRate = SimpleSplineRemapVal( flClampedAngle, 0.0f, 45.0f, 10.0f, 45.0f );
	if ( fabs(flAngle) > flBeamTurnRate * gpGlobals->frametime )
	{
		flAngle = flBeamTurnRate * gpGlobals->frametime;
	}

	// Compute the rotation axis
	Vector vecRotationAxis;
	CrossProduct( m_vSpotlightDir, vTargetDir, vecRotationAxis );
	if ( VectorNormalize( vecRotationAxis ) < 1e-3 )
	{
		vecRotationAxis.Init( 0, 0, 1 );
	}

	// Compute the actual rotation amount, using quat slerp blending
	Quaternion desiredQuat, resultQuat;
	AxisAngleQuaternion( vecRotationAxis, flAngle, desiredQuat );
	QuaternionSlerp( m_vAngularVelocity, desiredQuat, QUAT_BLEND_FACTOR, resultQuat );
	m_vAngularVelocity = resultQuat;

	// If we're really close, and we're not moving very quickly, slam.
	float flActualRotation = AngleNormalize( RAD2DEG(2 * acos(m_vAngularVelocity.w)) );
	if (( flActualRotation < 1e-3 ) && (flAngle < 1e-3 ))
	{
		m_vSpotlightDir = vTargetDir;
		m_vAngularVelocity.Init( 0, 0, 0, 1 );
		return;
	}

	// Update the desired direction
	matrix3x4_t rot;
	Vector vecNewDir;
	QuaternionMatrix( m_vAngularVelocity, rot );
	VectorRotate( m_vSpotlightDir, rot, vecNewDir );
	m_vSpotlightDir = vecNewDir;
	VectorNormalize(m_vSpotlightDir);
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_Spotlight::UpdateSpotlightEndpoint( void )
{
	if ( !m_hSpotlight )
	{
		CreateSpotlightEntities();
	}

	Vector vecStartPoint, vecEndPoint;
	vecStartPoint = m_hSpotlight->GetAbsStartPos();
	ComputeEndpoint( vecStartPoint, &vecEndPoint );

	// If I'm not facing the spotlight turn it off 
	Vector vecSpotDir;
	VectorSubtract( vecEndPoint, vecStartPoint, vecSpotDir );
	float flBeamLength = VectorNormalize(vecSpotDir);
	
	m_hSpotlightTarget->SetAbsOrigin( vecEndPoint );
	m_hSpotlightTarget->SetAbsVelocity( vec3_origin );
	m_hSpotlightTarget->m_vSpotlightOrg = vecStartPoint;
	m_hSpotlightTarget->m_vSpotlightDir = vecSpotDir;

	// Avoid sudden change in where beam fades out when cross disconinuities
	m_flSpotlightCurLength = Lerp( 0.20f, m_flSpotlightCurLength, flBeamLength );

	// Fade out spotlight end if past max length.  
	if (m_flSpotlightCurLength > 2*m_flSpotlightMaxLength)
	{
		m_hSpotlightTarget->SetRenderColorA( 0 );
		m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength);
	}
	else if (m_flSpotlightCurLength > m_flSpotlightMaxLength)		
	{
		m_hSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) );
		m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength);
	}
	else
	{
		m_hSpotlightTarget->SetRenderColorA( 1.0 );
		m_hSpotlight->SetFadeLength(m_flSpotlightCurLength);
	}

	// Adjust end width to keep beam width constant
	float flNewWidth = SPOTLIGHT_WIDTH * ( flBeamLength / m_flSpotlightMaxLength );
	
	flNewWidth = MIN( 100, flNewWidth );

	m_hSpotlight->SetWidth(flNewWidth);
	m_hSpotlight->SetEndWidth(flNewWidth);

	// Adjust width of light on the end.  
	if ( FBitSet (m_nFlags, AI_SPOTLIGHT_NO_DLIGHTS) )
	{
		m_hSpotlightTarget->m_flLightScale = 0.0;
	}
	else
	{
		m_hSpotlightTarget->m_flLightScale = flNewWidth;
	}
}


//------------------------------------------------------------------------------
// Purpose: Update the direction and position of my spotlight (if it's active)
//------------------------------------------------------------------------------
void CAI_Spotlight::Update(void)
{
	if ( !m_hSpotlight )
	{
		CreateSpotlightEntities();
	}

	// Update the beam direction
	UpdateSpotlightDirection();
	UpdateSpotlightEndpoint();
}