//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "env_headcrabcanister_shared.h"
#include "mapdata_shared.h"
#include "sharedInterface.h"
#include "mathlib/vmatrix.h"

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

#define ROTATION_SPEED 90.0f


BEGIN_SIMPLE_DATADESC( CEnvHeadcrabCanisterShared )
	DEFINE_FIELD( m_vecStartPosition,			FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecEnterWorldPosition,		FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecDirection,				FIELD_VECTOR ),
	DEFINE_FIELD( m_vecStartAngles,				FIELD_VECTOR ),
	DEFINE_KEYFIELD( m_flLaunchHeight,			FIELD_FLOAT,	"StartingHeight" ),
	DEFINE_KEYFIELD( m_flFlightSpeed,			FIELD_FLOAT,	"FlightSpeed" ),
	DEFINE_KEYFIELD( m_flFlightTime,			FIELD_FLOAT,	"FlightTime" ),
	DEFINE_FIELD( m_flLaunchTime,				FIELD_TIME ),
	DEFINE_FIELD( m_flWorldEnterTime,			FIELD_FLOAT ),
	DEFINE_FIELD( m_flInitialZSpeed,			FIELD_FLOAT ),
	DEFINE_FIELD( m_flZAcceleration,			FIELD_FLOAT ),
	DEFINE_FIELD( m_flHorizSpeed,				FIELD_FLOAT ),
	DEFINE_FIELD( m_bLaunchedFromWithinWorld,	FIELD_BOOLEAN ),
	DEFINE_FIELD( m_vecSkyboxOrigin,			FIELD_VECTOR ),
	DEFINE_FIELD( m_vecParabolaDirection,		FIELD_VECTOR ),
	DEFINE_FIELD( m_flSkyboxScale,				FIELD_FLOAT ),
	DEFINE_FIELD( m_bInSkybox,					FIELD_BOOLEAN ),
END_DATADESC()


BEGIN_NETWORK_TABLE_NOBASE( CEnvHeadcrabCanisterShared, DT_EnvHeadcrabCanisterShared )

#if !defined( CLIENT_DLL )
	SendPropFloat	( SENDINFO( m_flFlightSpeed ),			0, SPROP_NOSCALE ),	
	SendPropTime	( SENDINFO( m_flLaunchTime ) ),
	SendPropVector	( SENDINFO( m_vecParabolaDirection ),	0, SPROP_NOSCALE ),	

	SendPropFloat	( SENDINFO( m_flFlightTime ),			0, SPROP_NOSCALE ),
	SendPropFloat	( SENDINFO( m_flWorldEnterTime ),		0, SPROP_NOSCALE ),

	SendPropFloat	( SENDINFO( m_flInitialZSpeed ),		0, SPROP_NOSCALE ),
	SendPropFloat	( SENDINFO( m_flZAcceleration ),		0, SPROP_NOSCALE ),
	SendPropFloat	( SENDINFO( m_flHorizSpeed ),			0, SPROP_NOSCALE ),
	SendPropBool	( SENDINFO( m_bLaunchedFromWithinWorld ) ),
	
	SendPropVector	( SENDINFO( m_vecStartPosition ),       0, SPROP_NOSCALE ),	
	SendPropVector	( SENDINFO( m_vecEnterWorldPosition ),  0, SPROP_NOSCALE ),	
	SendPropVector	( SENDINFO( m_vecDirection ),			0, SPROP_NOSCALE ),	
	SendPropVector	( SENDINFO( m_vecStartAngles ),			0, SPROP_NOSCALE ),	

	SendPropVector	( SENDINFO( m_vecSkyboxOrigin ),		0, SPROP_NOSCALE ),	
	SendPropFloat	( SENDINFO( m_flSkyboxScale ),			0, SPROP_NOSCALE ),
	SendPropBool	( SENDINFO( m_bInSkybox ) ),
#else
	RecvPropFloat	( RECVINFO( m_flFlightSpeed ) ),	
	RecvPropTime	( RECVINFO( m_flLaunchTime ) ),
	RecvPropVector	( RECVINFO( m_vecParabolaDirection ) ),	

	RecvPropFloat	( RECVINFO( m_flFlightTime ) ),	
	RecvPropFloat	( RECVINFO( m_flWorldEnterTime ) ),	

	RecvPropFloat	( RECVINFO( m_flInitialZSpeed ) ),	
	RecvPropFloat	( RECVINFO( m_flZAcceleration ) ),	
	RecvPropFloat	( RECVINFO( m_flHorizSpeed ) ),	
	RecvPropBool	( RECVINFO( m_bLaunchedFromWithinWorld ) ),	

	RecvPropVector	( RECVINFO( m_vecStartPosition ) ),	
	RecvPropVector	( RECVINFO( m_vecEnterWorldPosition ) ),	
	RecvPropVector	( RECVINFO( m_vecDirection ) ),	
	RecvPropVector	( RECVINFO( m_vecStartAngles ) ),	

	RecvPropVector	( RECVINFO( m_vecSkyboxOrigin ) ),	
	RecvPropFloat	( RECVINFO( m_flSkyboxScale ) ),	
	RecvPropBool	( RECVINFO( m_bInSkybox ) ),	
#endif

END_NETWORK_TABLE()



//=============================================================================
//
// HeadcrabCanister Functions.
//

//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CEnvHeadcrabCanisterShared::CEnvHeadcrabCanisterShared()
{
	m_vecStartPosition.Init();
	m_vecDirection.Init();
	m_flFlightSpeed = 0.0f;

	// This tells the client DLL to not draw trails, etc.
	m_flLaunchTime = -1.0f;

	m_flWorldEnterTime = 0.0f;
	m_flFlightTime = 0.0f;
	m_bInSkybox = false;
}


//-----------------------------------------------------------------------------
// Creates a headcrab canister in the world
//-----------------------------------------------------------------------------
void CEnvHeadcrabCanisterShared::InitInWorld( float flLaunchTime, 
	const Vector &vecStartPosition, const QAngle &vecStartAngles, 
	const Vector &vecDirection, const Vector &vecImpactPosition, bool bLaunchedFromWithinWorld )
{
	Vector vecActualStartPosition = vecStartPosition;
	if ( !bLaunchedFromWithinWorld )
	{
		// Move the start position inward if it's too close
		Vector vecDelta;
		VectorSubtract( vecStartPosition, vecImpactPosition, vecDelta );
		VectorNormalize( vecDelta );

		VectorMA( vecImpactPosition, m_flFlightTime * m_flFlightSpeed, vecDelta, vecActualStartPosition );
	}
 
	// Setup initial parametric state.
	m_flLaunchTime = flLaunchTime;
	m_vecStartPosition = vecActualStartPosition;
	m_vecEnterWorldPosition = vecActualStartPosition;
	m_vecDirection = vecDirection;
	m_vecStartAngles = vecStartAngles;
	m_flWorldEnterTime = 0.0f;
	m_bInSkybox = false;
	m_bLaunchedFromWithinWorld = bLaunchedFromWithinWorld;
 
	if ( m_bLaunchedFromWithinWorld )
	{
		m_flSkyboxScale = 1;
		m_vecSkyboxOrigin = vec3_origin;

		float flLength = m_vecDirection.Get().AsVector2D().Length();
		VectorSubtract(vecImpactPosition, vecStartPosition, m_vecParabolaDirection.GetForModify());
		m_vecParabolaDirection.GetForModify().z = 0;
		float flTotalDistance = VectorNormalize( m_vecParabolaDirection.GetForModify() );
		m_vecDirection.GetForModify().x = flLength * m_vecParabolaDirection.Get().x;
		m_vecDirection.GetForModify().y = flLength * m_vecParabolaDirection.Get().y;
 
		m_flHorizSpeed = flTotalDistance / m_flFlightTime;
		m_flWorldEnterTime = 0;
 
		float flFinalZSpeed = m_vecDirection.Get().z * m_flHorizSpeed;
		m_flFlightSpeed = sqrt( m_flHorizSpeed * m_flHorizSpeed + flFinalZSpeed * flFinalZSpeed );
		m_flInitialZSpeed = (2.0f * ( vecImpactPosition.z - vecStartPosition.z ) - flFinalZSpeed * m_flFlightTime) / m_flFlightTime;
		m_flZAcceleration = (flFinalZSpeed - m_flInitialZSpeed) / m_flFlightTime;
	}
}


//-----------------------------------------------------------------------------
// Creates a headcrab canister in the skybox
//-----------------------------------------------------------------------------
void CEnvHeadcrabCanisterShared::InitInSkybox( float flLaunchTime, 
	const Vector &vecStartPosition, const QAngle &vecStartAngles, const Vector &vecDirection,
	const Vector &vecImpactPosition, const Vector &vecSkyboxOrigin, float flSkyboxScale )
{
	// Compute a horizontal speed (constant)
	m_vecParabolaDirection.Init( vecDirection.x, vecDirection.y, 0.0f );
	float flLength = VectorNormalize( m_vecParabolaDirection.GetForModify() ); 
	m_flHorizSpeed = flLength * m_flFlightSpeed;

	// compute total distance to travel
	float flTotalDistance = m_flFlightTime * m_flHorizSpeed;
	flTotalDistance -= vecStartPosition.AsVector2D().DistTo( vecImpactPosition.AsVector2D() );
	if ( flTotalDistance <= 0.0f )
	{
		InitInWorld( flLaunchTime, vecStartPosition, vecStartAngles, vecDirection, vecImpactPosition );
		return;
	}

	// Setup initial parametric state.
	m_flLaunchTime = flLaunchTime;
	m_flWorldEnterTime = flTotalDistance / m_flHorizSpeed;
	m_vecSkyboxOrigin = vecSkyboxOrigin;
	m_flSkyboxScale = flSkyboxScale;

	m_vecEnterWorldPosition = vecStartPosition;
	m_vecDirection = vecDirection;
	m_vecStartAngles = vecStartAngles;
	m_bInSkybox = true;
	m_bLaunchedFromWithinWorld = false;

	// Compute parabolic course
	// Assume the x velocity remains constant.
	// Z moves ballistically, as if under gravity
	// zf + lh = zo
	// vf = vo + a*t
	// zf = zo + vo*t + 0.5 * a * t*t
	// a*t = vf - vo
	// zf = zo + vo*t + 0.5f * (vf - vo) * t
	// zf - zo = 0.5f *vo*t + 0.5f * vf * t
	// -lh - 0.5f * vf * t = 0.5f * vo * t
	// vo = -2.0f * lh / t - vf
	// a = (vf - vo) / t
	m_flHorizSpeed /= flSkyboxScale;

	VectorMA( vecSkyboxOrigin, 1.0f / m_flSkyboxScale, vecStartPosition, m_vecStartPosition.GetForModify() );
	VectorMA( m_vecStartPosition.Get(), -m_flHorizSpeed * m_flWorldEnterTime, m_vecParabolaDirection, m_vecStartPosition.GetForModify() );

	float flLaunchHeight = m_flLaunchHeight / flSkyboxScale;
	float flFinalZSpeed = m_vecDirection.Get().z * m_flFlightSpeed / flSkyboxScale;
	m_vecStartPosition.GetForModify().z += flLaunchHeight;
	m_flZAcceleration = 2.0f * ( flLaunchHeight + flFinalZSpeed * m_flWorldEnterTime ) / ( m_flWorldEnterTime * m_flWorldEnterTime );
	m_flInitialZSpeed = flFinalZSpeed - m_flZAcceleration * m_flWorldEnterTime;
}


//-----------------------------------------------------------------------------
// Convert from skybox to world
//-----------------------------------------------------------------------------
void CEnvHeadcrabCanisterShared::ConvertFromSkyboxToWorld()
{
	Assert( m_bInSkybox );
	m_bInSkybox = false;
}


//-----------------------------------------------------------------------------
// Returns the time at which it enters the world
//-----------------------------------------------------------------------------
float CEnvHeadcrabCanisterShared::GetEnterWorldTime() const
{
	return m_flWorldEnterTime;
}


//-----------------------------------------------------------------------------
// Did we impact?
//-----------------------------------------------------------------------------
bool CEnvHeadcrabCanisterShared::DidImpact( float flTime ) const
{
	return (flTime - m_flLaunchTime) >= m_flFlightTime;
}


//-----------------------------------------------------------------------------
// Computes the position of the canister
//-----------------------------------------------------------------------------
void CEnvHeadcrabCanisterShared::GetPositionAtTime( float flTime, Vector &vecPosition, QAngle &vecAngles )
{
	float flDeltaTime = flTime - m_flLaunchTime;
	if ( flDeltaTime > m_flFlightTime )
	{
		flDeltaTime = m_flFlightTime;
	}

	VMatrix initToWorld;
	if ( m_bLaunchedFromWithinWorld || m_bInSkybox )
	{
		VectorMA( m_vecStartPosition, flDeltaTime * m_flHorizSpeed, m_vecParabolaDirection, vecPosition );
		vecPosition.z += m_flInitialZSpeed * flDeltaTime + 0.5f * m_flZAcceleration * flDeltaTime * flDeltaTime;

		Vector vecLeft;
		CrossProduct( m_vecParabolaDirection, Vector( 0, 0, 1 ), vecLeft );

		Vector vecForward;
		VectorMultiply( m_vecParabolaDirection, -1.0f, vecForward );
		vecForward.z = -(m_flInitialZSpeed + m_flZAcceleration * flDeltaTime) / m_flHorizSpeed;	// This is -dz/dx.
		VectorNormalize( vecForward );

		Vector vecUp;
		CrossProduct( vecForward, vecLeft, vecUp );
 
		initToWorld.SetBasisVectors( vecForward, vecLeft, vecUp );
	}
	else
	{
		flDeltaTime -= m_flWorldEnterTime;
		Vector vecVelocity;
		VectorMultiply( m_vecDirection, m_flFlightSpeed, vecVelocity );
		VectorMA( m_vecEnterWorldPosition, flDeltaTime, vecVelocity, vecPosition );

		MatrixFromAngles( m_vecStartAngles.Get(), initToWorld );
	}

	VMatrix rotation;
	MatrixBuildRotationAboutAxis( rotation, Vector( 1, 0, 0 ), flDeltaTime * ROTATION_SPEED );

	VMatrix newAngles;
	MatrixMultiply( initToWorld, rotation, newAngles );
	MatrixToAngles( newAngles, vecAngles );
}


//-----------------------------------------------------------------------------
// Are we in the skybox?
//-----------------------------------------------------------------------------
bool CEnvHeadcrabCanisterShared::IsInSkybox( )
{
	// Check to see if we are always in the world!
	return m_bInSkybox;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CEnvHeadcrabCanisterShared::CalcEnterTime( const Vector &vecTriggerMins, 
											  const Vector &vecTriggerMaxs )
{
	/*
#define HEADCRABCANISTER_TRIGGER_EPSILON	0.001f

	// Initialize the enter/exit fractions.
	float flEnterFrac = 0.0f;
	float flExitFrac = 1.0f;

	// Create an arbitrarily large end position.
	Vector vecEndPosition;
	VectorMA( m_vecStartPosition, 32000.0f, m_vecDirection, vecEndPosition );

	float flFrac, flDistStart, flDistEnd;
	for( int iAxis = 0; iAxis < 3; iAxis++ )
	{
		// Negative Axis
		flDistStart = -m_vecStartPosition[iAxis] + vecTriggerMins[iAxis];
		flDistEnd = -vecEndPosition[iAxis] + vecTriggerMins[iAxis];

		if ( ( flDistStart > 0.0f ) && ( flDistEnd < 0.0f ) ) 
		{ 
			flFrac = ( flDistStart - HEADCRABCANISTER_TRIGGER_EPSILON ) / ( flDistStart - flDistEnd );
			if ( flFrac > flEnterFrac ) { flEnterFrac = flFrac; }
		}

		if ( ( flDistStart < 0.0f ) && ( flDistEnd > 0.0f ) ) 
		{ 
			flFrac = ( flDistStart + HEADCRABCANISTER_TRIGGER_EPSILON ) / ( flDistStart - flDistEnd );
			if( flFrac < flExitFrac ) { flExitFrac = flFrac; }
		}

		if ( ( flDistStart > 0.0f ) && ( flDistEnd > 0.0f ) )
			return;

		// Positive Axis
		flDistStart = m_vecStartPosition[iAxis] - vecTriggerMaxs[iAxis];
		flDistEnd = vecEndPosition[iAxis] - vecTriggerMaxs[iAxis];

		if ( ( flDistStart > 0.0f ) && ( flDistEnd < 0.0f ) ) 
		{ 
			flFrac = ( flDistStart - HEADCRABCANISTER_TRIGGER_EPSILON ) / ( flDistStart - flDistEnd );
			if ( flFrac > flEnterFrac ) { flEnterFrac = flFrac; }
		}

		if ( ( flDistStart < 0.0f ) && ( flDistEnd > 0.0f ) ) 
		{ 
			flFrac = ( flDistStart + HEADCRABCANISTER_TRIGGER_EPSILON ) / ( flDistStart - flDistEnd );
			if( flFrac < flExitFrac ) { flExitFrac = flFrac; }
		}

		if ( ( flDistStart > 0.0f ) && ( flDistEnd > 0.0f ) )
			return;
	}

	// Check for intersection.
	if ( flExitFrac >= flEnterFrac )
	{
		// Check to see if we start in the world or the skybox!
		if ( flEnterFrac == 0.0f )
		{
			m_nLocation = HEADCRABCANISTER_LOCATION_WORLD;
		}
		else
		{
			m_nLocation = HEADCRABCANISTER_LOCATION_SKYBOX;
		}

		// Calculate the enter/exit times.
		Vector vecEnterPoint, vecExitPoint, vecDeltaPosition;
		VectorSubtract( vecEndPosition, m_vecStartPosition, vecDeltaPosition );
		VectorScale( vecDeltaPosition, flEnterFrac, vecEnterPoint );
		VectorScale( vecDeltaPosition, flExitFrac, vecExitPoint );

		m_flWorldEnterTime = vecEnterPoint.Length() / m_flFlightSpeed;
		m_flWorldEnterTime += m_flLaunchTime;
	}
	*/

#undef HEADCRABCANISTER_TRIGGER_EPSILON
}