//========= 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 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 g_SolverList; template <> CPhysicsNPCSolver *CEntityClassList::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 ); }