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

#ifndef COLLISIONPROPERTY_H
#define COLLISIONPROPERTY_H
#ifdef _WIN32
#pragma once
#endif

#include "networkvar.h"
#include "engine/ICollideable.h"
#include "mathlib/vector.h"
#include "ispatialpartition.h"


//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
class CBaseEntity;
class IHandleEntity;
class QAngle;
class Vector;
struct Ray_t;
class IPhysicsObject;


//-----------------------------------------------------------------------------
// Force spatial partition updates (to avoid threading problems caused by lazy update)
//-----------------------------------------------------------------------------
void UpdateDirtySpatialPartitionEntities();


//-----------------------------------------------------------------------------
// Specifies how to compute the surrounding box
//-----------------------------------------------------------------------------
enum SurroundingBoundsType_t
{
	USE_OBB_COLLISION_BOUNDS = 0,
	USE_BEST_COLLISION_BOUNDS,		// Always use the best bounds (most expensive)
	USE_HITBOXES,
	USE_SPECIFIED_BOUNDS,
	USE_GAME_CODE,
	USE_ROTATION_EXPANDED_BOUNDS,
	USE_COLLISION_BOUNDS_NEVER_VPHYSICS,

	SURROUNDING_TYPE_BIT_COUNT = 3
};


//-----------------------------------------------------------------------------
// Encapsulates collision representation for an entity
//-----------------------------------------------------------------------------
class CCollisionProperty : public ICollideable
{
	DECLARE_CLASS_NOBASE( CCollisionProperty );
	DECLARE_EMBEDDED_NETWORKVAR();
	DECLARE_PREDICTABLE();

#ifdef GAME_DLL
	DECLARE_DATADESC();
#endif

public:
	CCollisionProperty();
	~CCollisionProperty();

	void Init( CBaseEntity *pEntity );

	// Methods of ICollideable
	virtual IHandleEntity	*GetEntityHandle();
 	virtual const Vector&	OBBMinsPreScaled() const { return m_vecMinsPreScaled.Get(); }
	virtual const Vector&	OBBMaxsPreScaled() const { return m_vecMaxsPreScaled.Get(); }
	virtual const Vector&	OBBMins() const { return m_vecMins.Get(); }
	virtual const Vector&	OBBMaxs() const { return m_vecMaxs.Get(); }
	virtual void			WorldSpaceTriggerBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) const;
	virtual bool			TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
	virtual bool			TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
	virtual int				GetCollisionModelIndex();
	virtual const model_t*	GetCollisionModel();
	virtual const Vector&	GetCollisionOrigin() const;
	virtual const QAngle&	GetCollisionAngles() const;
	virtual const matrix3x4_t&	CollisionToWorldTransform() const;
	virtual SolidType_t		GetSolid() const;
	virtual int				GetSolidFlags() const;
	virtual IClientUnknown*	GetIClientUnknown();
	virtual int				GetCollisionGroup() const;
	virtual void			WorldSpaceSurroundingBounds( Vector *pVecMins, Vector *pVecMaxs );
	virtual bool			ShouldTouchTrigger( int triggerSolidFlags ) const;
	virtual const matrix3x4_t *GetRootParentToWorldTransform() const;

public:
	// Spatial partition management
	void			CreatePartitionHandle();
	void			DestroyPartitionHandle();
	unsigned short	GetPartitionHandle() const;

	// Marks the spatial partition dirty
	void			MarkPartitionHandleDirty();

	// Sets the collision bounds + the size (OBB)
	void			SetCollisionBounds( const Vector& mins, const Vector &maxs );

	// Rebuilds the scaled bounds from the pre-scaled bounds after a model's scale has changed
	void			RefreshScaledCollisionBounds( void );

	// Sets special trigger bounds. The bloat amount indicates how much bigger the 
	// trigger bounds should be beyond the bounds set in SetCollisionBounds
	// This method will also set the FSOLID flag FSOLID_USE_TRIGGER_BOUNDS
	void			UseTriggerBounds( bool bEnable, float flBloat = 0.0f );

	// Sets the method by which the surrounding collision bounds is set
	// You must pass in values for mins + maxs if you select the USE_SPECIFIED_BOUNDS type. 
	void			SetSurroundingBoundsType( SurroundingBoundsType_t type, const Vector *pMins = NULL, const Vector *pMaxs = NULL );

	// Sets the solid type (which type of collision representation)
	void			SetSolid( SolidType_t val );

	// Methods related to size. The OBB here is measured in CollisionSpace
	// (specified by GetCollisionToWorld)
	const Vector&	OBBSize( ) const;

	// Returns a radius (or the square of the radius) of a sphere 
	// *centered at the world space center* bounding the collision representation 
	// of the entity. NOTE: The world space center *may* move when the entity rotates.
	float			BoundingRadius() const;
	float			BoundingRadius2D() const;

	// Returns the center of the OBB in collision space
	const Vector &	OBBCenter( ) const;

	// center point of entity measured in world space
	// NOTE: This point *may* move when the entity moves depending on
	// which solid type is being used.
	const Vector &	WorldSpaceCenter( ) const;

	// Methods related to solid flags
	void			ClearSolidFlags( void );	
	void			RemoveSolidFlags( int flags );
	void			AddSolidFlags( int flags );
	bool			IsSolidFlagSet( int flagMask ) const;
	void		 	SetSolidFlags( int flags );
	bool			IsSolid() const;

	// Updates the spatial partition
	void			UpdatePartition( );

	// Are the bounds defined in entity space?
	bool			IsBoundsDefinedInEntitySpace() const;

	// Transforms a point in OBB space to world space
	const Vector &	CollisionToWorldSpace( const Vector &in, Vector *pResult ) const;

	// Transforms a point in world space to OBB space
	const Vector &	WorldToCollisionSpace( const Vector &in, Vector *pResult ) const;

	// Transforms a direction in world space to OBB space
	const Vector &	WorldDirectionToCollisionSpace( const Vector &in, Vector *pResult ) const;

	// Selects a random point in the bounds given the normalized 0-1 bounds 
	void			RandomPointInBounds( const Vector &vecNormalizedMins, const Vector &vecNormalizedMaxs, Vector *pPoint) const;

	// Is a worldspace point within the bounds of the OBB?
	bool			IsPointInBounds( const Vector &vecWorldPt ) const;

	// Computes a bounding box in world space surrounding the collision bounds
	void			WorldSpaceAABB( Vector *pWorldMins, Vector *pWorldMaxs ) const;

	// Get the collision space mins directly
	const Vector &	CollisionSpaceMins( void ) const;

	// Get the collision space maxs directly
	const Vector &	CollisionSpaceMaxs( void ) const;

	// Computes a "normalized" point (range 0,0,0 - 1,1,1) in collision space
	// Useful for things like getting a point 75% of the way along z on the OBB, for example
	const Vector &	NormalizedToCollisionSpace( const Vector &in, Vector *pResult ) const;

	// Computes a "normalized" point (range 0,0,0 - 1,1,1) in world space
	const Vector &	NormalizedToWorldSpace( const Vector &in, Vector *pResult ) const;

	// Transforms a point in world space to normalized space
	const Vector &	WorldToNormalizedSpace( const Vector &in, Vector *pResult ) const;

	// Transforms a point in collision space to normalized space
	const Vector &	CollisionToNormalizedSpace( const Vector &in, Vector *pResult ) const;

	// Computes the nearest point in the OBB to a point specified in world space
	void			CalcNearestPoint( const Vector &vecWorldPt, Vector *pVecNearestWorldPt ) const;

	// Computes the distance from a point in world space to the OBB
	float			CalcDistanceFromPoint( const Vector &vecWorldPt ) const;

	// Does a rotation make us need to recompute the surrounding box?
	bool			DoesRotationInvalidateSurroundingBox( ) const;

	// Does VPhysicsUpdate make us need to recompute the surrounding box?
	bool			DoesVPhysicsInvalidateSurroundingBox( ) const;

	// Marks the entity has having a dirty surrounding box
	void			MarkSurroundingBoundsDirty();

	// Compute the largest dot product of the OBB and the specified direction vector
	float			ComputeSupportMap( const Vector &vecDirection ) const;

private:
	// Transforms an AABB measured in collision space to a box that surrounds it in world space
	void CollisionAABBToWorldAABB( const Vector &entityMins, const Vector &entityMaxs, Vector *pWorldMins, Vector *pWorldMaxs ) const;

	// Expand trigger bounds..
	void ComputeVPhysicsSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs );

	// Expand trigger bounds..
	bool ComputeHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs );
	bool ComputeEntitySpaceHitboxSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs );

	// Computes the surrounding collision bounds based on whatever algorithm we want...
	void ComputeCollisionSurroundingBox( bool bUseVPhysics, Vector *pVecWorldMins, Vector *pVecWorldMaxs );

	// Computes the surrounding collision bounds from the the OBB (not vphysics)
	void ComputeRotationExpandedBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs );

	// Computes the surrounding collision bounds based on whatever algorithm we want...
	void ComputeSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs );

	// Check for untouch
	void CheckForUntouch();

	// Updates the spatial partition
	void UpdateServerPartitionMask( );

	// Outer
	CBaseEntity *GetOuter();
	const CBaseEntity *GetOuter() const;

private:
	CBaseEntity *m_pOuter;

	CNetworkVector( m_vecMinsPreScaled );
	CNetworkVector( m_vecMaxsPreScaled );
	CNetworkVector( m_vecMins );
	CNetworkVector( m_vecMaxs );
	float m_flRadius;

	CNetworkVar( unsigned short, m_usSolidFlags );

	// Spatial partition
	SpatialPartitionHandle_t m_Partition;
	CNetworkVar( unsigned char, m_nSurroundType );

	// One of the SOLID_ defines. Use GetSolid/SetSolid.
	CNetworkVar( unsigned char, m_nSolidType );			
	CNetworkVar( unsigned char , m_triggerBloat );

	// SUCKY: We didn't use to have to store this previously
	// but storing it here means that we can network it + avoid a ton of
	// client-side mismatch problems
	CNetworkVector( m_vecSpecifiedSurroundingMinsPreScaled );
	CNetworkVector( m_vecSpecifiedSurroundingMaxsPreScaled );
	CNetworkVector( m_vecSpecifiedSurroundingMins );
	CNetworkVector( m_vecSpecifiedSurroundingMaxs );

	// Cached off world-aligned surrounding bounds
#if 0
	short	m_surroundingMins[3];
	short	m_surroundingMaxs[3];
#else
	Vector	m_vecSurroundingMins;
	Vector	m_vecSurroundingMaxs;
#endif

	// pointer to the entity's physics object (vphysics.dll)
	//IPhysicsObject	*m_pPhysicsObject;
	
	friend class CBaseEntity;
};


//-----------------------------------------------------------------------------
// For networking this bad boy
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
EXTERN_RECV_TABLE( DT_CollisionProperty );
#else
EXTERN_SEND_TABLE( DT_CollisionProperty );
#endif


//-----------------------------------------------------------------------------
// Inline methods
//-----------------------------------------------------------------------------
inline CBaseEntity *CCollisionProperty::GetOuter()
{
	return m_pOuter;
}

inline const CBaseEntity *CCollisionProperty::GetOuter() const
{
	return m_pOuter;
}


//-----------------------------------------------------------------------------
// Spatial partition
//-----------------------------------------------------------------------------
inline unsigned short CCollisionProperty::GetPartitionHandle() const
{
	return m_Partition;
}


//-----------------------------------------------------------------------------
// Methods related to size
//-----------------------------------------------------------------------------
inline const Vector& CCollisionProperty::OBBSize( ) const
{
	// NOTE: Could precache this, but it's not used that often..
	Vector &temp = AllocTempVector();
	VectorSubtract( m_vecMaxs, m_vecMins, temp );
	return temp;
}


//-----------------------------------------------------------------------------
// Bounding radius size
//-----------------------------------------------------------------------------
inline float CCollisionProperty::BoundingRadius() const
{
	return m_flRadius;
}


//-----------------------------------------------------------------------------
// Methods relating to solid flags
//-----------------------------------------------------------------------------
inline bool CCollisionProperty::IsBoundsDefinedInEntitySpace() const
{
	return (( m_usSolidFlags & FSOLID_FORCE_WORLD_ALIGNED ) == 0 ) &&
			( m_nSolidType != SOLID_BBOX ) && ( m_nSolidType != SOLID_NONE );
}

inline void CCollisionProperty::ClearSolidFlags( void )
{
	SetSolidFlags( 0 );
}

inline void CCollisionProperty::RemoveSolidFlags( int flags )
{
	SetSolidFlags( m_usSolidFlags & ~flags );
}

inline void CCollisionProperty::AddSolidFlags( int flags )
{
	SetSolidFlags( m_usSolidFlags | flags );
}

inline int CCollisionProperty::GetSolidFlags( void ) const
{
	return m_usSolidFlags;
}

inline bool CCollisionProperty::IsSolidFlagSet( int flagMask ) const
{
	return (m_usSolidFlags & flagMask) != 0;
}

inline bool CCollisionProperty::IsSolid() const
{
	return ::IsSolid( (SolidType_t)(unsigned char)m_nSolidType, m_usSolidFlags );
}


//-----------------------------------------------------------------------------
// Returns the center in OBB space
//-----------------------------------------------------------------------------
inline const Vector& CCollisionProperty::OBBCenter( ) const
{
	Vector &vecResult = AllocTempVector();
	VectorLerp( m_vecMins, m_vecMaxs, 0.5f, vecResult );
	return vecResult;
}


//-----------------------------------------------------------------------------
// center point of entity
//-----------------------------------------------------------------------------
inline const Vector &CCollisionProperty::WorldSpaceCenter( ) const 
{
	Vector &vecResult = AllocTempVector();
	CollisionToWorldSpace( OBBCenter(), &vecResult );
	return vecResult;
}


//-----------------------------------------------------------------------------
// Transforms a point in OBB space to world space
//-----------------------------------------------------------------------------
inline const Vector &CCollisionProperty::CollisionToWorldSpace( const Vector &in, Vector *pResult ) const 
{
	// Makes sure we don't re-use the same temp twice
	if ( !IsBoundsDefinedInEntitySpace() || ( GetCollisionAngles() == vec3_angle ) )
	{
		VectorAdd( in, GetCollisionOrigin(), *pResult );
	}
	else
	{
		VectorTransform( in, CollisionToWorldTransform(), *pResult );
	}
	return *pResult;
}


//-----------------------------------------------------------------------------
// Transforms a point in world space to OBB space
//-----------------------------------------------------------------------------
inline const Vector &CCollisionProperty::WorldToCollisionSpace( const Vector &in, Vector *pResult ) const
{
	if ( !IsBoundsDefinedInEntitySpace() || ( GetCollisionAngles() == vec3_angle ) )
	{
		VectorSubtract( in, GetCollisionOrigin(), *pResult );
	}
	else
	{
		VectorITransform( in, CollisionToWorldTransform(), *pResult );
	}
	return *pResult;
}


//-----------------------------------------------------------------------------
// Transforms a direction in world space to OBB space
//-----------------------------------------------------------------------------
inline const Vector & CCollisionProperty::WorldDirectionToCollisionSpace( const Vector &in, Vector *pResult ) const
{
	if ( !IsBoundsDefinedInEntitySpace() || ( GetCollisionAngles() == vec3_angle ) )
	{
		*pResult = in;
	}
	else
	{
		VectorIRotate( in, CollisionToWorldTransform(), *pResult );
	}
	return *pResult;
}


//-----------------------------------------------------------------------------
// Computes a bounding box in world space surrounding the collision bounds
//-----------------------------------------------------------------------------
inline void CCollisionProperty::WorldSpaceAABB( Vector *pWorldMins, Vector *pWorldMaxs ) const
{
	CollisionAABBToWorldAABB( m_vecMins, m_vecMaxs, pWorldMins, pWorldMaxs );
}


// Get the collision space mins directly
inline const Vector & CCollisionProperty::CollisionSpaceMins( void ) const
{
	return m_vecMins;
}

// Get the collision space maxs directly
inline const Vector & CCollisionProperty::CollisionSpaceMaxs( void ) const
{
	return m_vecMaxs;
}


//-----------------------------------------------------------------------------
// Does a rotation make us need to recompute the surrounding box?
//-----------------------------------------------------------------------------
inline bool CCollisionProperty::DoesRotationInvalidateSurroundingBox( ) const
{
	if ( IsSolidFlagSet(FSOLID_ROOT_PARENT_ALIGNED) )
		return true;

	switch ( m_nSurroundType )
	{
	case USE_COLLISION_BOUNDS_NEVER_VPHYSICS:
	case USE_OBB_COLLISION_BOUNDS:
	case USE_BEST_COLLISION_BOUNDS:
		return IsBoundsDefinedInEntitySpace();

	// In the case of game code, we don't really know, so we have to assume it does
	case USE_HITBOXES:
	case USE_GAME_CODE:
		return true;

	case USE_ROTATION_EXPANDED_BOUNDS:
	case USE_SPECIFIED_BOUNDS:
		return false;

	default:
		Assert(0);
		return true;
	}
}


#endif // COLLISIONPROPERTY_H