2010 lines
60 KiB
C++
2010 lines
60 KiB
C++
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose: Physics constraint entities
|
|
//
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "physics.h"
|
|
#include "entityoutput.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "vphysics/constraints.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
|
|
|
|
// 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 hl_constraint_info_t
|
|
{
|
|
hl_constraint_info_t()
|
|
{
|
|
pObjects[0] = pObjects[1] = NULL;
|
|
pGroup = NULL;
|
|
anchorPosition[0].Init();
|
|
anchorPosition[1].Init();
|
|
swapped = false;
|
|
massScale[0] = massScale[1] = 1.0f;
|
|
}
|
|
Vector anchorPosition[2];
|
|
IPhysicsObject *pObjects[2];
|
|
IPhysicsConstraintGroup *pGroup;
|
|
float massScale[2];
|
|
bool swapped;
|
|
};
|
|
|
|
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();
|
|
~CPhysConstraintSystem();
|
|
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 );
|
|
}
|
|
|
|
CPhysConstraintSystem::~CPhysConstraintSystem()
|
|
{
|
|
physenv->DestroyConstraintGroup( m_pMachine );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
abstract_class CPhysConstraint : public CLogicalEntity
|
|
{
|
|
DECLARE_CLASS( CPhysConstraint, CLogicalEntity );
|
|
public:
|
|
|
|
CPhysConstraint();
|
|
~CPhysConstraint();
|
|
|
|
DECLARE_DATADESC();
|
|
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
void Activate( void );
|
|
|
|
void ClearStaticFlag( IPhysicsObject *pObj )
|
|
{
|
|
if ( !pObj )
|
|
return;
|
|
PhysClearGameFlags( pObj, FVPHYSICS_CONSTRAINT_STATIC );
|
|
}
|
|
|
|
virtual void 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 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 InputBreak( inputdata_t &inputdata )
|
|
{
|
|
if ( m_pConstraint )
|
|
m_pConstraint->Deactivate();
|
|
|
|
OnBreak();
|
|
}
|
|
|
|
void InputOnBreak( inputdata_t &inputdata )
|
|
{
|
|
OnBreak();
|
|
}
|
|
|
|
void 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 InputTurnOff( inputdata_t &inputdata )
|
|
{
|
|
Deactivate();
|
|
}
|
|
|
|
int DrawDebugTextOverlays()
|
|
{
|
|
int pos = BaseClass::DrawDebugTextOverlays();
|
|
if ( m_pConstraint && (m_debugOverlays & OVERLAY_TEXT_BIT) )
|
|
{
|
|
constraint_breakableparams_t params;
|
|
Q_memset(¶ms,0,sizeof(params));
|
|
m_pConstraint->GetConstraintParams( ¶ms );
|
|
|
|
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 DrawDebugGeometryOverlays()
|
|
{
|
|
if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
|
|
{
|
|
DrawConstraintObjectsAxes(this, m_pConstraint);
|
|
}
|
|
BaseClass::DrawDebugGeometryOverlays();
|
|
}
|
|
|
|
void GetBreakParams( constraint_breakableparams_t ¶ms, 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];
|
|
}
|
|
|
|
// the notify system calls this on the constrained entities - used to detect & follow teleports
|
|
void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms );
|
|
|
|
// gets called at setup time on first init and restore
|
|
virtual void OnConstraintSetup( hl_constraint_info_t &info );
|
|
|
|
// return the internal constraint object (used by sound gadgets)
|
|
inline IPhysicsConstraint *GetPhysConstraint() { return m_pConstraint; }
|
|
|
|
protected:
|
|
void GetConstraintObjects( hl_constraint_info_t &info );
|
|
void SetupTeleportationHandling( hl_constraint_info_t &info );
|
|
bool ActivateConstraint( void );
|
|
virtual IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info ) = 0;
|
|
|
|
IPhysicsConstraint *m_pConstraint;
|
|
|
|
// These are "template" values used to construct the hinge
|
|
string_t m_nameAttach1;
|
|
string_t m_nameAttach2;
|
|
string_t m_breakSound;
|
|
string_t m_nameSystem;
|
|
float m_forceLimit;
|
|
float m_torqueLimit;
|
|
unsigned int m_teleportTick;
|
|
float m_minTeleportDistance;
|
|
|
|
COutputEvent m_OnBreak;
|
|
};
|
|
|
|
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 ¶ms )
|
|
{
|
|
// 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 );
|
|
|
|
if ( m_pConstraint )
|
|
{
|
|
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
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fixed breakable constraint
|
|
//-----------------------------------------------------------------------------
|
|
class CPhysFixed : public CPhysConstraint
|
|
{
|
|
DECLARE_CLASS( CPhysFixed, CPhysConstraint );
|
|
public:
|
|
IPhysicsConstraint *CreateConstraint( IPhysicsConstraintGroup *pGroup, const hl_constraint_info_t &info );
|
|
|
|
// just for debugging - move to the position of the reference entity
|
|
void MoveToRefPosition()
|
|
{
|
|
if ( m_pConstraint )
|
|
{
|
|
matrix3x4_t xformRef;
|
|
m_pConstraint->GetConstraintTransform( &xformRef, NULL );
|
|
IPhysicsObject *pObj = m_pConstraint->GetReferenceObject();
|
|
if ( pObj && pObj->IsMoveable() )
|
|
{
|
|
Vector pos, posWorld;
|
|
MatrixPosition( xformRef, pos );
|
|
pObj->LocalToWorld(&posWorld, pos);
|
|
SetAbsOrigin(posWorld);
|
|
}
|
|
}
|
|
}
|
|
int DrawDebugTextOverlays()
|
|
{
|
|
if ( m_debugOverlays & OVERLAY_TEXT_BIT )
|
|
{
|
|
MoveToRefPosition();
|
|
}
|
|
return BaseClass::DrawDebugTextOverlays();
|
|
}
|
|
void DrawDebugGeometryOverlays()
|
|
{
|
|
if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
|
|
{
|
|
MoveToRefPosition();
|
|
}
|
|
BaseClass::DrawDebugGeometryOverlays();
|
|
}
|
|
};
|
|
|
|
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
|