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

#ifndef PHYSICS_OBJECT_H
#define PHYSICS_OBJECT_H

#ifdef _WIN32
#pragma once
#endif

#include "vphysics_interface.h"

class IVP_Real_Object;
class IVP_Environment;
class IVP_U_Float_Point;
class IVP_SurfaceManager;
class IVP_Controller;
class CPhysicsEnvironment;
struct vphysics_save_cphysicsobject_t
{
	const CPhysCollide *pCollide;
	const char *pName;
	float	sphereRadius;

	bool	isStatic;
	bool	collisionEnabled;
	bool	gravityEnabled;
	bool	dragEnabled;
	bool	motionEnabled;
	bool	isAsleep;
	bool	isTrigger;
	bool	asleepSinceCreation;		// has this been asleep since creation?
	bool	hasTouchedDynamic;
	bool	hasShadowController;
	short	collideType;
	unsigned short	gameIndex;
	int		hingeAxis;
	int		materialIndex;
	float	mass;
	Vector	rotInertia;
	float	speedDamping;
	float	rotSpeedDamping;
	Vector	massCenterOverride;

	unsigned int	callbacks;
	unsigned int	gameFlags;

	unsigned int	contentsMask;

	float			volume;
	float			dragCoefficient;
	float			angDragCoefficient;
	IPhysicsShadowController	*pShadow;
	//bool			m_shadowTempGravityDisable;

	Vector			origin;
	QAngle			angles;
	Vector			velocity;
	AngularImpulse	angVelocity;

	DECLARE_SIMPLE_DATADESC();
};

enum
{
	OBJ_AWAKE = 0,			// awake, simulating
	OBJ_STARTSLEEP = 1,		// going to sleep, but not queried yet
	OBJ_SLEEP = 2,			// sleeping, no state changes since last query
};


class CPhysicsObject : public IPhysicsObject
{
public:
	CPhysicsObject( void );
	virtual ~CPhysicsObject( void );

	void			Init( const CPhysCollide *pCollisionModel, IVP_Real_Object *pObject, int materialIndex, float volume, float drag, float angDrag );

	// IPhysicsObject functions
	bool			IsStatic() const;
	bool			IsAsleep() const;
	bool			IsTrigger() const;
	bool			IsFluid() const;
	bool			IsHinged() const { return (m_hingedAxis != 0) ? true : false; }
	bool			IsCollisionEnabled() const;
	bool			IsGravityEnabled() const;
	bool			IsDragEnabled() const;
	bool			IsMotionEnabled() const;
	bool			IsMoveable() const;
	bool			IsAttachedToConstraint( bool bExternalOnly ) const;


	void			EnableCollisions( bool enable );
	// Enable / disable gravity for this object
	void			EnableGravity( bool enable );
	// Enable / disable air friction / drag for this object
	void			EnableDrag( bool enable );
	void			EnableMotion( bool enable );

	void			SetGameData( void *pAppData );
	void			*GetGameData( void ) const;
	void			SetCallbackFlags( unsigned short callbackflags );
	unsigned short	GetCallbackFlags( void ) const;
	void			SetGameFlags( unsigned short userFlags );
	unsigned short	GetGameFlags( void ) const;
	void			SetGameIndex( unsigned short gameIndex );
	unsigned short	GetGameIndex( void ) const;

	void			Wake();
	void			WakeNow();
	void			Sleep();
	void			RecheckCollisionFilter();
	void			RecheckContactPoints();

	void			SetMass( float mass );
	float			GetMass( void ) const;
	float			GetInvMass( void ) const;
	void			SetInertia( const Vector &inertia );
	Vector			GetInertia( void ) const;
	Vector			GetInvInertia( void ) const;

	void			GetDamping( float *speed, float *rot ) const;
	void			SetDamping( const float *speed, const float *rot );
	void			SetDragCoefficient( float *pDrag, float *pAngularDrag );
	void			SetBuoyancyRatio( float ratio );
	int				GetMaterialIndex() const { return GetMaterialIndexInternal(); }
	void			SetMaterialIndex( int materialIndex );
	inline int		GetMaterialIndexInternal( void ) const { return m_materialIndex; }

	unsigned int	GetContents() const { return m_contentsMask; }
	void			SetContents( unsigned int contents );

	float			GetSphereRadius() const;
	Vector			GetMassCenterLocalSpace() const;
	float			GetEnergy() const;

	void			SetPosition( const Vector &worldPosition, const QAngle &angles, bool isTeleport = false );
	void			SetPositionMatrix( const matrix3x4_t& matrix, bool isTeleport = false  );
	void			GetPosition( Vector *worldPosition, QAngle *angles ) const;
	void			GetPositionMatrix( matrix3x4_t *positionMatrix ) const;

	void			SetVelocity( const Vector *velocity, const AngularImpulse *angularVelocity );
	void			SetVelocityInstantaneous( const Vector *velocity, const AngularImpulse *angularVelocity );
	void			AddVelocity( const Vector *velocity, const AngularImpulse *angularVelocity );
	void			GetVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const;
	void			GetImplicitVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const;
	void			GetVelocityAtPoint( const Vector &worldPosition, Vector *pVelocity ) const;

	void			LocalToWorld( Vector *worldPosition, const Vector &localPosition ) const;
	void			WorldToLocal( Vector *localPosition, const Vector &worldPosition ) const;
	void			LocalToWorldVector( Vector *worldVector, const Vector &localVector ) const;
	void			WorldToLocalVector( Vector *localVector, const Vector &worldVector ) const;

	void			ApplyForceCenter( const Vector &forceVector );
	void			ApplyForceOffset( const Vector &forceVector, const Vector &worldPosition );
	void			ApplyTorqueCenter( const AngularImpulse & );
	void			CalculateForceOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerForce, AngularImpulse *centerTorque ) const;
	void			CalculateVelocityOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerVelocity, AngularImpulse *centerAngularVelocity ) const;
	float			CalculateLinearDrag( const Vector &unitDirection ) const;
	float			CalculateAngularDrag( const Vector &objectSpaceRotationAxis ) const;

	bool			GetContactPoint( Vector *contactPoint, IPhysicsObject **contactObject ) const;
	void			SetShadow( float maxSpeed, float maxAngularSpeed, bool allowPhysicsMovement, bool allowPhysicsRotation );
	void			UpdateShadow( const Vector &targetPosition, const QAngle &targetAngles, bool tempDisableGravity, float timeOffset );
	void			RemoveShadowController();
	int				GetShadowPosition( Vector *position, QAngle *angles ) const;
	IPhysicsShadowController *GetShadowController( void ) const;
	float			ComputeShadowControl( const hlshadowcontrol_params_t &params, float secondsToArrival, float dt );

	const CPhysCollide	*GetCollide( void ) const;
	char const		*GetName() const;

	float			GetDragInDirection( const IVP_U_Float_Point &dir ) const;
	float			GetAngularDragInDirection( const IVP_U_Float_Point &angVelocity ) const;
	void			BecomeTrigger();
	void			RemoveTrigger();
	void			BecomeHinged( int localAxis );
	void			RemoveHinged();

	IPhysicsFrictionSnapshot *CreateFrictionSnapshot();
	void			DestroyFrictionSnapshot( IPhysicsFrictionSnapshot *pSnapshot );

	void			OutputDebugInfo() const;

	// local functions
	inline	IVP_Real_Object *GetObject( void ) const { return m_pObject; }
	inline int		CallbackFlags( void ) const { return m_callbacks; }
	inline void		AddCallbackFlags( unsigned short flags ) { m_callbacks |= flags; }
	inline void		RemoveCallbackFlags( unsigned short flags ) { m_callbacks &= ~flags; }
	inline bool		HasTouchedDynamic();
	inline void		SetTouchedDynamic();
	void			NotifySleep( void );
	void			NotifyWake( void );
	int				GetSleepState( void ) const { return m_sleepState; }
	inline void		ForceSilentDelete() { m_forceSilentDelete = true; }

	inline int		GetActiveIndex( void ) const { return m_activeIndex; }
	inline void		SetActiveIndex( int index ) { m_activeIndex = index; }
	inline float	GetBuoyancyRatio( void ) const { return m_buoyancyRatio; }
	// returns true if the mass center is set to the default for the collision model
	bool			IsMassCenterAtDefault() const;

	// is this object simulated, or controlled by game logic?
	bool			IsControlledByGame() const;

	IVP_SurfaceManager *GetSurfaceManager( void ) const;

	void			WriteToTemplate( vphysics_save_cphysicsobject_t &objectTemplate );
	void			InitFromTemplate( CPhysicsEnvironment *pEnvironment, void *pGameData, const vphysics_save_cphysicsobject_t &objectTemplate );

	CPhysicsEnvironment	*GetVPhysicsEnvironment();
	const CPhysicsEnvironment	*GetVPhysicsEnvironment() const;

private:
	// NOTE: Local to vphysics, used to save/restore shadow controller
	void			RestoreShadowController( IPhysicsShadowController *pShadowController );
	friend bool		RestorePhysicsObject( const physrestoreparams_t &params, CPhysicsObject **ppObject );

	bool			IsControlling( const IVP_Controller *pController ) const;
	float			GetVolume() const;
	void			SetVolume( float volume );
	
	// the mass has changed, recompute the drag information
	void			RecomputeDragBases();

	void			ClampVelocity();

	// NOTE: If m_pGameData is not the first member, the constructor debug code must be modified
	void			*m_pGameData;
	IVP_Real_Object	*m_pObject;
	const CPhysCollide *m_pCollide;
	IPhysicsShadowController	*m_pShadow;

	Vector			m_dragBasis;
	Vector			m_angDragBasis;

	// these 5 should pack into a short
	// pack new bools here
	bool			m_shadowTempGravityDisable : 5;
	bool			m_hasTouchedDynamic : 1;
	bool			m_asleepSinceCreation : 1;
	bool			m_forceSilentDelete : 1;
	unsigned char	m_sleepState : 2;
	unsigned char	m_hingedAxis : 3;
	unsigned char	m_collideType : 3;
	unsigned short	m_gameIndex;

private:
	unsigned short	m_materialIndex;
	unsigned short	m_activeIndex;

	unsigned short	m_callbacks;
	unsigned short	m_gameFlags;
	unsigned int	m_contentsMask;
	
	float			m_volume;
	float			m_buoyancyRatio;
	float			m_dragCoefficient;
	float			m_angDragCoefficient;

	friend CPhysicsObject *CreatePhysicsObject( CPhysicsEnvironment *pEnvironment, const CPhysCollide *pCollisionModel, int materialIndex, const Vector &position, const QAngle& angles, objectparams_t *pParams, bool isStatic );
	friend bool CPhysicsEnvironment::TransferObject( IPhysicsObject *pObject, IPhysicsEnvironment *pDestinationEnvironment ); //need direct access to m_pShadow for Portal mod's physics object transfer system
};

// If you haven't ever touched a dynamic object, there's no need to search for contacting objects to 
// wakeup when you are deleted.  So cache a bit here when contacts are generated
inline bool CPhysicsObject::HasTouchedDynamic()
{
	return m_hasTouchedDynamic;
}

inline void CPhysicsObject::SetTouchedDynamic()
{
	m_hasTouchedDynamic = true;
}

extern CPhysicsObject *CreatePhysicsObject( CPhysicsEnvironment *pEnvironment, const CPhysCollide *pCollisionModel, int materialIndex, const Vector &position, const QAngle &angles, objectparams_t *pParams, bool isStatic );
extern CPhysicsObject *CreatePhysicsSphere( CPhysicsEnvironment *pEnvironment, float radius, int materialIndex, const Vector &position, const QAngle &angles, objectparams_t *pParams, bool isStatic );
extern void PostRestorePhysicsObject();
extern IPhysicsObject *CreateObjectFromBuffer( CPhysicsEnvironment *pEnvironment, void *pGameData, unsigned char *pBuffer, unsigned int bufferSize, bool enableCollisions );
extern IPhysicsObject *CreateObjectFromBuffer_UseExistingMemory( CPhysicsEnvironment *pEnvironment, void *pGameData, unsigned char *pBuffer, unsigned int bufferSize, CPhysicsObject *pExistingMemory );

#endif // PHYSICS_OBJECT_H