You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
12 KiB
468 lines
12 KiB
//========= 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 ); |
|
} |
|
|
|
|