mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-29 09:33:00 +00:00
1052 lines
30 KiB
C++
1052 lines
30 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "vcollide_parse.h"
|
|
#include "filesystem.h"
|
|
#include "engine/IStaticPropMgr.h"
|
|
#include "solidsetdefaults.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "vphysics_sound.h"
|
|
#include "movevars_shared.h"
|
|
#include "engine/ivmodelinfo.h"
|
|
#include "fx.h"
|
|
#include "tier0/vprof.h"
|
|
#include "c_world.h"
|
|
#include "vphysics/object_hash.h"
|
|
#include "vphysics/collision_set.h"
|
|
#include "soundenvelope.h"
|
|
#include "fx_water.h"
|
|
#include "positionwatcher.h"
|
|
#include "vphysics/constraints.h"
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// file system interface
|
|
extern IFileSystem *filesystem;
|
|
|
|
ConVar cl_phys_timescale( "cl_phys_timescale", "1.0", FCVAR_CHEAT, "Sets the scale of time for client-side physics (ragdolls)" );
|
|
|
|
void PrecachePhysicsSounds( void );
|
|
|
|
//FIXME: Replicated from server end, consolidate?
|
|
|
|
|
|
extern IVEngineClient *engine;
|
|
|
|
class CCollisionEvent : public IPhysicsCollisionEvent, public IPhysicsCollisionSolver, public IPhysicsObjectEvent
|
|
{
|
|
public:
|
|
CCollisionEvent( void );
|
|
|
|
void ObjectSound( int index, vcollisionevent_t *pEvent );
|
|
void PreCollision( vcollisionevent_t *pEvent ) {}
|
|
void PostCollision( vcollisionevent_t *pEvent );
|
|
void Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData );
|
|
|
|
void BufferTouchEvents( bool enable ) { m_bBufferTouchEvents = enable; }
|
|
|
|
void StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData );
|
|
void EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData );
|
|
|
|
void FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid );
|
|
void FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid );
|
|
void PostSimulationFrame() {}
|
|
|
|
virtual void ObjectEnterTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {}
|
|
virtual void ObjectLeaveTrigger( IPhysicsObject *pTrigger, IPhysicsObject *pObject ) {}
|
|
|
|
float DeltaTimeSinceLastFluid( CBaseEntity *pEntity );
|
|
void FrameUpdate( void );
|
|
|
|
void UpdateFluidEvents( void );
|
|
void UpdateTouchEvents( void );
|
|
|
|
// IPhysicsCollisionSolver
|
|
int ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 );
|
|
#if _DEBUG
|
|
int ShouldCollide_2( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 );
|
|
#endif
|
|
// debugging collision problem in TF2
|
|
int ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt );
|
|
bool ShouldFreezeObject( IPhysicsObject *pObject ) { return true; }
|
|
int AdditionalCollisionChecksThisTick( int currentChecksDone ) { return 0; }
|
|
bool ShouldFreezeContacts( IPhysicsObject **pObjectList, int objectCount ) { return true; }
|
|
|
|
// IPhysicsObjectEvent
|
|
virtual void ObjectWake( IPhysicsObject *pObject )
|
|
{
|
|
C_BaseEntity *pEntity = static_cast<C_BaseEntity *>(pObject->GetGameData());
|
|
if (pEntity && pEntity->HasDataObjectType(VPHYSICSWATCHER))
|
|
{
|
|
ReportVPhysicsStateChanged( pObject, pEntity, true );
|
|
}
|
|
}
|
|
|
|
virtual void ObjectSleep( IPhysicsObject *pObject )
|
|
{
|
|
C_BaseEntity *pEntity = static_cast<C_BaseEntity *>(pObject->GetGameData());
|
|
if ( pEntity && pEntity->HasDataObjectType( VPHYSICSWATCHER ) )
|
|
{
|
|
ReportVPhysicsStateChanged( pObject, pEntity, false );
|
|
}
|
|
}
|
|
|
|
|
|
friction_t *FindFriction( CBaseEntity *pObject );
|
|
void ShutdownFriction( friction_t &friction );
|
|
void UpdateFrictionSounds();
|
|
bool IsInCallback() { return m_inCallback > 0 ? true : false; }
|
|
|
|
private:
|
|
class CallbackContext
|
|
{
|
|
public:
|
|
CallbackContext(CCollisionEvent *pOuter)
|
|
{
|
|
m_pOuter = pOuter;
|
|
m_pOuter->m_inCallback++;
|
|
}
|
|
~CallbackContext()
|
|
{
|
|
m_pOuter->m_inCallback--;
|
|
}
|
|
private:
|
|
CCollisionEvent *m_pOuter;
|
|
};
|
|
friend class CallbackContext;
|
|
|
|
void AddTouchEvent( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, int touchType, const Vector &point, const Vector &normal );
|
|
void DispatchStartTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, const Vector &point, const Vector &normal );
|
|
void DispatchEndTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1 );
|
|
|
|
friction_t m_current[8];
|
|
CUtlVector<fluidevent_t> m_fluidEvents;
|
|
CUtlVector<touchevent_t> m_touchEvents;
|
|
int m_inCallback;
|
|
bool m_bBufferTouchEvents;
|
|
};
|
|
|
|
CCollisionEvent g_Collisions;
|
|
|
|
bool PhysIsInCallback()
|
|
{
|
|
if ( (physenv && physenv->IsInSimulation()) || g_Collisions.IsInCallback() )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PhysicsDLLInit( CreateInterfaceFn physicsFactory )
|
|
{
|
|
if ((physics = (IPhysics *)physicsFactory( VPHYSICS_INTERFACE_VERSION, NULL )) == NULL ||
|
|
(physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL )) == NULL ||
|
|
(physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL )) == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( IsX360() )
|
|
{
|
|
// Reduce timescale to save perf on 360
|
|
cl_phys_timescale.SetValue(0.9f);
|
|
}
|
|
PhysParseSurfaceData( physprops, filesystem );
|
|
return true;
|
|
}
|
|
|
|
#define DEFAULT_XBOX_CLIENT_VPHYSICS_TICK 0.025 // 25ms ticks on xbox ragdolls
|
|
void PhysicsLevelInit( void )
|
|
{
|
|
physenv = physics->CreateEnvironment();
|
|
assert( physenv );
|
|
#ifdef PORTAL
|
|
physenv_main = physenv;
|
|
#endif
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
g_EntityCollisionHash = physics->CreateObjectPairHash();
|
|
}
|
|
|
|
// TODO: need to get the right factory function here
|
|
//physenv->SetDebugOverlay( appSystemFactory );
|
|
physenv->SetGravity( Vector(0, 0, -GetCurrentGravity() ) );
|
|
// 15 ms per tick
|
|
// NOTE: Always run client physics at this rate - helps keep ragdolls stable
|
|
physenv->SetSimulationTimestep( IsXbox() ? DEFAULT_XBOX_CLIENT_VPHYSICS_TICK : DEFAULT_TICK_INTERVAL );
|
|
physenv->SetCollisionEventHandler( &g_Collisions );
|
|
physenv->SetCollisionSolver( &g_Collisions );
|
|
|
|
g_PhysWorldObject = PhysCreateWorld_Shared( GetClientWorldEntity(), modelinfo->GetVCollide(1), g_PhysDefaultObjectParams );
|
|
|
|
staticpropmgr->CreateVPhysicsRepresentations( physenv, &g_SolidSetup, NULL );
|
|
}
|
|
|
|
void PhysicsReset()
|
|
{
|
|
if ( !physenv )
|
|
return;
|
|
|
|
physenv->ResetSimulationClock();
|
|
}
|
|
|
|
|
|
ConVar cl_ragdoll_collide( "cl_ragdoll_collide", "0" );
|
|
|
|
int CCollisionEvent::ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 )
|
|
#if _DEBUG
|
|
{
|
|
int x0 = ShouldCollide_2(pObj0, pObj1, pGameData0, pGameData1);
|
|
int x1 = ShouldCollide_2(pObj1, pObj0, pGameData1, pGameData0);
|
|
Assert(x0==x1);
|
|
return x0;
|
|
}
|
|
int CCollisionEvent::ShouldCollide_2( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 )
|
|
#endif
|
|
{
|
|
CallbackContext callback(this);
|
|
|
|
C_BaseEntity *pEntity0 = static_cast<C_BaseEntity *>(pGameData0);
|
|
C_BaseEntity *pEntity1 = static_cast<C_BaseEntity *>(pGameData1);
|
|
|
|
if ( !pEntity0 || !pEntity1 )
|
|
return 1;
|
|
|
|
unsigned short gameFlags0 = pObj0->GetGameFlags();
|
|
unsigned short gameFlags1 = pObj1->GetGameFlags();
|
|
|
|
if ( pEntity0 == pEntity1 )
|
|
{
|
|
// allow all-or-nothing per-entity disable
|
|
if ( (gameFlags0 | gameFlags1) & FVPHYSICS_NO_SELF_COLLISIONS )
|
|
return 0;
|
|
|
|
IPhysicsCollisionSet *pSet = physics->FindCollisionSet( pEntity0->GetModelIndex() );
|
|
if ( pSet )
|
|
return pSet->ShouldCollide( pObj0->GetGameIndex(), pObj1->GetGameIndex() );
|
|
|
|
return 1;
|
|
}
|
|
// Obey collision group rules
|
|
Assert(GameRules());
|
|
if ( GameRules() )
|
|
{
|
|
if (!GameRules()->ShouldCollide( pEntity0->GetCollisionGroup(), pEntity1->GetCollisionGroup() ))
|
|
return 0;
|
|
}
|
|
|
|
if ( (pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) && (pObj1->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL) )
|
|
{
|
|
if ( !cl_ragdoll_collide.GetBool() )
|
|
return 0;
|
|
}
|
|
|
|
// check contents
|
|
if ( !(pObj0->GetContents() & pEntity1->PhysicsSolidMaskForEntity()) || !(pObj1->GetContents() & pEntity0->PhysicsSolidMaskForEntity()) )
|
|
return 0;
|
|
|
|
if ( g_EntityCollisionHash->IsObjectPairInHash( pGameData0, pGameData1 ) )
|
|
return 0;
|
|
|
|
if ( g_EntityCollisionHash->IsObjectPairInHash( pObj0, pObj1 ) )
|
|
return 0;
|
|
|
|
#if 0
|
|
int solid0 = pEntity0->GetSolid();
|
|
int solid1 = pEntity1->GetSolid();
|
|
int nSolidFlags0 = pEntity0->GetSolidFlags();
|
|
int nSolidFlags1 = pEntity1->GetSolidFlags();
|
|
#endif
|
|
|
|
int movetype0 = pEntity0->GetMoveType();
|
|
int movetype1 = pEntity1->GetMoveType();
|
|
|
|
// entities with non-physical move parents or entities with MOVETYPE_PUSH
|
|
// are considered as "AI movers". They are unchanged by collision; they exert
|
|
// physics forces on the rest of the system.
|
|
bool aiMove0 = (movetype0==MOVETYPE_PUSH) ? true : false;
|
|
bool aiMove1 = (movetype1==MOVETYPE_PUSH) ? true : false;
|
|
|
|
if ( pEntity0->GetMoveParent() )
|
|
{
|
|
// if the object & its parent are both MOVETYPE_VPHYSICS, then this must be a special case
|
|
// like a prop_ragdoll_attached
|
|
if ( !(movetype0 == MOVETYPE_VPHYSICS && pEntity0->GetRootMoveParent()->GetMoveType() == MOVETYPE_VPHYSICS) )
|
|
{
|
|
aiMove0 = true;
|
|
}
|
|
}
|
|
if ( pEntity1->GetMoveParent() )
|
|
{
|
|
// if the object & its parent are both MOVETYPE_VPHYSICS, then this must be a special case.
|
|
if ( !(movetype1 == MOVETYPE_VPHYSICS && pEntity1->GetRootMoveParent()->GetMoveType() == MOVETYPE_VPHYSICS) )
|
|
{
|
|
aiMove1 = true;
|
|
}
|
|
}
|
|
|
|
// AI movers don't collide with the world/static/pinned objects or other AI movers
|
|
if ( (aiMove0 && !pObj1->IsMoveable()) ||
|
|
(aiMove1 && !pObj0->IsMoveable()) ||
|
|
(aiMove0 && aiMove1) )
|
|
return 0;
|
|
|
|
// two objects under shadow control should not collide. The AI will figure it out
|
|
if ( pObj0->GetShadowController() && pObj1->GetShadowController() )
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
int CCollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt )
|
|
{
|
|
CallbackContext callback(this);
|
|
// solve it yourself here and return 0, or have the default implementation do it
|
|
if ( pGameData0 == pGameData1 )
|
|
{
|
|
if ( pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL )
|
|
{
|
|
// this is a ragdoll, self penetrating
|
|
C_BaseEntity *pEnt = reinterpret_cast<C_BaseEntity *>(pGameData0);
|
|
C_BaseAnimating *pAnim = pEnt->GetBaseAnimating();
|
|
|
|
if ( pAnim && pAnim->m_pRagdoll )
|
|
{
|
|
IPhysicsConstraintGroup *pGroup = pAnim->m_pRagdoll->GetConstraintGroup();
|
|
if ( pGroup )
|
|
{
|
|
pGroup->SolvePenetration( pObj0, pObj1 );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// A class that implements an IClientSystem for physics
|
|
class CPhysicsSystem : public CAutoGameSystemPerFrame
|
|
{
|
|
public:
|
|
CPhysicsSystem( char const *name ) : CAutoGameSystemPerFrame( name )
|
|
{
|
|
}
|
|
|
|
// HACKHACK: PhysicsDLLInit() is called explicitly because it requires a parameter
|
|
virtual bool Init();
|
|
virtual void Shutdown();
|
|
|
|
// Level init, shutdown
|
|
virtual void LevelInitPreEntity();
|
|
virtual void LevelInitPostEntity();
|
|
|
|
// The level is shutdown in two parts
|
|
virtual void LevelShutdownPreEntity();
|
|
|
|
virtual void LevelShutdownPostEntity();
|
|
|
|
void AddImpactSound( void *pGameData, IPhysicsObject *pObject, int surfaceProps, int surfacePropsHit, float volume, float speed );
|
|
|
|
virtual void Update( float frametime );
|
|
|
|
void PhysicsSimulate();
|
|
|
|
private:
|
|
physicssound::soundlist_t m_impactSounds;
|
|
};
|
|
|
|
static CPhysicsSystem g_PhysicsSystem( "CPhysicsSystem" );
|
|
// singleton to hook into the client system
|
|
IGameSystem *PhysicsGameSystem( void )
|
|
{
|
|
return &g_PhysicsSystem;
|
|
}
|
|
|
|
|
|
// HACKHACK: PhysicsDLLInit() is called explicitly because it requires a parameter
|
|
bool CPhysicsSystem::Init()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void CPhysicsSystem::Shutdown()
|
|
{
|
|
}
|
|
|
|
// Level init, shutdown
|
|
void CPhysicsSystem::LevelInitPreEntity( void )
|
|
{
|
|
m_impactSounds.RemoveAll();
|
|
PrecachePhysicsSounds();
|
|
}
|
|
|
|
void CPhysicsSystem::LevelInitPostEntity( void )
|
|
{
|
|
PhysicsLevelInit();
|
|
}
|
|
|
|
// The level is shutdown in two parts
|
|
void CPhysicsSystem::LevelShutdownPreEntity()
|
|
{
|
|
if ( physenv )
|
|
{
|
|
// we may have deleted multiple objects including the world by now, so
|
|
// don't try to wake them up
|
|
physenv->SetQuickDelete( true );
|
|
}
|
|
}
|
|
|
|
void CPhysicsSystem::LevelShutdownPostEntity()
|
|
{
|
|
if ( physenv )
|
|
{
|
|
// environment destroys all objects
|
|
// entities are gone, so this is safe now
|
|
physics->DestroyEnvironment( physenv );
|
|
}
|
|
physics->DestroyObjectPairHash( g_EntityCollisionHash );
|
|
g_EntityCollisionHash = NULL;
|
|
|
|
physics->DestroyAllCollisionSets();
|
|
|
|
physenv = NULL;
|
|
g_PhysWorldObject = NULL;
|
|
}
|
|
|
|
void CPhysicsSystem::AddImpactSound( void *pGameData, IPhysicsObject *pObject, int surfaceProps, int surfacePropsHit, float volume, float speed )
|
|
{
|
|
physicssound::AddImpactSound( m_impactSounds, pGameData, SOUND_FROM_WORLD, CHAN_STATIC, pObject, surfaceProps, surfacePropsHit, volume, speed );
|
|
}
|
|
|
|
|
|
void CPhysicsSystem::Update( float frametime )
|
|
{
|
|
// THIS WAS MOVED TO POST-ENTITY SIM
|
|
//PhysicsSimulate();
|
|
}
|
|
|
|
|
|
void CPhysicsSystem::PhysicsSimulate()
|
|
{
|
|
VPROF_BUDGET( "CPhysicsSystem::PhysicsSimulate", VPROF_BUDGETGROUP_PHYSICS );
|
|
float frametime = gpGlobals->frametime;
|
|
|
|
if ( physenv )
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, physenv->GetActiveObjectCount() );
|
|
|
|
g_Collisions.BufferTouchEvents( true );
|
|
#ifdef _DEBUG
|
|
physenv->DebugCheckContacts();
|
|
#endif
|
|
physenv->Simulate( frametime * cl_phys_timescale.GetFloat() );
|
|
|
|
int activeCount = physenv->GetActiveObjectCount();
|
|
IPhysicsObject **pActiveList = NULL;
|
|
if ( activeCount )
|
|
{
|
|
pActiveList = (IPhysicsObject **)stackalloc( sizeof(IPhysicsObject *)*activeCount );
|
|
physenv->GetActiveObjects( pActiveList );
|
|
|
|
for ( int i = 0; i < activeCount; i++ )
|
|
{
|
|
C_BaseEntity *pEntity = reinterpret_cast<C_BaseEntity *>(pActiveList[i]->GetGameData());
|
|
if ( pEntity )
|
|
{
|
|
if ( pEntity->CollisionProp()->DoesVPhysicsInvalidateSurroundingBox() )
|
|
{
|
|
pEntity->CollisionProp()->MarkSurroundingBoundsDirty();
|
|
}
|
|
pEntity->VPhysicsUpdate( pActiveList[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
g_Collisions.BufferTouchEvents( false );
|
|
g_Collisions.FrameUpdate();
|
|
}
|
|
physicssound::PlayImpactSounds( m_impactSounds );
|
|
}
|
|
|
|
|
|
void PhysicsSimulate()
|
|
{
|
|
g_PhysicsSystem.PhysicsSimulate();
|
|
}
|
|
|
|
|
|
|
|
CCollisionEvent::CCollisionEvent( void )
|
|
{
|
|
}
|
|
|
|
void CCollisionEvent::ObjectSound( int index, vcollisionevent_t *pEvent )
|
|
{
|
|
IPhysicsObject *pObject = pEvent->pObjects[index];
|
|
if ( !pObject || pObject->IsStatic() )
|
|
return;
|
|
|
|
float speed = pEvent->collisionSpeed * pEvent->collisionSpeed;
|
|
int surfaceProps = pEvent->surfaceProps[index];
|
|
|
|
void *pGameData = pObject->GetGameData();
|
|
|
|
if ( pGameData )
|
|
{
|
|
float volume = speed * (1.0f/(320.0f*320.0f)); // max volume at 320 in/s
|
|
|
|
if ( volume > 1.0f )
|
|
volume = 1.0f;
|
|
|
|
if ( surfaceProps >= 0 )
|
|
{
|
|
g_PhysicsSystem.AddImpactSound( pGameData, pObject, surfaceProps, pEvent->surfaceProps[!index], volume, speed );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCollisionEvent::PostCollision( vcollisionevent_t *pEvent )
|
|
{
|
|
CallbackContext callback(this);
|
|
if ( pEvent->deltaCollisionTime > 0.1f && pEvent->collisionSpeed > 70 )
|
|
{
|
|
ObjectSound( 0, pEvent );
|
|
ObjectSound( 1, pEvent );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::FrameUpdate( void )
|
|
{
|
|
UpdateFrictionSounds();
|
|
UpdateTouchEvents();
|
|
UpdateFluidEvents();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::UpdateTouchEvents( void )
|
|
{
|
|
// Turn on buffering in case new touch events occur during processing
|
|
bool bOldTouchEvents = m_bBufferTouchEvents;
|
|
m_bBufferTouchEvents = true;
|
|
for ( int i = 0; i < m_touchEvents.Count(); i++ )
|
|
{
|
|
const touchevent_t &event = m_touchEvents[i];
|
|
if ( event.touchType == TOUCH_START )
|
|
{
|
|
DispatchStartTouch( event.pEntity0, event.pEntity1, event.endPoint, event.normal );
|
|
}
|
|
else
|
|
{
|
|
// TOUCH_END
|
|
DispatchEndTouch( event.pEntity0, event.pEntity1 );
|
|
}
|
|
}
|
|
|
|
m_touchEvents.RemoveAll();
|
|
m_bBufferTouchEvents = bOldTouchEvents;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEntity0 -
|
|
// *pEntity1 -
|
|
// touchType -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::AddTouchEvent( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, int touchType, const Vector &point, const Vector &normal )
|
|
{
|
|
if ( !pEntity0 || !pEntity1 )
|
|
return;
|
|
|
|
int index = m_touchEvents.AddToTail();
|
|
touchevent_t &event = m_touchEvents[index];
|
|
event.pEntity0 = pEntity0;
|
|
event.pEntity1 = pEntity1;
|
|
event.touchType = touchType;
|
|
event.endPoint = point;
|
|
event.normal = normal;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pObject1 -
|
|
// *pObject2 -
|
|
// *pTouchData -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::StartTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData )
|
|
{
|
|
CallbackContext callback(this);
|
|
C_BaseEntity *pEntity1 = static_cast<C_BaseEntity *>(pObject1->GetGameData());
|
|
C_BaseEntity *pEntity2 = static_cast<C_BaseEntity *>(pObject2->GetGameData());
|
|
|
|
if ( !pEntity1 || !pEntity2 )
|
|
return;
|
|
|
|
Vector endPoint, normal;
|
|
pTouchData->GetContactPoint( endPoint );
|
|
pTouchData->GetSurfaceNormal( normal );
|
|
if ( !m_bBufferTouchEvents )
|
|
{
|
|
DispatchStartTouch( pEntity1, pEntity2, endPoint, normal );
|
|
}
|
|
else
|
|
{
|
|
AddTouchEvent( pEntity1, pEntity2, TOUCH_START, endPoint, normal );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEntity0 -
|
|
// *pEntity1 -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::DispatchStartTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1, const Vector &point, const Vector &normal )
|
|
{
|
|
trace_t trace;
|
|
memset( &trace, 0, sizeof(trace) );
|
|
trace.endpos = point;
|
|
trace.plane.dist = DotProduct( point, normal );
|
|
trace.plane.normal = normal;
|
|
|
|
// NOTE: This sets up the touch list for both entities, no call to pEntity1 is needed
|
|
pEntity0->PhysicsMarkEntitiesAsTouchingEventDriven( pEntity1, trace );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pObject1 -
|
|
// *pObject2 -
|
|
// *pTouchData -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::EndTouch( IPhysicsObject *pObject1, IPhysicsObject *pObject2, IPhysicsCollisionData *pTouchData )
|
|
{
|
|
CallbackContext callback(this);
|
|
C_BaseEntity *pEntity1 = static_cast<C_BaseEntity *>(pObject1->GetGameData());
|
|
C_BaseEntity *pEntity2 = static_cast<C_BaseEntity *>(pObject2->GetGameData());
|
|
|
|
if ( !pEntity1 || !pEntity2 )
|
|
return;
|
|
|
|
if ( !m_bBufferTouchEvents )
|
|
{
|
|
DispatchEndTouch( pEntity1, pEntity2 );
|
|
}
|
|
else
|
|
{
|
|
AddTouchEvent( pEntity1, pEntity2, TOUCH_END, vec3_origin, vec3_origin );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEntity0 -
|
|
// *pEntity1 -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::DispatchEndTouch( C_BaseEntity *pEntity0, C_BaseEntity *pEntity1 )
|
|
{
|
|
// frees the event-driven touchlinks
|
|
pEntity0->PhysicsNotifyOtherOfUntouch( pEntity0, pEntity1 );
|
|
pEntity1->PhysicsNotifyOtherOfUntouch( pEntity1, pEntity0 );
|
|
}
|
|
|
|
void CCollisionEvent::Friction( IPhysicsObject *pObject, float energy, int surfaceProps, int surfacePropsHit, IPhysicsCollisionData *pData )
|
|
{
|
|
CallbackContext callback(this);
|
|
if ( energy < 0.05f || surfaceProps < 0 )
|
|
return;
|
|
|
|
//Get our friction information
|
|
Vector vecPos, vecVel;
|
|
pData->GetContactPoint( vecPos );
|
|
pObject->GetVelocityAtPoint( vecPos, &vecVel );
|
|
|
|
CBaseEntity *pEntity = reinterpret_cast<CBaseEntity *>(pObject->GetGameData());
|
|
|
|
if ( pEntity )
|
|
{
|
|
friction_t *pFriction = g_Collisions.FindFriction( pEntity );
|
|
|
|
if ( (gpGlobals->maxClients > 1) && pFriction && pFriction->pObject)
|
|
{
|
|
// in MP mode play sound and effects once every 500 msecs,
|
|
// no ongoing updates, takes too much bandwidth
|
|
if ( (pFriction->flLastEffectTime + 0.5f) > gpGlobals->curtime)
|
|
{
|
|
pFriction->flLastUpdateTime = gpGlobals->curtime;
|
|
return;
|
|
}
|
|
}
|
|
|
|
PhysFrictionSound( pEntity, pObject, energy, surfaceProps, surfacePropsHit );
|
|
}
|
|
|
|
PhysFrictionEffect( vecPos, vecVel, energy, surfaceProps, surfacePropsHit );
|
|
}
|
|
|
|
friction_t *CCollisionEvent::FindFriction( CBaseEntity *pObject )
|
|
{
|
|
friction_t *pFree = NULL;
|
|
|
|
for ( int i = 0; i < ARRAYSIZE(m_current); i++ )
|
|
{
|
|
if ( !m_current[i].pObject && !pFree )
|
|
pFree = &m_current[i];
|
|
|
|
if ( m_current[i].pObject == pObject )
|
|
return &m_current[i];
|
|
}
|
|
|
|
return pFree;
|
|
}
|
|
|
|
void CCollisionEvent::ShutdownFriction( friction_t &friction )
|
|
{
|
|
// Msg( "Scrape Stop %s \n", STRING(friction.pObject->m_iClassname) );
|
|
CSoundEnvelopeController::GetController().SoundDestroy( friction.patch );
|
|
friction.patch = NULL;
|
|
friction.pObject = NULL;
|
|
}
|
|
|
|
void CCollisionEvent::UpdateFrictionSounds( void )
|
|
{
|
|
for ( int i = 0; i < ARRAYSIZE(m_current); i++ )
|
|
{
|
|
if ( m_current[i].patch )
|
|
{
|
|
if ( m_current[i].flLastUpdateTime < (gpGlobals->curtime-0.1f) )
|
|
{
|
|
// friction wasn't updated the last 100msec, assume fiction finished
|
|
ShutdownFriction( m_current[i] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &matrix -
|
|
// &normal -
|
|
// Output : static int
|
|
//-----------------------------------------------------------------------------
|
|
static int BestAxisMatchingNormal( matrix3x4_t &matrix, const Vector &normal )
|
|
{
|
|
float bestDot = -1;
|
|
int best = 0;
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
Vector tmp;
|
|
MatrixGetColumn( matrix, i, tmp );
|
|
float dot = fabs(DotProduct( tmp, normal ));
|
|
if ( dot > bestDot )
|
|
{
|
|
bestDot = dot;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pFluid -
|
|
// *pObject -
|
|
// *pEntity -
|
|
//-----------------------------------------------------------------------------
|
|
void PhysicsSplash( IPhysicsFluidController *pFluid, IPhysicsObject *pObject, CBaseEntity *pEntity )
|
|
{
|
|
//FIXME: For now just allow ragdolls for E3 - jdw
|
|
if ( ( pObject->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) == false )
|
|
return;
|
|
|
|
Vector velocity;
|
|
pObject->GetVelocity( &velocity, NULL );
|
|
|
|
float impactSpeed = velocity.Length();
|
|
|
|
if ( impactSpeed < 25.0f )
|
|
return;
|
|
|
|
Vector normal;
|
|
float dist;
|
|
pFluid->GetSurfacePlane( &normal, &dist );
|
|
|
|
matrix3x4_t &matrix = pEntity->EntityToWorldTransform();
|
|
|
|
// Find the local axis that best matches the water surface normal
|
|
int bestAxis = BestAxisMatchingNormal( matrix, normal );
|
|
|
|
Vector tangent, binormal;
|
|
MatrixGetColumn( matrix, (bestAxis+1)%3, tangent );
|
|
binormal = CrossProduct( normal, tangent );
|
|
VectorNormalize( binormal );
|
|
tangent = CrossProduct( binormal, normal );
|
|
VectorNormalize( tangent );
|
|
|
|
// Now we have a basis tangent to the surface that matches the object's local orientation as well as possible
|
|
// compute an OBB using this basis
|
|
|
|
// Get object extents in basis
|
|
Vector tanPts[2], binPts[2];
|
|
tanPts[0] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), -tangent );
|
|
tanPts[1] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), tangent );
|
|
binPts[0] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), -binormal );
|
|
binPts[1] = physcollision->CollideGetExtent( pObject->GetCollide(), pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), binormal );
|
|
|
|
// now compute the centered bbox
|
|
float mins[2], maxs[2], center[2], extents[2];
|
|
mins[0] = DotProduct( tanPts[0], tangent );
|
|
maxs[0] = DotProduct( tanPts[1], tangent );
|
|
|
|
mins[1] = DotProduct( binPts[0], binormal );
|
|
maxs[1] = DotProduct( binPts[1], binormal );
|
|
|
|
center[0] = 0.5 * (mins[0] + maxs[0]);
|
|
center[1] = 0.5 * (mins[1] + maxs[1]);
|
|
|
|
extents[0] = maxs[0] - center[0];
|
|
extents[1] = maxs[1] - center[1];
|
|
|
|
Vector centerPoint = center[0] * tangent + center[1] * binormal + dist * normal;
|
|
|
|
Vector axes[2];
|
|
axes[0] = (maxs[0] - center[0]) * tangent;
|
|
axes[1] = (maxs[1] - center[1]) * binormal;
|
|
|
|
// visualize OBB hit
|
|
/*
|
|
Vector corner1 = centerPoint - axes[0] - axes[1];
|
|
Vector corner2 = centerPoint + axes[0] - axes[1];
|
|
Vector corner3 = centerPoint + axes[0] + axes[1];
|
|
Vector corner4 = centerPoint - axes[0] + axes[1];
|
|
NDebugOverlay::Line( corner1, corner2, 0, 0, 255, false, 10 );
|
|
NDebugOverlay::Line( corner2, corner3, 0, 0, 255, false, 10 );
|
|
NDebugOverlay::Line( corner3, corner4, 0, 0, 255, false, 10 );
|
|
NDebugOverlay::Line( corner4, corner1, 0, 0, 255, false, 10 );
|
|
*/
|
|
|
|
Vector corner[4];
|
|
|
|
corner[0] = centerPoint - axes[0] - axes[1];
|
|
corner[1] = centerPoint + axes[0] - axes[1];
|
|
corner[2] = centerPoint + axes[0] + axes[1];
|
|
corner[3] = centerPoint - axes[0] + axes[1];
|
|
|
|
int contents = enginetrace->GetPointContents( centerPoint-Vector(0,0,2) );
|
|
|
|
bool bInSlime = ( contents & CONTENTS_SLIME ) ? true : false;
|
|
|
|
Vector color = vec3_origin;
|
|
float luminosity = 1.0f;
|
|
|
|
if ( !bInSlime )
|
|
{
|
|
// Get our lighting information
|
|
FX_GetSplashLighting( centerPoint + ( normal * 8.0f ), &color, &luminosity );
|
|
}
|
|
|
|
if ( impactSpeed > 150 )
|
|
{
|
|
if ( bInSlime )
|
|
{
|
|
FX_GunshotSlimeSplash( centerPoint, normal, random->RandomFloat( 8, 10 ) );
|
|
}
|
|
else
|
|
{
|
|
FX_GunshotSplash( centerPoint, normal, random->RandomFloat( 8, 10 ) );
|
|
}
|
|
}
|
|
else if ( !bInSlime )
|
|
{
|
|
FX_WaterRipple( centerPoint, 1.5f, &color, 1.5f, luminosity );
|
|
}
|
|
|
|
int splashes = 4;
|
|
Vector point;
|
|
|
|
for ( int i = 0; i < splashes; i++ )
|
|
{
|
|
point = RandomVector( -32.0f, 32.0f );
|
|
point[2] = 0.0f;
|
|
|
|
point += corner[i];
|
|
|
|
if ( impactSpeed > 150 )
|
|
{
|
|
if ( bInSlime )
|
|
{
|
|
FX_GunshotSlimeSplash( centerPoint, normal, random->RandomFloat( 4, 6 ) );
|
|
}
|
|
else
|
|
{
|
|
FX_GunshotSplash( centerPoint, normal, random->RandomFloat( 4, 6 ) );
|
|
}
|
|
}
|
|
else if ( !bInSlime )
|
|
{
|
|
FX_WaterRipple( point, random->RandomFloat( 0.25f, 0.5f ), &color, luminosity, random->RandomFloat( 0.5f, 1.0f ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::UpdateFluidEvents( void )
|
|
{
|
|
for ( int i = m_fluidEvents.Count()-1; i >= 0; --i )
|
|
{
|
|
if ( (gpGlobals->curtime - m_fluidEvents[i].impactTime) > FLUID_TIME_MAX )
|
|
{
|
|
m_fluidEvents.FastRemove(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEntity -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CCollisionEvent::DeltaTimeSinceLastFluid( CBaseEntity *pEntity )
|
|
{
|
|
for ( int i = m_fluidEvents.Count()-1; i >= 0; --i )
|
|
{
|
|
if ( m_fluidEvents[i].hEntity.Get() == pEntity )
|
|
{
|
|
return gpGlobals->curtime - m_fluidEvents[i].impactTime;
|
|
}
|
|
}
|
|
|
|
int index = m_fluidEvents.AddToTail();
|
|
m_fluidEvents[index].hEntity = pEntity;
|
|
m_fluidEvents[index].impactTime = gpGlobals->curtime;
|
|
return FLUID_TIME_MAX;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pObject -
|
|
// *pFluid -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::FluidStartTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid )
|
|
{
|
|
CallbackContext callback(this);
|
|
if ( ( pObject == NULL ) || ( pFluid == NULL ) )
|
|
return;
|
|
|
|
CBaseEntity *pEntity = static_cast<CBaseEntity *>(pObject->GetGameData());
|
|
|
|
if ( pEntity )
|
|
{
|
|
float timeSinceLastCollision = DeltaTimeSinceLastFluid( pEntity );
|
|
|
|
if ( timeSinceLastCollision < 0.5f )
|
|
return;
|
|
|
|
PhysicsSplash( pFluid, pObject, pEntity );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pObject -
|
|
// *pFluid -
|
|
//-----------------------------------------------------------------------------
|
|
void CCollisionEvent::FluidEndTouch( IPhysicsObject *pObject, IPhysicsFluidController *pFluid )
|
|
{
|
|
CallbackContext callback(this);
|
|
//FIXME: Do nothing for now
|
|
}
|
|
|
|
IPhysicsObject *GetWorldPhysObject ( void )
|
|
{
|
|
return g_PhysWorldObject;
|
|
}
|
|
|
|
void PhysFrictionSound( CBaseEntity *pEntity, IPhysicsObject *pObject, const char *pSoundName, HSOUNDSCRIPTHANDLE& handle, float flVolume )
|
|
{
|
|
if ( !pEntity )
|
|
return;
|
|
|
|
// cut out the quiet sounds
|
|
// UNDONE: Separate threshold for starting a sound vs. continuing?
|
|
flVolume = clamp( flVolume, 0.0f, 1.0f );
|
|
if ( flVolume > (1.0f/128.0f) )
|
|
{
|
|
friction_t *pFriction = g_Collisions.FindFriction( pEntity );
|
|
if ( !pFriction )
|
|
return;
|
|
|
|
CSoundParameters params;
|
|
if ( !CBaseEntity::GetParametersForSound( pSoundName, handle, params, NULL ) )
|
|
return;
|
|
|
|
if ( !pFriction->pObject )
|
|
{
|
|
// don't create really quiet scrapes
|
|
if ( params.volume * flVolume <= 0.1f )
|
|
return;
|
|
|
|
pFriction->pObject = pEntity;
|
|
CPASAttenuationFilter filter( pEntity, params.soundlevel );
|
|
int entindex = pEntity->entindex();
|
|
|
|
// clientside created entites doesn't have a valid entindex, let 'world' play the sound for them
|
|
if ( entindex < 0 )
|
|
entindex = 0;
|
|
|
|
pFriction->patch = CSoundEnvelopeController::GetController().SoundCreate(
|
|
filter, entindex, CHAN_BODY, pSoundName, params.soundlevel );
|
|
CSoundEnvelopeController::GetController().Play( pFriction->patch, params.volume * flVolume, params.pitch );
|
|
}
|
|
else
|
|
{
|
|
float pitch = (flVolume * (params.pitchhigh - params.pitchlow)) + params.pitchlow;
|
|
CSoundEnvelopeController::GetController().SoundChangeVolume( pFriction->patch, params.volume * flVolume, 0.1f );
|
|
CSoundEnvelopeController::GetController().SoundChangePitch( pFriction->patch, pitch, 0.1f );
|
|
}
|
|
|
|
pFriction->flLastUpdateTime = gpGlobals->curtime;
|
|
pFriction->flLastEffectTime = gpGlobals->curtime;
|
|
}
|
|
}
|
|
|
|
void PhysCleanupFrictionSounds( CBaseEntity *pEntity )
|
|
{
|
|
friction_t *pFriction = g_Collisions.FindFriction( pEntity );
|
|
if ( pFriction && pFriction->patch )
|
|
{
|
|
g_Collisions.ShutdownFriction( *pFriction );
|
|
}
|
|
}
|
|
|
|
float PhysGetNextSimTime()
|
|
{
|
|
return physenv->GetSimulationTime() + gpGlobals->frametime * cl_phys_timescale.GetFloat();
|
|
}
|
|
|
|
float PhysGetSyncCreateTime()
|
|
{
|
|
float nextTime = physenv->GetNextFrameTime();
|
|
float simTime = PhysGetNextSimTime();
|
|
if ( nextTime < simTime )
|
|
{
|
|
// The next simulation frame begins before the end of this frame
|
|
// so create physics objects at that time so that they will reach the current
|
|
// position at curtime. Otherwise the physics object will simulate forward from curtime
|
|
// and pop into the future a bit at this point of transition
|
|
return gpGlobals->curtime + nextTime - simTime;
|
|
}
|
|
return gpGlobals->curtime;
|
|
}
|