aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/physconstraint.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/physconstraint.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/physconstraint.cpp')
-rw-r--r--mp/src/game/server/physconstraint.cpp1895
1 files changed, 1895 insertions, 0 deletions
diff --git a/mp/src/game/server/physconstraint.cpp b/mp/src/game/server/physconstraint.cpp
new file mode 100644
index 00000000..3d1cc1f1
--- /dev/null
+++ b/mp/src/game/server/physconstraint.cpp
@@ -0,0 +1,1895 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Physics constraint entities
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#include "cbase.h"
+#include "physics.h"
+#include "entityoutput.h"
+#include "engine/IEngineSound.h"
+#include "igamesystem.h"
+#include "physics_saverestore.h"
+#include "vcollide_parse.h"
+#include "positionwatcher.h"
+#include "fmtstr.h"
+#include "physics_prop_ragdoll.h"
+
+#define HINGE_NOTIFY HL2_EPISODIC
+#if HINGE_NOTIFY
+#include "physconstraint_sounds.h"
+#endif
+
+#include "physconstraint.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define SF_CONSTRAINT_DISABLE_COLLISION 0x0001
+#define SF_SLIDE_LIMIT_ENDS 0x0002
+#define SF_PULLEY_RIGID 0x0002
+#define SF_LENGTH_RIGID 0x0002
+#define SF_RAGDOLL_FREEMOVEMENT 0x0002
+#define SF_CONSTRAINT_START_INACTIVE 0x0004
+#define SF_CONSTRAINT_ASSUME_WORLD_GEOMETRY 0x0008
+#define SF_CONSTRAINT_NO_CONNECT_UNTIL_ACTIVATED 0x0010 // Will only check the two attached entities at activation
+
+
+ConVar g_debug_constraint_sounds ( "g_debug_constraint_sounds", "0", FCVAR_CHEAT, "Enable debug printing about constraint sounds.");
+
+struct constraint_anchor_t
+{
+ Vector localOrigin;
+ EHANDLE hEntity;
+ int parentAttachment;
+ string_t name;
+ float massScale;
+};
+
+class CAnchorList : public CAutoGameSystem
+{
+public:
+ CAnchorList( char const *name ) : CAutoGameSystem( name )
+ {
+ }
+ void LevelShutdownPostEntity()
+ {
+ m_list.Purge();
+ }
+
+ void AddToList( CBaseEntity *pEntity, float massScale )
+ {
+ int index = m_list.AddToTail();
+ constraint_anchor_t *pAnchor = &m_list[index];
+
+ pAnchor->hEntity = pEntity->GetParent();
+ pAnchor->parentAttachment = pEntity->GetParentAttachment();
+ pAnchor->name = pEntity->GetEntityName();
+ pAnchor->localOrigin = pEntity->GetLocalOrigin();
+ pAnchor->massScale = massScale;
+ }
+
+ constraint_anchor_t *Find( string_t name )
+ {
+ for ( int i = m_list.Count()-1; i >=0; i-- )
+ {
+ if ( FStrEq( STRING(m_list[i].name), STRING(name) ) )
+ {
+ return &m_list[i];
+ }
+ }
+ return NULL;
+ }
+
+private:
+ CUtlVector<constraint_anchor_t> m_list;
+};
+
+static CAnchorList g_AnchorList( "CAnchorList" );
+
+class CConstraintAnchor : public CPointEntity
+{
+ DECLARE_CLASS( CConstraintAnchor, CPointEntity );
+public:
+ CConstraintAnchor()
+ {
+ m_massScale = 1.0f;
+ }
+ void Spawn( void )
+ {
+ if ( GetParent() )
+ {
+ g_AnchorList.AddToList( this, m_massScale );
+ UTIL_Remove( this );
+ }
+ }
+ DECLARE_DATADESC();
+
+ float m_massScale;
+};
+
+BEGIN_DATADESC( CConstraintAnchor )
+ DEFINE_KEYFIELD( m_massScale, FIELD_FLOAT, "massScale" ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( info_constraint_anchor, CConstraintAnchor );
+
+class CPhysConstraintSystem : public CLogicalEntity
+{
+ DECLARE_CLASS( CPhysConstraintSystem, CLogicalEntity );
+public:
+
+ void Spawn();
+ IPhysicsConstraintGroup *GetVPhysicsGroup() { return m_pMachine; }
+
+ DECLARE_DATADESC();
+private:
+ IPhysicsConstraintGroup *m_pMachine;
+ int m_additionalIterations;
+};
+
+BEGIN_DATADESC( CPhysConstraintSystem )
+ DEFINE_PHYSPTR( m_pMachine ),
+ DEFINE_KEYFIELD( m_additionalIterations, FIELD_INTEGER, "additionaliterations" ),
+
+END_DATADESC()
+
+
+void CPhysConstraintSystem::Spawn()
+{
+ constraint_groupparams_t group;
+ group.Defaults();
+ group.additionalIterations = m_additionalIterations;
+ m_pMachine = physenv->CreateConstraintGroup( group );
+}
+
+LINK_ENTITY_TO_CLASS( phys_constraintsystem, CPhysConstraintSystem );
+
+void PhysTeleportConstrainedEntity( CBaseEntity *pTeleportSource, IPhysicsObject *pObject0, IPhysicsObject *pObject1, const Vector &prevPosition, const QAngle &prevAngles, bool physicsRotate )
+{
+ // teleport the other object
+ CBaseEntity *pEntity0 = static_cast<CBaseEntity *> (pObject0->GetGameData());
+ CBaseEntity *pEntity1 = static_cast<CBaseEntity *> (pObject1->GetGameData());
+ if ( !pEntity0 || !pEntity1 )
+ return;
+
+ // figure out which entity needs to be fixed up (the one that isn't pTeleportSource)
+ CBaseEntity *pFixup = pEntity1;
+ // teleport the other object
+ if ( pTeleportSource != pEntity0 )
+ {
+ if ( pTeleportSource != pEntity1 )
+ {
+ Msg("Bogus teleport notification!!\n");
+ return;
+ }
+ pFixup = pEntity0;
+ }
+
+ // constraint doesn't move this entity
+ if ( pFixup->GetMoveType() != MOVETYPE_VPHYSICS )
+ return;
+
+ if ( !pFixup->VPhysicsGetObject() || !pFixup->VPhysicsGetObject()->IsMoveable() )
+ return;
+
+ QAngle oldAngles = prevAngles;
+
+ if ( !physicsRotate )
+ {
+ oldAngles = pTeleportSource->GetAbsAngles();
+ }
+
+ matrix3x4_t startCoord, startInv, endCoord, xform;
+ AngleMatrix( oldAngles, prevPosition, startCoord );
+ MatrixInvert( startCoord, startInv );
+ ConcatTransforms( pTeleportSource->EntityToWorldTransform(), startInv, xform );
+ QAngle fixupAngles;
+ Vector fixupPos;
+
+ ConcatTransforms( xform, pFixup->EntityToWorldTransform(), endCoord );
+ MatrixAngles( endCoord, fixupAngles, fixupPos );
+ pFixup->Teleport( &fixupPos, &fixupAngles, NULL );
+}
+
+static void DrawPhysicsBounds( IPhysicsObject *pObject, int r, int g, int b, int a )
+{
+ const CPhysCollide *pCollide = pObject->GetCollide();
+ Vector pos;
+ QAngle angles;
+ pObject->GetPosition( &pos, &angles );
+ Vector mins, maxs;
+ physcollision->CollideGetAABB( &mins, &maxs, pCollide, vec3_origin, vec3_angle );
+ // don't fight the z-buffer
+ mins -= Vector(1,1,1);
+ maxs += Vector(1,1,1);
+ NDebugOverlay::BoxAngles( pos, mins, maxs, angles, r, g, b, a, 0 );
+}
+
+static void DrawConstraintObjectsAxes(CBaseEntity *pConstraintEntity, IPhysicsConstraint *pConstraint)
+{
+ if ( !pConstraint || !pConstraintEntity )
+ return;
+ matrix3x4_t xformRef, xformAtt;
+ bool bXform = pConstraint->GetConstraintTransform( &xformRef, &xformAtt );
+ IPhysicsObject *pRef = pConstraint->GetReferenceObject();
+
+ if ( pRef && !pRef->IsStatic() )
+ {
+ if ( bXform )
+ {
+ Vector pos, posWorld;
+ QAngle angles;
+ MatrixAngles( xformRef, angles, pos );
+ pRef->LocalToWorld( &posWorld, pos );
+ NDebugOverlay::Axis( posWorld, vec3_angle, 12, false, 0 );
+ }
+ DrawPhysicsBounds( pRef, 0, 255, 0, 12 );
+ }
+ IPhysicsObject *pAttach = pConstraint->GetAttachedObject();
+ if ( pAttach && !pAttach->IsStatic() )
+ {
+ if ( bXform )
+ {
+ Vector pos, posWorld;
+ QAngle angles;
+ MatrixAngles( xformAtt, angles, pos );
+ pAttach->LocalToWorld( &posWorld, pos );
+ NDebugOverlay::Axis( posWorld, vec3_angle, 12, false, 0 );
+ }
+ DrawPhysicsBounds( pAttach, 255, 0, 0, 12 );
+ }
+}
+
+void CPhysConstraint::ClearStaticFlag( IPhysicsObject *pObj )
+{
+ if ( !pObj )
+ return;
+ PhysClearGameFlags( pObj, FVPHYSICS_CONSTRAINT_STATIC );
+}
+
+void CPhysConstraint::Deactivate()
+{
+ if ( !m_pConstraint )
+ return;
+ m_pConstraint->Deactivate();
+ ClearStaticFlag( m_pConstraint->GetReferenceObject() );
+ ClearStaticFlag( m_pConstraint->GetAttachedObject() );
+ if ( m_spawnflags & SF_CONSTRAINT_DISABLE_COLLISION )
+ {
+ // constraint may be getting deactivated because an object got deleted, so check them here.
+ IPhysicsObject *pRef = m_pConstraint->GetReferenceObject();
+ IPhysicsObject *pAtt = m_pConstraint->GetAttachedObject();
+ if ( pRef && pAtt )
+ {
+ PhysEnableEntityCollisions( pRef, pAtt );
+ }
+ }
+}
+
+void CPhysConstraint::OnBreak( void )
+{
+ Deactivate();
+ if ( m_breakSound != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this, ATTN_STATIC );
+
+ Vector origin = GetAbsOrigin();
+ Vector refPos = origin, attachPos = origin;
+
+ IPhysicsObject *pRef = m_pConstraint->GetReferenceObject();
+ if ( pRef && (pRef != g_PhysWorldObject) )
+ {
+ pRef->GetPosition( &refPos, NULL );
+ attachPos = refPos;
+ }
+ IPhysicsObject *pAttach = m_pConstraint->GetAttachedObject();
+ if ( pAttach && (pAttach != g_PhysWorldObject) )
+ {
+ pAttach->GetPosition( &attachPos, NULL );
+ if ( !pRef || (pRef == g_PhysWorldObject) )
+ {
+ refPos = attachPos;
+ }
+ }
+
+ VectorAdd( refPos, attachPos, origin );
+ origin *= 0.5f;
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_STATIC;
+ ep.m_pSoundName = STRING(m_breakSound);
+ ep.m_flVolume = VOL_NORM;
+ ep.m_SoundLevel = ATTN_TO_SNDLVL( ATTN_STATIC );
+ ep.m_pOrigin = &origin;
+
+ EmitSound( filter, entindex(), ep );
+ }
+ m_OnBreak.FireOutput( this, this );
+ // queue this up to be deleted at the end of physics
+ // The Deactivate() call should make sure we don't get more of these callbacks.
+ PhysCallbackRemove( this->NetworkProp() );
+}
+
+void CPhysConstraint::InputBreak( inputdata_t &inputdata )
+{
+ if ( m_pConstraint )
+ m_pConstraint->Deactivate();
+
+ OnBreak();
+}
+
+void CPhysConstraint::InputOnBreak( inputdata_t &inputdata )
+{
+ OnBreak();
+}
+
+void CPhysConstraint::InputTurnOn( inputdata_t &inputdata )
+{
+ if ( HasSpawnFlags( SF_CONSTRAINT_NO_CONNECT_UNTIL_ACTIVATED ) )
+ {
+ ActivateConstraint();
+ }
+
+ if ( !m_pConstraint || !m_pConstraint->GetReferenceObject() || !m_pConstraint->GetAttachedObject() )
+ return;
+
+ m_pConstraint->Activate();
+ m_pConstraint->GetReferenceObject()->Wake();
+ m_pConstraint->GetAttachedObject()->Wake();
+}
+
+void CPhysConstraint::InputTurnOff( inputdata_t &inputdata )
+{
+ Deactivate();
+}
+
+int CPhysConstraint::DrawDebugTextOverlays()
+{
+ int pos = BaseClass::DrawDebugTextOverlays();
+ if ( m_pConstraint && (m_debugOverlays & OVERLAY_TEXT_BIT) )
+ {
+ constraint_breakableparams_t params;
+ Q_memset(&params,0,sizeof(params));
+ m_pConstraint->GetConstraintParams( &params );
+
+ if ( (params.bodyMassScale[0] != 1.0f && params.bodyMassScale[0] != 0.0f) || (params.bodyMassScale[1] != 1.0f && params.bodyMassScale[1] != 0.0f) )
+ {
+ CFmtStr str("mass ratio %.4f:%.4f\n", params.bodyMassScale[0], params.bodyMassScale[1] );
+ NDebugOverlay::EntityTextAtPosition( GetAbsOrigin(), pos, str.Access(), 0, 255, 255, 0, 255 );
+ }
+ pos++;
+ }
+ return pos;
+}
+
+void CPhysConstraint::DrawDebugGeometryOverlays()
+{
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
+ {
+ DrawConstraintObjectsAxes(this, m_pConstraint);
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+}
+
+void CPhysConstraint::GetBreakParams( constraint_breakableparams_t &params, const hl_constraint_info_t &info )
+{
+ params.Defaults();
+ params.forceLimit = lbs2kg(m_forceLimit);
+ params.torqueLimit = lbs2kg(m_torqueLimit);
+ params.isActive = HasSpawnFlags( SF_CONSTRAINT_START_INACTIVE ) ? false : true;
+ params.bodyMassScale[0] = info.massScale[0];
+ params.bodyMassScale[1] = info.massScale[1];
+}
+
+BEGIN_DATADESC( CPhysConstraint )
+
+ DEFINE_PHYSPTR( m_pConstraint ),
+
+ DEFINE_KEYFIELD( m_nameSystem, FIELD_STRING, "constraintsystem" ),
+ DEFINE_KEYFIELD( m_nameAttach1, FIELD_STRING, "attach1" ),
+ DEFINE_KEYFIELD( m_nameAttach2, FIELD_STRING, "attach2" ),
+ DEFINE_KEYFIELD( m_breakSound, FIELD_SOUNDNAME, "breaksound" ),
+ DEFINE_KEYFIELD( m_forceLimit, FIELD_FLOAT, "forcelimit" ),
+ DEFINE_KEYFIELD( m_torqueLimit, FIELD_FLOAT, "torquelimit" ),
+ DEFINE_KEYFIELD( m_minTeleportDistance, FIELD_FLOAT, "teleportfollowdistance" ),
+// DEFINE_FIELD( m_teleportTick, FIELD_INTEGER ),
+
+ DEFINE_OUTPUT( m_OnBreak, "OnBreak" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputOnBreak ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
+
+END_DATADESC()
+
+
+CPhysConstraint::CPhysConstraint( void )
+{
+ m_pConstraint = NULL;
+ m_nameAttach1 = NULL_STRING;
+ m_nameAttach2 = NULL_STRING;
+ m_forceLimit = 0;
+ m_torqueLimit = 0;
+ m_teleportTick = 0xFFFFFFFF;
+ m_minTeleportDistance = 0.0f;
+}
+
+CPhysConstraint::~CPhysConstraint()
+{
+ Deactivate();
+ physenv->DestroyConstraint( m_pConstraint );
+}
+
+void CPhysConstraint::Precache( void )
+{
+ if ( m_breakSound != NULL_STRING )
+ {
+ PrecacheScriptSound( STRING(m_breakSound) );
+ }
+}
+
+void CPhysConstraint::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ Precache();
+}
+
+// debug function - slow, uses dynamic_cast<> - use this to query the attached objects
+// physics_debug_entity toggles the constraint system for an object using this
+bool GetConstraintAttachments( CBaseEntity *pEntity, CBaseEntity *pAttachOut[2], IPhysicsObject *pAttachVPhysics[2] )
+{
+ CPhysConstraint *pConstraintEntity = dynamic_cast<CPhysConstraint *>(pEntity);
+ if ( pConstraintEntity )
+ {
+ IPhysicsConstraint *pConstraint = pConstraintEntity->GetPhysConstraint();
+ if ( pConstraint )
+ {
+ IPhysicsObject *pRef = pConstraint->GetReferenceObject();
+ pAttachVPhysics[0] = pRef;
+ pAttachOut[0] = pRef ? static_cast<CBaseEntity *>(pRef->GetGameData()) : NULL;
+ IPhysicsObject *pAttach = pConstraint->GetAttachedObject();
+ pAttachVPhysics[1] = pAttach;
+ pAttachOut[1] = pAttach ? static_cast<CBaseEntity *>(pAttach->GetGameData()) : NULL;
+ return true;
+ }
+ }
+ return false;
+}
+
+void DebugConstraint(CBaseEntity *pEntity)
+{
+ CPhysConstraint *pConstraintEntity = dynamic_cast<CPhysConstraint *>(pEntity);
+ if ( pConstraintEntity )
+ {
+ IPhysicsConstraint *pConstraint = pConstraintEntity->GetPhysConstraint();
+ if ( pConstraint )
+ {
+ pConstraint->OutputDebugInfo();
+ }
+ }
+}
+
+
+void FindPhysicsAnchor( string_t name, hl_constraint_info_t &info, int index, CBaseEntity *pErrorEntity )
+{
+ constraint_anchor_t *pAnchor = g_AnchorList.Find( name );
+ if ( pAnchor )
+ {
+ CBaseEntity *pEntity = pAnchor->hEntity;
+ if ( pEntity )
+ {
+ info.massScale[index] = pAnchor->massScale;
+ bool bWroteAttachment = false;
+ if ( pAnchor->parentAttachment > 0 )
+ {
+ CBaseAnimating *pAnim = pAnchor->hEntity->GetBaseAnimating();
+ if ( pAnim )
+ {
+ IPhysicsObject *list[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int listCount = pAnchor->hEntity->VPhysicsGetObjectList( list, ARRAYSIZE(list) );
+ int iPhysicsBone = pAnim->GetPhysicsBone( pAnim->GetAttachmentBone( pAnchor->parentAttachment ) );
+ if ( iPhysicsBone < listCount )
+ {
+ Vector pos;
+ info.pObjects[index] = list[iPhysicsBone];
+ pAnim->GetAttachment( pAnchor->parentAttachment, pos );
+ list[iPhysicsBone]->WorldToLocal( &info.anchorPosition[index], pos );
+ bWroteAttachment = true;
+ }
+ }
+ }
+ if ( !bWroteAttachment )
+ {
+ info.anchorPosition[index] = pAnchor->localOrigin;
+ info.pObjects[index] = pAnchor->hEntity->VPhysicsGetObject();
+ }
+ }
+ else
+ {
+ pAnchor = NULL;
+ }
+ }
+ if ( !pAnchor )
+ {
+ info.anchorPosition[index] = vec3_origin;
+ info.pObjects[index] = FindPhysicsObjectByName( STRING(name), pErrorEntity );
+ info.massScale[index] = 1.0f;
+ }
+}
+
+void CPhysConstraint::OnConstraintSetup( hl_constraint_info_t &info )
+{
+ if ( info.pObjects[0] && info.pObjects[1] )
+ {
+ SetupTeleportationHandling( info );
+ }
+ if ( m_spawnflags & SF_CONSTRAINT_DISABLE_COLLISION )
+ {
+ PhysDisableEntityCollisions( info.pObjects[0], info.pObjects[1] );
+ }
+}
+
+void CPhysConstraint::SetupTeleportationHandling( hl_constraint_info_t &info )
+{
+ CBaseEntity *pEntity0 = (CBaseEntity *)info.pObjects[0]->GetGameData();
+ if ( pEntity0 )
+ {
+ g_pNotify->AddEntity( this, pEntity0 );
+ }
+
+ CBaseEntity *pEntity1 = (CBaseEntity *)info.pObjects[1]->GetGameData();
+ if ( pEntity1 )
+ {
+ g_pNotify->AddEntity( this, pEntity1 );
+ }
+}
+
+static IPhysicsConstraintGroup *GetRagdollConstraintGroup( IPhysicsObject *pObj )
+{
+ if ( pObj )
+ {
+ CBaseEntity *pEntity = static_cast<CBaseEntity *>(pObj->GetGameData());
+ ragdoll_t *pRagdoll = Ragdoll_GetRagdoll(pEntity);
+ if ( pRagdoll )
+ return pRagdoll->pGroup;
+ }
+ return NULL;
+}
+
+void CPhysConstraint::GetConstraintObjects( hl_constraint_info_t &info )
+{
+ FindPhysicsAnchor( m_nameAttach1, info, 0, this );
+ FindPhysicsAnchor( m_nameAttach2, info, 1, this );
+
+ // Missing one object, assume the world instead
+ if ( info.pObjects[0] == NULL && info.pObjects[1] )
+ {
+ if ( Q_strlen(STRING(m_nameAttach1)) )
+ {
+ Warning("Bogus constraint %s (attaches ENTITY NOT FOUND:%s to %s)\n", GetDebugName(), STRING(m_nameAttach1), STRING(m_nameAttach2));
+#ifdef HL2_EPISODIC
+ info.pObjects[0] = info.pObjects[1] = NULL;
+ return;
+#endif // HL2_EPISODIC
+ }
+ info.pObjects[0] = g_PhysWorldObject;
+ info.massScale[0] = info.massScale[1] = 1.0f; // no mass scale on world constraint
+ }
+ else if ( info.pObjects[0] && !info.pObjects[1] )
+ {
+ if ( Q_strlen(STRING(m_nameAttach2)) )
+ {
+ Warning("Bogus constraint %s (attaches %s to ENTITY NOT FOUND:%s)\n", GetDebugName(), STRING(m_nameAttach1), STRING(m_nameAttach2));
+#ifdef HL2_EPISODIC
+ info.pObjects[0] = info.pObjects[1] = NULL;
+ return;
+#endif // HL2_EPISODIC
+ }
+ info.pObjects[1] = info.pObjects[0];
+ info.pObjects[0] = g_PhysWorldObject; // Try to make the world object consistently object0 for ease of implementation
+ info.massScale[0] = info.massScale[1] = 1.0f; // no mass scale on world constraint
+ info.swapped = true;
+ }
+
+ info.pGroup = GetRagdollConstraintGroup(info.pObjects[0]);
+ if ( !info.pGroup )
+ {
+ info.pGroup = GetRagdollConstraintGroup(info.pObjects[1]);
+ }
+}
+
+void CPhysConstraint::Activate( void )
+{
+ BaseClass::Activate();
+
+ if ( HasSpawnFlags( SF_CONSTRAINT_NO_CONNECT_UNTIL_ACTIVATED ) == false )
+ {
+ if ( !ActivateConstraint() )
+ {
+ UTIL_Remove(this);
+ }
+ }
+}
+
+IPhysicsConstraintGroup *GetConstraintGroup( string_t systemName )
+{
+ CBaseEntity *pMachine = gEntList.FindEntityByName( NULL, systemName );
+
+ if ( pMachine )
+ {
+ CPhysConstraintSystem *pGroup = dynamic_cast<CPhysConstraintSystem *>(pMachine);
+ if ( pGroup )
+ {
+ return pGroup->GetVPhysicsGroup();
+ }
+ }
+ return NULL;
+}
+
+bool CPhysConstraint::ActivateConstraint( void )
+{
+ // A constraint attaches two objects to each other.
+ // The constraint is specified in the coordinate frame of the "reference" object
+ // and constrains the "attached" object
+ hl_constraint_info_t info;
+ if ( m_pConstraint )
+ {
+ // already have a constraint, don't make a new one
+ info.pObjects[0] = m_pConstraint->GetReferenceObject();
+ info.pObjects[1] = m_pConstraint->GetAttachedObject();
+ OnConstraintSetup(info);
+ return true;
+ }
+
+ GetConstraintObjects( info );
+ if ( !info.pObjects[0] && !info.pObjects[1] )
+ return false;
+
+ if ( info.pObjects[0]->IsStatic() && info.pObjects[1]->IsStatic() )
+ {
+ Warning("Constraint (%s) attached to two static objects (%s and %s)!!!\n", STRING(GetEntityName()), STRING(m_nameAttach1), m_nameAttach2 == NULL_STRING ? "world" : STRING(m_nameAttach2) );
+ return false;
+ }
+
+ if ( info.pObjects[0]->GetShadowController() && info.pObjects[1]->GetShadowController() )
+ {
+ Warning("Constraint (%s) attached to two shadow objects (%s and %s)!!!\n", STRING(GetEntityName()), STRING(m_nameAttach1), m_nameAttach2 == NULL_STRING ? "world" : STRING(m_nameAttach2) );
+ return false;
+ }
+ IPhysicsConstraintGroup *pGroup = GetConstraintGroup( m_nameSystem );
+ if ( !pGroup )
+ {
+ pGroup = info.pGroup;
+ }
+ m_pConstraint = CreateConstraint( pGroup, info );
+ if ( !m_pConstraint )
+ return false;
+
+ m_pConstraint->SetGameData( (void *)this );
+
+ if ( pGroup )
+ {
+ pGroup->Activate();
+ }
+
+ OnConstraintSetup(info);
+
+ return true;
+}
+
+void CPhysConstraint::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
+{
+ // don't recurse
+ if ( eventType != NOTIFY_EVENT_TELEPORT || (unsigned int)gpGlobals->tickcount == m_teleportTick )
+ return;
+
+ float distance = (params.pTeleport->prevOrigin - pNotify->GetAbsOrigin()).Length();
+
+ // no need to follow a small teleport
+ if ( distance <= m_minTeleportDistance )
+ return;
+
+ m_teleportTick = gpGlobals->tickcount;
+
+ PhysTeleportConstrainedEntity( pNotify, m_pConstraint->GetReferenceObject(), m_pConstraint->GetAttachedObject(), params.pTeleport->prevOrigin, params.pTeleport->prevAngles, params.pTeleport->physicsRotate );
+}
+
+class CPhysHinge : public CPhysConstraint, public IVPhysicsWatcher
+{
+ DECLARE_CLASS( CPhysHinge, CPhysConstraint );
+
+public:
+ void Spawn( void );
+ IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+ {
+ if ( m_hinge.worldAxisDirection == vec3_origin )
+ {
+ DevMsg("ERROR: Hinge with bad data!!!\n" );
+ return NULL;
+ }
+ GetBreakParams( m_hinge.constraint, info );
+ m_hinge.constraint.strength = 1.0;
+ // BUGBUG: These numbers are very hard to edit
+ // Scale by 1000 to make things easier
+ // CONSIDER: Unify the units of torque around something other
+ // than HL units (kg * in^2 / s ^2)
+ m_hinge.hingeAxis.SetAxisFriction( 0, 0, m_hingeFriction * 1000 );
+
+ int hingeAxis;
+ if ( IsWorldHinge( info, &hingeAxis ) )
+ {
+ info.pObjects[1]->BecomeHinged( hingeAxis );
+ }
+ else
+ {
+ RemoveSpawnFlags( SF_CONSTRAINT_ASSUME_WORLD_GEOMETRY );
+ }
+
+ return physenv->CreateHingeConstraint( info.pObjects[0], info.pObjects[1], pGroup, m_hinge );
+ }
+
+ void DrawDebugGeometryOverlays()
+ {
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
+ {
+ NDebugOverlay::Line(m_hinge.worldPosition, m_hinge.worldPosition + 48 * m_hinge.worldAxisDirection, 0, 255, 0, false, 0 );
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+ }
+
+ void InputSetVelocity( inputdata_t &inputdata )
+ {
+ if ( !m_pConstraint || !m_pConstraint->GetReferenceObject() || !m_pConstraint->GetAttachedObject() )
+ return;
+
+ float speed = inputdata.value.Float();
+ float massLoad = 1;
+ int numMasses = 0;
+ if ( m_pConstraint->GetReferenceObject()->IsMoveable() )
+ {
+ massLoad = m_pConstraint->GetReferenceObject()->GetInertia().Length();
+ numMasses++;
+ m_pConstraint->GetReferenceObject()->Wake();
+ }
+ if ( m_pConstraint->GetAttachedObject()->IsMoveable() )
+ {
+ massLoad += m_pConstraint->GetAttachedObject()->GetInertia().Length();
+ numMasses++;
+ m_pConstraint->GetAttachedObject()->Wake();
+ }
+ if ( numMasses > 0 )
+ {
+ massLoad /= (float)numMasses;
+ }
+
+ float loadscale = m_systemLoadScale != 0 ? m_systemLoadScale : 1;
+ m_pConstraint->SetAngularMotor( speed, speed * loadscale * massLoad * loadscale * (1.0/TICK_INTERVAL) );
+ }
+
+ void InputSetHingeFriction( inputdata_t &inputdata )
+ {
+ m_hingeFriction = inputdata.value.Float();
+ Msg("Setting hinge friction to %f\n", m_hingeFriction );
+ m_hinge.hingeAxis.SetAxisFriction( 0, 0, m_hingeFriction * 1000 );
+ }
+
+ virtual void Deactivate()
+ {
+ if ( HasSpawnFlags( SF_CONSTRAINT_ASSUME_WORLD_GEOMETRY ) )
+ {
+ if ( m_pConstraint && m_pConstraint->GetAttachedObject() )
+ {
+ // NOTE: RemoveHinged() is always safe
+ m_pConstraint->GetAttachedObject()->RemoveHinged();
+ }
+ }
+
+ BaseClass::Deactivate();
+ }
+
+ void NotifyVPhysicsStateChanged( IPhysicsObject *pPhysics, CBaseEntity *pEntity, bool bAwake )
+ {
+#if HINGE_NOTIFY
+ Assert(m_pConstraint);
+ if (!m_pConstraint)
+ return;
+
+ // if something woke up, start thinking. If everything is asleep, stop thinking.
+ if ( bAwake )
+ {
+ // Did something wake up when I was not thinking?
+ if ( GetNextThink() == TICK_NEVER_THINK )
+ {
+ m_soundInfo.StartThinking(this,
+ VelocitySampler::GetRelativeAngularVelocity(m_pConstraint->GetAttachedObject(), m_pConstraint->GetReferenceObject()) ,
+ m_hinge.worldAxisDirection
+ );
+
+ SetThink(&CPhysHinge::SoundThink);
+ SetNextThink(gpGlobals->curtime + m_soundInfo.getThinkRate());
+ }
+ }
+ else
+ {
+ // Is everything asleep? If so, stop thinking.
+ if ( GetNextThink() != TICK_NEVER_THINK &&
+ m_pConstraint->GetAttachedObject()->IsAsleep() &&
+ m_pConstraint->GetReferenceObject()->IsAsleep() )
+ {
+ m_soundInfo.StopThinking(this);
+ SetNextThink(TICK_NEVER_THINK);
+ }
+ }
+#endif
+ }
+
+
+#if HINGE_NOTIFY
+ virtual void OnConstraintSetup( hl_constraint_info_t &info )
+ {
+ CBaseEntity *pEntity0 = info.pObjects[0] ? static_cast<CBaseEntity *>(info.pObjects[0]->GetGameData()) : NULL;
+ if ( pEntity0 && !info.pObjects[0]->IsStatic() )
+ {
+ WatchVPhysicsStateChanges( this, pEntity0 );
+ }
+ CBaseEntity *pEntity1 = info.pObjects[1] ? static_cast<CBaseEntity *>(info.pObjects[1]->GetGameData()) : NULL;
+ if ( pEntity1 && !info.pObjects[1]->IsStatic() )
+ {
+ WatchVPhysicsStateChanges( this, pEntity1 );
+ }
+ BaseClass::OnConstraintSetup(info);
+ }
+
+ void SoundThink( void );
+ // void Spawn( void );
+ void Activate( void );
+ void Precache( void );
+#endif
+
+ DECLARE_DATADESC();
+
+
+#if HINGE_NOTIFY
+protected:
+ ConstraintSoundInfo m_soundInfo;
+#endif
+
+private:
+ constraint_hingeparams_t m_hinge;
+ float m_hingeFriction;
+ float m_systemLoadScale;
+ bool IsWorldHinge( const hl_constraint_info_t &info, int *pAxisOut );
+};
+
+BEGIN_DATADESC( CPhysHinge )
+
+// Quiet down classcheck
+// DEFINE_FIELD( m_hinge, FIELD_??? ),
+
+ DEFINE_KEYFIELD( m_hingeFriction, FIELD_FLOAT, "hingefriction" ),
+ DEFINE_FIELD( m_hinge.worldPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_KEYFIELD( m_hinge.worldAxisDirection, FIELD_VECTOR, "hingeaxis" ),
+ DEFINE_KEYFIELD( m_systemLoadScale, FIELD_FLOAT, "systemloadscale" ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetAngularVelocity", InputSetVelocity ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHingeFriction", InputSetHingeFriction ),
+
+#if HINGE_NOTIFY
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_keyPoints[SimpleConstraintSoundProfile::kMIN_THRESHOLD] , FIELD_FLOAT, "minSoundThreshold" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_keyPoints[SimpleConstraintSoundProfile::kMIN_FULL] , FIELD_FLOAT, "maxSoundThreshold" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszTravelSoundFwd, FIELD_SOUNDNAME, "slidesoundfwd" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszTravelSoundBack, FIELD_SOUNDNAME, "slidesoundback" ),
+
+ DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSounds[0], FIELD_SOUNDNAME, "reversalsoundSmall" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSounds[1], FIELD_SOUNDNAME, "reversalsoundMedium" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSounds[2], FIELD_SOUNDNAME, "reversalsoundLarge" ),
+
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThresholds[0] , FIELD_FLOAT, "reversalsoundthresholdSmall" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThresholds[1], FIELD_FLOAT, "reversalsoundthresholdMedium" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThresholds[2] , FIELD_FLOAT, "reversalsoundthresholdLarge" ),
+
+ DEFINE_THINKFUNC( SoundThink ),
+#endif
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( phys_hinge, CPhysHinge );
+
+
+void CPhysHinge::Spawn( void )
+{
+ m_hinge.worldPosition = GetLocalOrigin();
+ m_hinge.worldAxisDirection -= GetLocalOrigin();
+ VectorNormalize(m_hinge.worldAxisDirection);
+ UTIL_SnapDirectionToAxis( m_hinge.worldAxisDirection );
+
+ m_hinge.hingeAxis.SetAxisFriction( 0, 0, 0 );
+
+ if ( HasSpawnFlags( SF_CONSTRAINT_ASSUME_WORLD_GEOMETRY ) )
+ {
+ masscenteroverride_t params;
+ if ( m_nameAttach1 == NULL_STRING )
+ {
+ params.SnapToAxis( m_nameAttach2, m_hinge.worldPosition, m_hinge.worldAxisDirection );
+ PhysSetMassCenterOverride( params );
+ }
+ else if ( m_nameAttach2 == NULL_STRING )
+ {
+ params.SnapToAxis( m_nameAttach1, m_hinge.worldPosition, m_hinge.worldAxisDirection );
+ PhysSetMassCenterOverride( params );
+ }
+ else
+ {
+ RemoveSpawnFlags( SF_CONSTRAINT_ASSUME_WORLD_GEOMETRY );
+ }
+ }
+
+ Precache();
+}
+
+#if HINGE_NOTIFY
+void CPhysHinge::Activate( void )
+{
+ BaseClass::Activate();
+
+ m_soundInfo.OnActivate(this);
+ if (m_pConstraint)
+ {
+ m_soundInfo.StartThinking(this,
+ VelocitySampler::GetRelativeAngularVelocity(m_pConstraint->GetAttachedObject(), m_pConstraint->GetReferenceObject()) ,
+ m_hinge.worldAxisDirection
+ );
+
+ SetThink(&CPhysHinge::SoundThink);
+ SetNextThink( gpGlobals->curtime + m_soundInfo.getThinkRate() );
+ }
+}
+
+void CPhysHinge::Precache( void )
+{
+ BaseClass::Precache();
+ return m_soundInfo.OnPrecache(this);
+}
+
+#endif
+
+
+static int GetUnitAxisIndex( const Vector &axis )
+{
+ bool valid = false;
+ int index = -1;
+
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( axis[i] != 0 )
+ {
+ if ( fabs(axis[i]) == 1 )
+ {
+ if ( index < 0 )
+ {
+ index = i;
+ valid = true;
+ continue;
+ }
+ }
+ valid = false;
+ }
+ }
+ return valid ? index : -1;
+}
+
+bool CPhysHinge::IsWorldHinge( const hl_constraint_info_t &info, int *pAxisOut )
+{
+ if ( HasSpawnFlags( SF_CONSTRAINT_ASSUME_WORLD_GEOMETRY ) && info.pObjects[0] == g_PhysWorldObject )
+ {
+ Vector localHinge;
+ info.pObjects[1]->WorldToLocalVector( &localHinge, m_hinge.worldAxisDirection );
+ UTIL_SnapDirectionToAxis( localHinge );
+ int hingeAxis = GetUnitAxisIndex( localHinge );
+ if ( hingeAxis >= 0 )
+ {
+ *pAxisOut = hingeAxis;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+#if HINGE_NOTIFY
+void CPhysHinge::SoundThink( void )
+{
+ Assert(m_pConstraint);
+ if (!m_pConstraint)
+ return;
+
+ IPhysicsObject * pAttached = m_pConstraint->GetAttachedObject(), *pReference = m_pConstraint->GetReferenceObject();
+ Assert( pAttached && pReference );
+ if (pAttached && pReference)
+ {
+ Vector relativeVel = VelocitySampler::GetRelativeAngularVelocity(pAttached,pReference);
+ if (g_debug_constraint_sounds.GetBool())
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (relativeVel), 255, 255, 0, true, 0.1f );
+ }
+ m_soundInfo.OnThink( this, relativeVel );
+
+ SetNextThink(gpGlobals->curtime + m_soundInfo.getThinkRate());
+ }
+}
+#endif
+
+class CPhysBallSocket : public CPhysConstraint
+{
+public:
+ DECLARE_CLASS( CPhysBallSocket, CPhysConstraint );
+
+ IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+ {
+ constraint_ballsocketparams_t ballsocket;
+
+ ballsocket.Defaults();
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ info.pObjects[i]->WorldToLocal( &ballsocket.constraintPosition[i], GetAbsOrigin() );
+ }
+ GetBreakParams( ballsocket.constraint, info );
+ ballsocket.constraint.torqueLimit = 0;
+
+ return physenv->CreateBallsocketConstraint( info.pObjects[0], info.pObjects[1], pGroup, ballsocket );
+ }
+};
+
+LINK_ENTITY_TO_CLASS( phys_ballsocket, CPhysBallSocket );
+
+class CPhysSlideConstraint : public CPhysConstraint, public IVPhysicsWatcher
+{
+public:
+ DECLARE_CLASS( CPhysSlideConstraint, CPhysConstraint );
+
+ DECLARE_DATADESC();
+ IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info );
+ void InputSetVelocity( inputdata_t &inputdata )
+ {
+ if ( !m_pConstraint || !m_pConstraint->GetReferenceObject() || !m_pConstraint->GetAttachedObject() )
+ return;
+
+ float speed = inputdata.value.Float();
+ float massLoad = 1;
+ int numMasses = 0;
+ if ( m_pConstraint->GetReferenceObject()->IsMoveable() )
+ {
+ massLoad = m_pConstraint->GetReferenceObject()->GetMass();
+ numMasses++;
+ m_pConstraint->GetReferenceObject()->Wake();
+ }
+ if ( m_pConstraint->GetAttachedObject()->IsMoveable() )
+ {
+ massLoad += m_pConstraint->GetAttachedObject()->GetMass();
+ numMasses++;
+ m_pConstraint->GetAttachedObject()->Wake();
+ }
+ if ( numMasses > 0 )
+ {
+ massLoad /= (float)numMasses;
+ }
+ float loadscale = m_systemLoadScale != 0 ? m_systemLoadScale : 1;
+ m_pConstraint->SetLinearMotor( speed, speed * loadscale * massLoad * (1.0/TICK_INTERVAL) );
+ }
+
+ void DrawDebugGeometryOverlays()
+ {
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
+ {
+ NDebugOverlay::Box( GetAbsOrigin(), -Vector(8,8,8), Vector(8,8,8), 0, 255, 0, 0, 0 );
+ NDebugOverlay::Box( m_axisEnd, -Vector(4,4,4), Vector(4,4,4), 0, 0, 255, 0, 0 );
+ NDebugOverlay::Line( GetAbsOrigin(), m_axisEnd, 255, 255, 0, false, 0 );
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+ }
+
+ void NotifyVPhysicsStateChanged( IPhysicsObject *pPhysics, CBaseEntity *pEntity, bool bAwake )
+ {
+#if HINGE_NOTIFY
+ Assert(m_pConstraint);
+ if (!m_pConstraint)
+ return;
+
+ // if something woke up, start thinking. If everything is asleep, stop thinking.
+ if ( bAwake )
+ {
+ // Did something wake up when I was not thinking?
+ if ( GetNextThink() == TICK_NEVER_THINK )
+ {
+ Vector axisDirection = m_axisEnd - GetAbsOrigin();
+ VectorNormalize( axisDirection );
+ UTIL_SnapDirectionToAxis( axisDirection );
+
+ m_soundInfo.StartThinking(this,
+ VelocitySampler::GetRelativeVelocity(m_pConstraint->GetAttachedObject(), m_pConstraint->GetReferenceObject()),
+ axisDirection
+ );
+ SetThink(&CPhysSlideConstraint::SoundThink);
+ SetNextThink(gpGlobals->curtime + m_soundInfo.getThinkRate());
+ }
+ }
+ else
+ {
+ // Is everything asleep? If so, stop thinking.
+ if ( GetNextThink() != TICK_NEVER_THINK &&
+ m_pConstraint->GetAttachedObject()->IsAsleep() &&
+ m_pConstraint->GetReferenceObject()->IsAsleep() )
+ {
+ m_soundInfo.StopThinking(this);
+ SetNextThink(TICK_NEVER_THINK);
+ }
+ }
+#endif
+ }
+
+
+#if HINGE_NOTIFY
+ virtual void OnConstraintSetup( hl_constraint_info_t &info )
+ {
+ CBaseEntity *pEntity0 = info.pObjects[0] ? static_cast<CBaseEntity *>(info.pObjects[0]->GetGameData()) : NULL;
+ if ( pEntity0 && !info.pObjects[0]->IsStatic() )
+ {
+ WatchVPhysicsStateChanges( this, pEntity0 );
+ }
+ CBaseEntity *pEntity1 = info.pObjects[1] ? static_cast<CBaseEntity *>(info.pObjects[1]->GetGameData()) : NULL;
+ if ( pEntity1 && !info.pObjects[1]->IsStatic() )
+ {
+ WatchVPhysicsStateChanges( this, pEntity1 );
+ }
+ BaseClass::OnConstraintSetup(info);
+ }
+
+
+ void SoundThink( void );
+ // void Spawn( void );
+ void Activate( void );
+ void Precache( void );
+#endif
+
+ Vector m_axisEnd;
+ float m_slideFriction;
+ float m_systemLoadScale;
+
+#if HINGE_NOTIFY
+protected:
+ ConstraintSoundInfo m_soundInfo;
+#endif
+};
+
+LINK_ENTITY_TO_CLASS( phys_slideconstraint, CPhysSlideConstraint );
+
+BEGIN_DATADESC( CPhysSlideConstraint )
+
+ DEFINE_KEYFIELD( m_axisEnd, FIELD_POSITION_VECTOR, "slideaxis" ),
+ DEFINE_KEYFIELD( m_slideFriction, FIELD_FLOAT, "slidefriction" ),
+ DEFINE_KEYFIELD( m_systemLoadScale, FIELD_FLOAT, "systemloadscale" ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetVelocity", InputSetVelocity ),
+#if HINGE_NOTIFY
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_keyPoints[SimpleConstraintSoundProfile::kMIN_THRESHOLD] , FIELD_FLOAT, "minSoundThreshold" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_keyPoints[SimpleConstraintSoundProfile::kMIN_FULL] , FIELD_FLOAT, "maxSoundThreshold" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszTravelSoundFwd, FIELD_SOUNDNAME, "slidesoundfwd" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszTravelSoundBack, FIELD_SOUNDNAME, "slidesoundback" ),
+
+ DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSounds[0], FIELD_SOUNDNAME, "reversalsoundSmall" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSounds[1], FIELD_SOUNDNAME, "reversalsoundMedium" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_iszReversalSounds[2], FIELD_SOUNDNAME, "reversalsoundLarge" ),
+
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThresholds[0] , FIELD_FLOAT, "reversalsoundthresholdSmall" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThresholds[1], FIELD_FLOAT, "reversalsoundthresholdMedium" ),
+ DEFINE_KEYFIELD( m_soundInfo.m_soundProfile.m_reversalSoundThresholds[2] , FIELD_FLOAT, "reversalsoundthresholdLarge" ),
+
+
+ DEFINE_THINKFUNC( SoundThink ),
+#endif
+
+END_DATADESC()
+
+
+
+IPhysicsConstraint *CPhysSlideConstraint::CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+{
+ constraint_slidingparams_t sliding;
+ sliding.Defaults();
+ GetBreakParams( sliding.constraint, info );
+ sliding.constraint.strength = 1.0;
+
+ Vector axisDirection = m_axisEnd - GetAbsOrigin();
+ VectorNormalize( axisDirection );
+ UTIL_SnapDirectionToAxis( axisDirection );
+
+ sliding.InitWithCurrentObjectState( info.pObjects[0], info.pObjects[1], axisDirection );
+ sliding.friction = m_slideFriction;
+ if ( m_spawnflags & SF_SLIDE_LIMIT_ENDS )
+ {
+ Vector position;
+ info.pObjects[1]->GetPosition( &position, NULL );
+
+ sliding.limitMin = DotProduct( axisDirection, GetAbsOrigin() );
+ sliding.limitMax = DotProduct( axisDirection, m_axisEnd );
+ if ( sliding.limitMax < sliding.limitMin )
+ {
+ ::V_swap( sliding.limitMin, sliding.limitMax );
+ }
+
+ // expand limits to make initial position of the attached object valid
+ float limit = DotProduct( position, axisDirection );
+ if ( limit < sliding.limitMin )
+ {
+ sliding.limitMin = limit;
+ }
+ else if ( limit > sliding.limitMax )
+ {
+ sliding.limitMax = limit;
+ }
+ // offset so that the current position is 0
+ sliding.limitMin -= limit;
+ sliding.limitMax -= limit;
+ }
+
+ return physenv->CreateSlidingConstraint( info.pObjects[0], info.pObjects[1], pGroup, sliding );
+}
+
+
+#if HINGE_NOTIFY
+void CPhysSlideConstraint::SoundThink( void )
+{
+ Assert(m_pConstraint);
+ if (!m_pConstraint)
+ return;
+
+ IPhysicsObject * pAttached = m_pConstraint->GetAttachedObject(), *pReference = m_pConstraint->GetReferenceObject();
+ Assert( pAttached && pReference );
+ if (pAttached && pReference)
+ {
+ Vector relativeVel = VelocitySampler::GetRelativeVelocity(pAttached,pReference);
+ // project velocity onto my primary axis.:
+
+ Vector axisDirection = m_axisEnd - GetAbsOrigin();
+ relativeVel = m_axisEnd * relativeVel.Dot(m_axisEnd)/m_axisEnd.Dot(m_axisEnd);
+
+ m_soundInfo.OnThink( this, relativeVel );
+
+ SetNextThink(gpGlobals->curtime + m_soundInfo.getThinkRate());
+ }
+
+}
+
+void CPhysSlideConstraint::Activate( void )
+{
+ BaseClass::Activate();
+
+ m_soundInfo.OnActivate(this);
+
+ Vector axisDirection = m_axisEnd - GetAbsOrigin();
+ VectorNormalize( axisDirection );
+ UTIL_SnapDirectionToAxis( axisDirection );
+ m_soundInfo.StartThinking(this,
+ VelocitySampler::GetRelativeVelocity(m_pConstraint->GetAttachedObject(), m_pConstraint->GetReferenceObject()),
+ axisDirection
+ );
+
+ SetThink(&CPhysSlideConstraint::SoundThink);
+ SetNextThink(gpGlobals->curtime + m_soundInfo.getThinkRate());
+}
+
+void CPhysSlideConstraint::Precache()
+{
+ m_soundInfo.OnPrecache(this);
+}
+
+#endif
+
+
+
+LINK_ENTITY_TO_CLASS( phys_constraint, CPhysFixed );
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate/create the constraint
+//-----------------------------------------------------------------------------
+IPhysicsConstraint *CPhysFixed::CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+{
+ constraint_fixedparams_t fixed;
+ fixed.Defaults();
+ fixed.InitWithCurrentObjectState( info.pObjects[0], info.pObjects[1] );
+ GetBreakParams( fixed.constraint, info );
+
+ // constraining to the world means object 1 is fixed
+ if ( info.pObjects[0] == g_PhysWorldObject )
+ {
+ PhysSetGameFlags( info.pObjects[1], FVPHYSICS_CONSTRAINT_STATIC );
+ }
+
+ return physenv->CreateFixedConstraint( info.pObjects[0], info.pObjects[1], pGroup, fixed );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Breakable pulley w/ropes constraint
+//-----------------------------------------------------------------------------
+class CPhysPulley : public CPhysConstraint
+{
+ DECLARE_CLASS( CPhysPulley, CPhysConstraint );
+public:
+ DECLARE_DATADESC();
+
+ void DrawDebugGeometryOverlays()
+ {
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
+ {
+ Vector origin = GetAbsOrigin();
+ Vector refPos = origin, attachPos = origin;
+ IPhysicsObject *pRef = m_pConstraint->GetReferenceObject();
+ if ( pRef )
+ {
+ matrix3x4_t matrix;
+ pRef->GetPositionMatrix( &matrix );
+ VectorTransform( m_offset[0], matrix, refPos );
+ }
+ IPhysicsObject *pAttach = m_pConstraint->GetAttachedObject();
+ if ( pAttach )
+ {
+ matrix3x4_t matrix;
+ pAttach->GetPositionMatrix( &matrix );
+ VectorTransform( m_offset[1], matrix, attachPos );
+ }
+ NDebugOverlay::Line( refPos, origin, 0, 255, 0, false, 0 );
+ NDebugOverlay::Line( origin, m_position2, 128, 128, 128, false, 0 );
+ NDebugOverlay::Line( m_position2, attachPos, 0, 255, 0, false, 0 );
+ NDebugOverlay::Box( origin, -Vector(8,8,8), Vector(8,8,8), 128, 255, 128, 32, 0 );
+ NDebugOverlay::Box( m_position2, -Vector(8,8,8), Vector(8,8,8), 255, 128, 128, 32, 0 );
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+ }
+
+ IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info );
+
+private:
+ Vector m_position2;
+ Vector m_offset[2];
+ float m_addLength;
+ float m_gearRatio;
+};
+
+BEGIN_DATADESC( CPhysPulley )
+
+ DEFINE_KEYFIELD( m_position2, FIELD_POSITION_VECTOR, "position2" ),
+ DEFINE_AUTO_ARRAY( m_offset, FIELD_VECTOR ),
+ DEFINE_KEYFIELD( m_addLength, FIELD_FLOAT, "addlength" ),
+ DEFINE_KEYFIELD( m_gearRatio, FIELD_FLOAT, "gearratio" ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( phys_pulleyconstraint, CPhysPulley );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate/create the constraint
+//-----------------------------------------------------------------------------
+IPhysicsConstraint *CPhysPulley::CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+{
+ constraint_pulleyparams_t pulley;
+ pulley.Defaults();
+ pulley.pulleyPosition[0] = GetAbsOrigin();
+ pulley.pulleyPosition[1] = m_position2;
+
+ matrix3x4_t matrix;
+ Vector world[2];
+
+ info.pObjects[0]->GetPositionMatrix( &matrix );
+ VectorTransform( info.anchorPosition[0], matrix, world[0] );
+ info.pObjects[1]->GetPositionMatrix( &matrix );
+ VectorTransform( info.anchorPosition[1], matrix, world[1] );
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ pulley.objectPosition[i] = info.anchorPosition[i];
+ m_offset[i] = info.anchorPosition[i];
+ }
+
+ pulley.totalLength = m_addLength +
+ (world[0] - pulley.pulleyPosition[0]).Length() +
+ ((world[1] - pulley.pulleyPosition[1]).Length() * m_gearRatio);
+
+ if ( m_gearRatio != 0 )
+ {
+ pulley.gearRatio = m_gearRatio;
+ }
+ GetBreakParams( pulley.constraint, info );
+ if ( m_spawnflags & SF_PULLEY_RIGID )
+ {
+ pulley.isRigid = true;
+ }
+
+ return physenv->CreatePulleyConstraint( info.pObjects[0], info.pObjects[1], pGroup, pulley );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Breakable rope/length constraint
+//-----------------------------------------------------------------------------
+class CPhysLength : public CPhysConstraint
+{
+ DECLARE_CLASS( CPhysLength, CPhysConstraint );
+public:
+ DECLARE_DATADESC();
+
+ void DrawDebugGeometryOverlays()
+ {
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
+ {
+ Vector origin = GetAbsOrigin();
+ Vector refPos = origin, attachPos = origin;
+ IPhysicsObject *pRef = m_pConstraint->GetReferenceObject();
+ if ( pRef )
+ {
+ matrix3x4_t matrix;
+ pRef->GetPositionMatrix( &matrix );
+ VectorTransform( m_offset[0], matrix, refPos );
+ }
+ IPhysicsObject *pAttach = m_pConstraint->GetAttachedObject();
+ if ( pAttach )
+ {
+ matrix3x4_t matrix;
+ pAttach->GetPositionMatrix( &matrix );
+ VectorTransform( m_offset[1], matrix, attachPos );
+ }
+ Vector dir = attachPos - refPos;
+
+ float len = VectorNormalize(dir);
+ if ( len > m_totalLength )
+ {
+ Vector mid = refPos + dir * m_totalLength;
+ NDebugOverlay::Line( refPos, mid, 0, 255, 0, false, 0 );
+ NDebugOverlay::Line( mid, attachPos, 255, 0, 0, false, 0 );
+ }
+ else
+ {
+ NDebugOverlay::Line( refPos, attachPos, 0, 255, 0, false, 0 );
+ }
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+ }
+
+ IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info );
+
+private:
+ Vector m_offset[2];
+ Vector m_vecAttach;
+ float m_addLength;
+ float m_minLength;
+ float m_totalLength;
+};
+
+BEGIN_DATADESC( CPhysLength )
+
+ DEFINE_AUTO_ARRAY( m_offset, FIELD_VECTOR ),
+ DEFINE_KEYFIELD( m_addLength, FIELD_FLOAT, "addlength" ),
+ DEFINE_KEYFIELD( m_minLength, FIELD_FLOAT, "minlength" ),
+ DEFINE_KEYFIELD( m_vecAttach, FIELD_POSITION_VECTOR, "attachpoint" ),
+ DEFINE_FIELD( m_totalLength, FIELD_FLOAT ),
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( phys_lengthconstraint, CPhysLength );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate/create the constraint
+//-----------------------------------------------------------------------------
+IPhysicsConstraint *CPhysLength::CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+{
+ constraint_lengthparams_t length;
+ length.Defaults();
+ Vector position[2];
+ position[0] = GetAbsOrigin();
+ position[1] = m_vecAttach;
+ int index = info.swapped ? 1 : 0;
+ length.InitWorldspace( info.pObjects[0], info.pObjects[1], position[index], position[!index] );
+ length.totalLength += m_addLength;
+ length.minLength = m_minLength;
+ m_totalLength = length.totalLength;
+ if ( HasSpawnFlags(SF_LENGTH_RIGID) )
+ {
+ length.minLength = length.totalLength;
+ }
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ m_offset[i] = length.objectPosition[i];
+ }
+ GetBreakParams( length.constraint, info );
+
+ return physenv->CreateLengthConstraint( info.pObjects[0], info.pObjects[1], pGroup, length );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Limited ballsocket constraint with toggle-able translation constraints
+//-----------------------------------------------------------------------------
+class CRagdollConstraint : public CPhysConstraint
+{
+ DECLARE_CLASS( CRagdollConstraint, CPhysConstraint );
+public:
+ DECLARE_DATADESC();
+#if 0
+ void DrawDebugGeometryOverlays()
+ {
+ if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
+ {
+ NDebugOverlay::Line( refPos, attachPos, 0, 255, 0, false, 0 );
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+ }
+#endif
+
+ IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info );
+
+private:
+ float m_xmin; // constraint limits in degrees
+ float m_xmax;
+ float m_ymin;
+ float m_ymax;
+ float m_zmin;
+ float m_zmax;
+
+ float m_xfriction;
+ float m_yfriction;
+ float m_zfriction;
+};
+
+BEGIN_DATADESC( CRagdollConstraint )
+
+ DEFINE_KEYFIELD( m_xmin, FIELD_FLOAT, "xmin" ),
+ DEFINE_KEYFIELD( m_xmax, FIELD_FLOAT, "xmax" ),
+ DEFINE_KEYFIELD( m_ymin, FIELD_FLOAT, "ymin" ),
+ DEFINE_KEYFIELD( m_ymax, FIELD_FLOAT, "ymax" ),
+ DEFINE_KEYFIELD( m_zmin, FIELD_FLOAT, "zmin" ),
+ DEFINE_KEYFIELD( m_zmax, FIELD_FLOAT, "zmax" ),
+ DEFINE_KEYFIELD( m_xfriction, FIELD_FLOAT, "xfriction" ),
+ DEFINE_KEYFIELD( m_yfriction, FIELD_FLOAT, "yfriction" ),
+ DEFINE_KEYFIELD( m_zfriction, FIELD_FLOAT, "zfriction" ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( phys_ragdollconstraint, CRagdollConstraint );
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate/create the constraint
+//-----------------------------------------------------------------------------
+IPhysicsConstraint *CRagdollConstraint::CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info )
+{
+ constraint_ragdollparams_t ragdoll;
+ ragdoll.Defaults();
+
+ matrix3x4_t entityToWorld, worldToEntity;
+ info.pObjects[0]->GetPositionMatrix( &entityToWorld );
+ MatrixInvert( entityToWorld, worldToEntity );
+ ConcatTransforms( worldToEntity, EntityToWorldTransform(), ragdoll.constraintToReference );
+
+ info.pObjects[1]->GetPositionMatrix( &entityToWorld );
+ MatrixInvert( entityToWorld, worldToEntity );
+ ConcatTransforms( worldToEntity, EntityToWorldTransform(), ragdoll.constraintToAttached );
+
+ ragdoll.onlyAngularLimits = HasSpawnFlags( SF_RAGDOLL_FREEMOVEMENT ) ? true : false;
+
+ // FIXME: Why are these friction numbers in different units from what the hinge uses?
+ ragdoll.axes[0].SetAxisFriction( m_xmin, m_xmax, m_xfriction );
+ ragdoll.axes[1].SetAxisFriction( m_ymin, m_ymax, m_yfriction );
+ ragdoll.axes[2].SetAxisFriction( m_zmin, m_zmax, m_zfriction );
+
+ if ( HasSpawnFlags( SF_CONSTRAINT_START_INACTIVE ) )
+ {
+ ragdoll.isActive = false;
+ }
+ return physenv->CreateRagdollConstraint( info.pObjects[0], info.pObjects[1], pGroup, ragdoll );
+}
+
+
+
+class CPhysConstraintEvents : public IPhysicsConstraintEvent
+{
+ void ConstraintBroken( IPhysicsConstraint *pConstraint )
+ {
+ CBaseEntity *pEntity = (CBaseEntity *)pConstraint->GetGameData();
+ if ( pEntity )
+ {
+ IPhysicsConstraintEvent *pConstraintEvent = dynamic_cast<IPhysicsConstraintEvent*>( pEntity );
+ //Msg("Constraint broken %s\n", pEntity->GetDebugName() );
+ if ( pConstraintEvent )
+ {
+ pConstraintEvent->ConstraintBroken( pConstraint );
+ }
+ else
+ {
+ variant_t emptyVariant;
+ pEntity->AcceptInput( "ConstraintBroken", NULL, NULL, emptyVariant, 0 );
+ }
+ }
+ }
+};
+
+static CPhysConstraintEvents constraintevents;
+// registered in physics.cpp
+IPhysicsConstraintEvent *g_pConstraintEvents = &constraintevents;
+
+
+
+
+
+#if HINGE_NOTIFY
+//-----------------------------------------------------------------------------
+// Code for sampler
+//-----------------------------------------------------------------------------
+
+
+/// Call this in spawn(). (Not a constructor because those are difficult to use in entities.)
+void VelocitySampler::Initialize(float samplerate)
+{
+ m_fIdealSampleRate = samplerate;
+}
+
+// This is an old style approach to reversal sounds, from when there was only one.
+#if 0
+bool VelocitySampler::HasReversed(const Vector &relativeVelocity, float thresholdAcceleration)
+{
+ // first, make sure the velocity has reversed (is more than 90deg off) from last time, or is zero now.
+ // float rVsq = relativeVelocity.LengthSqr();
+ float vDot = relativeVelocity.Dot(m_prevSample);
+ if (vDot <= 0) // there is a reversal in direction. compute the magnitude of acceleration.
+ {
+ // find the scalar projection of the relative acceleration this fame onto the previous frame's
+ // velocity, and compare that to the threshold.
+ Vector accel = relativeVelocity - m_prevSample;
+
+ float prevSampleLength = m_prevSample.Length();
+ float projection = 0;
+ // divide through by dt to get the accel per sec
+ if (prevSampleLength)
+ {
+ projection = -(accel.Dot(m_prevSample) / prevSampleLength) / (gpGlobals->curtime - m_fPrevSampleTime);
+ }
+ else
+ {
+ projection = accel.Length() / (gpGlobals->curtime - m_fPrevSampleTime);
+ }
+
+ if (g_debug_constraint_sounds.GetBool())
+ {
+ Msg("Reversal accel is %f/%f\n",projection,thresholdAcceleration);
+ }
+ return ((projection) > thresholdAcceleration); // the scalar projection is negative because the acceleration is against vel
+ }
+ else
+ {
+ return false;
+ }
+}
+#endif
+
+/// Looks at the force of reversal and compares it to a ladder of thresholds.
+/// Returns the index of the highest threshold exceeded by the reversal velocity.
+int VelocitySampler::HasReversed(const Vector &relativeVelocity, const float thresholdAcceleration[], const unsigned short numThresholds)
+{
+ // first, make sure the velocity has reversed (is more than 90deg off) from last time, or is zero now.
+ // float rVsq = relativeVelocity.LengthSqr();
+ float vDot = relativeVelocity.Dot(m_prevSample);
+ if (vDot <= 0) // there is a reversal in direction. compute the magnitude of acceleration.
+ {
+ // find the scalar projection of the relative acceleration this fame onto the previous frame's
+ // velocity, and compare that to the threshold.
+ Vector accel = relativeVelocity - m_prevSample;
+
+ float prevSampleLength = m_prevSample.Length();
+ float projection = 0;
+ // divide through by dt to get the accel per sec
+ if (prevSampleLength)
+ {
+ // the scalar projection is negative because the acceleration is against vel
+ projection = -(accel.Dot(m_prevSample) / prevSampleLength) / (gpGlobals->curtime - m_fPrevSampleTime);
+ }
+ else
+ {
+ projection = accel.Length() / (gpGlobals->curtime - m_fPrevSampleTime);
+ }
+
+ if (g_debug_constraint_sounds.GetBool())
+ {
+ Msg("Reversal accel is %f/%f\n", projection, thresholdAcceleration[0]);
+ }
+
+
+ // now find the threshold crossed.
+ int retval;
+ for (retval = numThresholds - 1; retval >= 0 ; --retval)
+ {
+ if (projection > thresholdAcceleration[retval])
+ break;
+ }
+
+ return retval;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+/// small helper function used just below (technique copy-pasted from sound.cpp)
+inline static bool IsEmpty (const string_t &str)
+{
+ return (!str || strlen(str.ToCStr()) < 1 );
+}
+
+void ConstraintSoundInfo::OnActivate( CPhysConstraint *pOuter )
+{
+ m_pTravelSound = NULL;
+ m_vSampler.Initialize( getThinkRate() );
+
+
+ ValidateInternals( pOuter );
+
+ // make sure sound filenames are not empty
+ m_bPlayTravelSound = !IsEmpty(m_iszTravelSoundFwd) || !IsEmpty(m_iszTravelSoundBack);
+ m_bPlayReversalSound = false;
+ for (int i = 0; i < SimpleConstraintSoundProfile::kREVERSAL_SOUND_ARRAY_SIZE ; ++i)
+ {
+ if ( !IsEmpty(m_iszReversalSounds[i]) )
+ {
+ // if there is at least one filled sound field, we should try
+ // to play reversals
+ m_bPlayReversalSound = true;
+ break;
+ }
+ }
+
+
+ /*
+ SetThink(&CPhysSlideConstraint::SoundThink);
+ SetNextThink(gpGlobals->curtime + m_vSampler.getSampleRate());
+ */
+}
+
+/// Maintain consistency of internal datastructures on start
+void ConstraintSoundInfo::ValidateInternals( CPhysConstraint *pOuter )
+{
+ // Make sure the reversal sound thresholds are strictly increasing.
+ for (int i = 1 ; i < SimpleConstraintSoundProfile::kREVERSAL_SOUND_ARRAY_SIZE ; ++i)
+ {
+ // if decreases from small to medium, promote small to medium and warn.
+ if (m_soundProfile.m_reversalSoundThresholds[i] < m_soundProfile.m_reversalSoundThresholds[i-1])
+ {
+ Warning("Constraint reversal sounds for %s are out of order!", pOuter->GetDebugName() );
+ m_soundProfile.m_reversalSoundThresholds[i] = m_soundProfile.m_reversalSoundThresholds[i-1];
+ m_iszReversalSounds[i] = m_iszReversalSounds[i-1];
+ }
+ }
+}
+
+void ConstraintSoundInfo::OnPrecache( CPhysConstraint *pOuter )
+{
+ pOuter->PrecacheScriptSound( m_iszTravelSoundFwd.ToCStr() );
+ pOuter->PrecacheScriptSound( m_iszTravelSoundBack.ToCStr() );
+ for (int i = 0 ; i < SimpleConstraintSoundProfile::kREVERSAL_SOUND_ARRAY_SIZE; ++i )
+ {
+ pOuter->PrecacheScriptSound( m_iszReversalSounds[i].ToCStr() );
+ }
+}
+
+void ConstraintSoundInfo::OnThink( CPhysConstraint *pOuter, const Vector &relativeVelocity )
+{
+ // have we had a hard reversal?
+ int playReversal = m_vSampler.HasReversed( relativeVelocity, m_soundProfile.m_reversalSoundThresholds, SimpleConstraintSoundProfile::kREVERSAL_SOUND_ARRAY_SIZE );
+ float relativeVelMag = relativeVelocity.Length(); //< magnitude of relative velocity
+
+ CBaseEntity *pChildEntity = static_cast<CBaseEntity *>(pOuter->GetPhysConstraint()->GetAttachedObject()->GetGameData());
+
+ // compute sound level
+ float soundVol = this->m_soundProfile.GetVolume(relativeVelMag);
+
+ if (g_debug_constraint_sounds.GetBool())
+ {
+ char tempstr[512];
+ Q_snprintf(tempstr,sizeof(tempstr),"Velocity: %.3f", relativeVelMag );
+ pChildEntity->EntityText( 0, tempstr, m_vSampler.getSampleRate() );
+
+ Q_snprintf(tempstr,sizeof(tempstr),"Sound volume: %.3f", soundVol );
+ pChildEntity->EntityText( 1, tempstr, m_vSampler.getSampleRate() );
+
+ if (playReversal >= 0)
+ {
+ Q_snprintf(tempstr,sizeof(tempstr),"Reversal [%d]", playReversal );
+ pChildEntity->EntityText(2,tempstr,m_vSampler.getSampleRate());
+ }
+ }
+
+ // if we loaded a travel sound
+ if (m_bPlayTravelSound)
+ {
+ if (soundVol > 0)
+ {
+ // if we want to play a sound...
+ if ( m_pTravelSound )
+ { // if a sound exists, modify it
+ CSoundEnvelopeController::GetController().SoundChangeVolume( m_pTravelSound, soundVol, 0.1f );
+ }
+ else
+ { // if a sound does not exist, create it
+ bool travellingForward = relativeVelocity.Dot(m_forwardAxis) > 0;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ CPASAttenuationFilter filter( pChildEntity );
+ m_pTravelSound = controller.SoundCreate( filter, pChildEntity->entindex(),
+ (travellingForward ? m_iszTravelSoundFwd : m_iszTravelSoundBack).ToCStr() );
+ controller.Play( m_pTravelSound, soundVol, 100 );
+ }
+ }
+ else
+ {
+ // if we want to not play sound
+ if ( m_pTravelSound )
+ { // and it exists, kill it
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pTravelSound );
+ m_pTravelSound = NULL;
+ }
+ }
+ }
+
+ if (m_bPlayReversalSound && (playReversal >= 0))
+ {
+ pChildEntity->EmitSound(m_iszReversalSounds[playReversal].ToCStr());
+ }
+
+ m_vSampler.AddSample( relativeVelocity );
+
+}
+
+
+void ConstraintSoundInfo::StartThinking( CPhysConstraint *pOuter, const Vector &relativeVelocity, const Vector &forwardVector )
+{
+ m_forwardAxis = forwardVector;
+ m_vSampler.BeginSampling( relativeVelocity );
+
+ /*
+ IPhysicsConstraint *pConstraint = pOuter->GetPhysConstraint();
+ Assert(pConstraint);
+ if (pConstraint)
+ {
+ IPhysicsObject * pAttached = pConstraint->GetAttachedObject(), *pReference = pConstraint->GetReferenceObject();
+ m_vSampler.BeginSampling( VelocitySampler::GetRelativeVelocity(pAttached,pReference) );
+ }
+ */
+}
+
+void ConstraintSoundInfo::StopThinking( CPhysConstraint *pOuter )
+{
+ DeleteAllSounds();
+}
+
+
+ConstraintSoundInfo::~ConstraintSoundInfo()
+{
+ DeleteAllSounds();
+}
+
+// Any sounds envelopes that are active, kill.
+void ConstraintSoundInfo::DeleteAllSounds()
+{
+ if ( m_pTravelSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pTravelSound );
+ m_pTravelSound = NULL;
+ }
+}
+
+#endif