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.
1051 lines
30 KiB
1051 lines
30 KiB
//========= 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 : gpGlobals->interval_per_tick ); |
|
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; |
|
}
|
|
|