469 lines
13 KiB
C++
469 lines
13 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "physics_saverestore.h"
|
|
#include "vphysics/friction.h"
|
|
#include "ai_basenpc.h"
|
|
#include "movevars_shared.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
class CPhysicsNPCSolver : public CLogicalEntity, public IMotionEvent
|
|
{
|
|
DECLARE_CLASS( CPhysicsNPCSolver, CLogicalEntity );
|
|
public:
|
|
CPhysicsNPCSolver();
|
|
~CPhysicsNPCSolver();
|
|
DECLARE_DATADESC();
|
|
void Init( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject, bool disableCollisions, float separationTime );
|
|
static CPhysicsNPCSolver *Create( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject, bool disableCollisions, float separationTime );
|
|
|
|
// CBaseEntity
|
|
virtual void Spawn();
|
|
virtual void UpdateOnRemove();
|
|
virtual void Think();
|
|
virtual void OnRestore()
|
|
{
|
|
BaseClass::OnRestore();
|
|
if ( m_allowIntersection )
|
|
{
|
|
PhysDisableEntityCollisions( m_hNPC, m_hEntity );
|
|
}
|
|
}
|
|
|
|
// IMotionEvent
|
|
virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
|
|
|
|
public:
|
|
CPhysicsNPCSolver *m_pNext;
|
|
private:
|
|
// locals
|
|
void ResetCancelTime();
|
|
void BecomePenetrationSolver();
|
|
bool IsIntersecting();
|
|
bool IsContactOnNPCHead( IPhysicsFrictionSnapshot *pSnapshot, IPhysicsObject *pPhysics, CAI_BaseNPC *pNPC );
|
|
bool CheckTouching();
|
|
friend bool NPCPhysics_SolverExists( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject );
|
|
|
|
CHandle<CAI_BaseNPC> m_hNPC;
|
|
EHANDLE m_hEntity;
|
|
IPhysicsMotionController *m_pController;
|
|
float m_separationDuration;
|
|
float m_cancelTime;
|
|
bool m_allowIntersection;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( physics_npc_solver, CPhysicsNPCSolver );
|
|
|
|
BEGIN_DATADESC( CPhysicsNPCSolver )
|
|
|
|
DEFINE_FIELD( m_hNPC, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hEntity, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_separationDuration, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_cancelTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_allowIntersection, FIELD_BOOLEAN ),
|
|
DEFINE_PHYSPTR( m_pController ),
|
|
//DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ),
|
|
|
|
END_DATADESC()
|
|
|
|
CEntityClassList<CPhysicsNPCSolver> g_SolverList;
|
|
template <> CPhysicsNPCSolver *CEntityClassList<CPhysicsNPCSolver>::m_pClassList = NULL;
|
|
|
|
bool NPCPhysics_SolverExists( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject )
|
|
{
|
|
CPhysicsNPCSolver *pSolver = g_SolverList.m_pClassList;
|
|
while ( pSolver )
|
|
{
|
|
if ( pSolver->m_hEntity == pPhysicsObject && pSolver->m_hNPC == pNPC )
|
|
return true;
|
|
pSolver = pSolver->m_pNext;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CPhysicsNPCSolver *CPhysicsNPCSolver::Create( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject, bool disableCollisions, float separationTime )
|
|
{
|
|
CPhysicsNPCSolver *pSolver = (CPhysicsNPCSolver *)CBaseEntity::CreateNoSpawn( "physics_npc_solver", vec3_origin, vec3_angle, NULL );
|
|
pSolver->Init( pNPC, pPhysicsObject, disableCollisions, separationTime );
|
|
pSolver->Spawn();
|
|
//NDebugOverlay::EntityBounds(pNPC, 255, 255, 0, 64, 0.5f );
|
|
return pSolver;
|
|
}
|
|
|
|
CPhysicsNPCSolver::CPhysicsNPCSolver()
|
|
{
|
|
g_SolverList.Insert( this );
|
|
}
|
|
|
|
CPhysicsNPCSolver::~CPhysicsNPCSolver()
|
|
{
|
|
g_SolverList.Remove( this );
|
|
}
|
|
|
|
void CPhysicsNPCSolver::Init( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject, bool disableCollisions, float separationTime )
|
|
{
|
|
m_hNPC = pNPC;
|
|
m_hEntity = pPhysicsObject;
|
|
m_pController = NULL;
|
|
m_separationDuration = separationTime;
|
|
m_allowIntersection = disableCollisions;
|
|
|
|
}
|
|
|
|
void CPhysicsNPCSolver::ResetCancelTime()
|
|
{
|
|
m_cancelTime = gpGlobals->curtime + m_separationDuration;
|
|
SetNextThink( m_cancelTime );
|
|
}
|
|
|
|
void CPhysicsNPCSolver::BecomePenetrationSolver()
|
|
{
|
|
CBaseEntity *pEntity = m_hEntity.Get();
|
|
if ( pEntity )
|
|
{
|
|
m_allowIntersection = true;
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
PhysDisableEntityCollisions( m_hNPC, pEntity );
|
|
m_pController = physenv->CreateMotionController( this );
|
|
for ( int i = 0; i < listCount; i++ )
|
|
{
|
|
m_pController->AttachObject( pList[i], false );
|
|
pList[i]->Wake();
|
|
}
|
|
m_pController->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
|
|
}
|
|
}
|
|
|
|
void CPhysicsNPCSolver::Spawn()
|
|
{
|
|
if ( m_allowIntersection )
|
|
{
|
|
BecomePenetrationSolver();
|
|
}
|
|
else
|
|
{
|
|
m_hEntity->SetNavIgnore();
|
|
}
|
|
ResetCancelTime();
|
|
}
|
|
|
|
void CPhysicsNPCSolver::UpdateOnRemove()
|
|
{
|
|
if ( m_allowIntersection )
|
|
{
|
|
physenv->DestroyMotionController( m_pController );
|
|
m_pController = NULL;
|
|
PhysEnableEntityCollisions( m_hNPC, m_hEntity );
|
|
}
|
|
else
|
|
{
|
|
if ( m_hEntity.Get() )
|
|
{
|
|
m_hEntity->ClearNavIgnore();
|
|
}
|
|
}
|
|
//NDebugOverlay::EntityBounds(m_hNPC, 0, 255, 0, 64, 0.5f );
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
bool CPhysicsNPCSolver::IsIntersecting()
|
|
{
|
|
CAI_BaseNPC *pNPC = m_hNPC.Get();
|
|
CBaseEntity *pPhysics = m_hEntity.Get();
|
|
if ( pNPC && pPhysics )
|
|
{
|
|
Ray_t ray;
|
|
// bloated bounds to force slight separation
|
|
Vector mins = pNPC->WorldAlignMins() - Vector(1,1,1);
|
|
Vector maxs = pNPC->WorldAlignMaxs() + Vector(1,1,1);
|
|
|
|
ray.Init( pNPC->GetAbsOrigin(), pNPC->GetAbsOrigin(), mins, maxs );
|
|
trace_t tr;
|
|
enginetrace->ClipRayToEntity( ray, pNPC->PhysicsSolidMaskForEntity(), pPhysics, &tr );
|
|
if ( tr.startsolid )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPhysicsNPCSolver::IsContactOnNPCHead( IPhysicsFrictionSnapshot *pSnapshot, IPhysicsObject *pPhysics, CAI_BaseNPC *pNPC )
|
|
{
|
|
float heightCheck = pNPC->GetAbsOrigin().z + pNPC->GetHullMaxs().z;
|
|
Vector vel, point;
|
|
pPhysics->GetVelocity( &vel, NULL );
|
|
pSnapshot->GetContactPoint( point );
|
|
// don't care if the object is already moving away
|
|
if ( vel.LengthSqr() < 10.0f*10.0f )
|
|
{
|
|
float topdist = fabs(point.z-heightCheck);
|
|
if ( topdist < 2.0f )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPhysicsNPCSolver::CheckTouching()
|
|
{
|
|
CAI_BaseNPC *pNPC = m_hNPC.Get();
|
|
if ( !pNPC )
|
|
return false;
|
|
|
|
CBaseEntity *pPhysicsEnt = m_hEntity.Get();
|
|
if ( !pPhysicsEnt )
|
|
return false;
|
|
|
|
IPhysicsObject *pPhysics = pPhysicsEnt->VPhysicsGetObject();
|
|
IPhysicsObject *pNPCPhysics = pNPC->VPhysicsGetObject();
|
|
if ( !pNPCPhysics || !pPhysics )
|
|
return false;
|
|
|
|
IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
|
|
bool found = false;
|
|
bool penetrate = false;
|
|
|
|
while ( pSnapshot->IsValid() )
|
|
{
|
|
IPhysicsObject *pOther = pSnapshot->GetObject(1);
|
|
if ( pOther == pNPCPhysics )
|
|
{
|
|
found = true;
|
|
if ( IsContactOnNPCHead(pSnapshot, pPhysics, pNPC ) )
|
|
{
|
|
penetrate = true;
|
|
pSnapshot->MarkContactForDelete();
|
|
}
|
|
break;
|
|
}
|
|
pSnapshot->NextFrictionData();
|
|
}
|
|
pSnapshot->DeleteAllMarkedContacts( true );
|
|
pPhysics->DestroyFrictionSnapshot( pSnapshot );
|
|
|
|
// if the object is penetrating something, check to see if it's intersecting this NPC
|
|
// if so, go ahead and switch over to penetration solver mode
|
|
if ( !penetrate && (pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING) )
|
|
{
|
|
penetrate = IsIntersecting();
|
|
}
|
|
|
|
if ( penetrate )
|
|
{
|
|
pPhysicsEnt->ClearNavIgnore();
|
|
BecomePenetrationSolver();
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
void CPhysicsNPCSolver::Think()
|
|
{
|
|
bool finished = m_allowIntersection ? !IsIntersecting() : !CheckTouching();
|
|
|
|
if ( finished )
|
|
{
|
|
UTIL_Remove(this);
|
|
return;
|
|
}
|
|
if ( m_allowIntersection )
|
|
{
|
|
IPhysicsObject *pObject = m_hEntity->VPhysicsGetObject();
|
|
if ( !pObject )
|
|
{
|
|
UTIL_Remove(this);
|
|
return;
|
|
}
|
|
pObject->Wake();
|
|
}
|
|
ResetCancelTime();
|
|
}
|
|
|
|
IMotionEvent::simresult_e CPhysicsNPCSolver::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject,
|
|
float deltaTime, Vector &linear, AngularImpulse &angular )
|
|
{
|
|
if ( IsIntersecting() )
|
|
{
|
|
const float PUSH_SPEED = 150.0f;
|
|
|
|
if ( pObject->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->ForceDropOfCarriedPhysObjects( m_hEntity );
|
|
}
|
|
}
|
|
|
|
ResetCancelTime();
|
|
angular.Init();
|
|
linear.Init();
|
|
|
|
// Don't push on vehicles because they won't move
|
|
if ( pObject->GetGameFlags() & FVPHYSICS_MULTIOBJECT_ENTITY )
|
|
{
|
|
if ( m_hEntity->GetServerVehicle() )
|
|
return SIM_NOTHING;
|
|
}
|
|
|
|
Vector origin, vel;
|
|
pObject->GetPosition( &origin, NULL );
|
|
pObject->GetVelocity( &vel, NULL );
|
|
Vector dir = origin - m_hNPC->GetAbsOrigin();
|
|
dir.z = dir.z > 0 ? 0.1f : -0.1f;
|
|
VectorNormalize(dir);
|
|
AngularImpulse angVel;
|
|
angVel.Init();
|
|
|
|
// NOTE: Iterate this object's contact points
|
|
// if it can't move in this direction, try sliding along the plane/crease
|
|
Vector pushImpulse;
|
|
PhysComputeSlideDirection( pObject, dir * PUSH_SPEED, angVel, &pushImpulse, NULL, 0 );
|
|
|
|
dir = pushImpulse;
|
|
VectorNormalize(dir);
|
|
|
|
if ( DotProduct( vel, dir ) < PUSH_SPEED * 0.5f )
|
|
{
|
|
linear = pushImpulse;
|
|
if ( pObject->GetContactPoint(NULL,NULL) )
|
|
{
|
|
linear.z += sv_gravity.GetFloat();
|
|
}
|
|
}
|
|
return SIM_GLOBAL_ACCELERATION;
|
|
}
|
|
return SIM_NOTHING;
|
|
}
|
|
|
|
|
|
CBaseEntity *NPCPhysics_CreateSolver( CAI_BaseNPC *pNPC, CBaseEntity *pPhysicsObject, bool disableCollisions, float separationDuration )
|
|
{
|
|
if ( disableCollisions )
|
|
{
|
|
if ( PhysEntityCollisionsAreDisabled( pNPC, pPhysicsObject ) )
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
if ( pPhysicsObject->IsNavIgnored() )
|
|
return NULL;
|
|
}
|
|
return CPhysicsNPCSolver::Create( pNPC, pPhysicsObject, disableCollisions, separationDuration );
|
|
}
|
|
|
|
|
|
class CPhysicsEntitySolver : public CLogicalEntity//, public IMotionEvent
|
|
{
|
|
DECLARE_CLASS( CPhysicsEntitySolver, CLogicalEntity );
|
|
public:
|
|
DECLARE_DATADESC();
|
|
void Init( CBaseEntity *pMovingEntity, CBaseEntity *pPhysicsBlocker, float separationTime );
|
|
static CPhysicsEntitySolver *Create( CBaseEntity *pMovingEntity, CBaseEntity *pPhysicsBlocker, float separationTime );
|
|
|
|
// CBaseEntity
|
|
virtual void Spawn();
|
|
virtual void UpdateOnRemove();
|
|
virtual void Think();
|
|
|
|
// IMotionEvent
|
|
//virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
|
|
|
|
private:
|
|
// locals
|
|
void ResetCancelTime();
|
|
void BecomePenetrationSolver();
|
|
//bool IsIntersecting();
|
|
//bool IsTouching();
|
|
|
|
EHANDLE m_hMovingEntity;
|
|
EHANDLE m_hPhysicsBlocker;
|
|
//IPhysicsMotionController *m_pController;
|
|
float m_separationDuration;
|
|
float m_cancelTime;
|
|
int m_savedCollisionGroup;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( physics_entity_solver, CPhysicsEntitySolver );
|
|
|
|
BEGIN_DATADESC( CPhysicsEntitySolver )
|
|
|
|
DEFINE_FIELD( m_hMovingEntity, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hPhysicsBlocker, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_separationDuration, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_cancelTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_savedCollisionGroup, FIELD_INTEGER ),
|
|
//DEFINE_PHYSPTR( m_pController ),
|
|
|
|
END_DATADESC()
|
|
|
|
CPhysicsEntitySolver *CPhysicsEntitySolver::Create( CBaseEntity *pMovingEntity, CBaseEntity *pPhysicsBlocker, float separationTime )
|
|
{
|
|
CPhysicsEntitySolver *pSolver = (CPhysicsEntitySolver *)CBaseEntity::CreateNoSpawn( "physics_entity_solver", vec3_origin, vec3_angle, NULL );
|
|
pSolver->Init( pMovingEntity, pPhysicsBlocker, separationTime );
|
|
pSolver->Spawn();
|
|
//NDebugOverlay::EntityBounds(pNPC, 255, 255, 0, 64, 0.5f );
|
|
return pSolver;
|
|
}
|
|
|
|
void CPhysicsEntitySolver::Init( CBaseEntity *pMovingEntity, CBaseEntity *pPhysicsBlocker, float separationTime )
|
|
{
|
|
m_hMovingEntity = pMovingEntity;
|
|
m_hPhysicsBlocker = pPhysicsBlocker;
|
|
//m_pController = NULL;
|
|
m_separationDuration = separationTime;
|
|
}
|
|
|
|
void CPhysicsEntitySolver::Spawn()
|
|
{
|
|
SetNextThink( gpGlobals->curtime + m_separationDuration );
|
|
PhysDisableEntityCollisions( m_hMovingEntity, m_hPhysicsBlocker );
|
|
m_savedCollisionGroup = m_hPhysicsBlocker->GetCollisionGroup();
|
|
m_hPhysicsBlocker->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
|
|
if ( m_hPhysicsBlocker->VPhysicsGetObject() )
|
|
{
|
|
m_hPhysicsBlocker->VPhysicsGetObject()->RecheckContactPoints();
|
|
}
|
|
}
|
|
|
|
void CPhysicsEntitySolver::Think()
|
|
{
|
|
UTIL_Remove(this);
|
|
}
|
|
|
|
void CPhysicsEntitySolver::UpdateOnRemove()
|
|
{
|
|
//physenv->DestroyMotionController( m_pController );
|
|
//m_pController = NULL;
|
|
CBaseEntity *pEntity = m_hMovingEntity.Get();
|
|
CBaseEntity *pPhysics = m_hPhysicsBlocker.Get();
|
|
if ( pEntity && pPhysics )
|
|
{
|
|
PhysEnableEntityCollisions( pEntity, pPhysics );
|
|
}
|
|
if ( pPhysics )
|
|
{
|
|
pPhysics->SetCollisionGroup( m_savedCollisionGroup );
|
|
}
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
|
|
CBaseEntity *EntityPhysics_CreateSolver( CBaseEntity *pMovingEntity, CBaseEntity *pPhysicsObject, bool disableCollisions, float separationDuration )
|
|
{
|
|
if ( PhysEntityCollisionsAreDisabled( pMovingEntity, pPhysicsObject ) )
|
|
return NULL;
|
|
|
|
return CPhysicsEntitySolver::Create( pMovingEntity, pPhysicsObject, separationDuration );
|
|
}
|
|
|