You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
619 lines
15 KiB
619 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
/*** |
|
* |
|
* Copyright (c) 1998, Valve LLC. All rights reserved. |
|
* |
|
* This product contains software technology licensed from Id |
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. |
|
* All Rights Reserved. |
|
* |
|
****/ |
|
|
|
#include "filesystem.h" |
|
#include "vphysics/constraints.h" |
|
#include "phyfile.h" |
|
#include "physdll.h" |
|
#include "physmesh.h" |
|
#include "mathlib/mathlib.h" |
|
#include <stddef.h> |
|
#include "utlvector.h" |
|
#include "commonmacros.h" |
|
#include "studiomodel.h" |
|
#include "tier1/strtools.h" |
|
#include "bone_setup.h" |
|
#include "fmtstr.h" |
|
#include "vcollide_parse.h" |
|
|
|
int FindPhysprop( const char *pPropname ); |
|
|
|
bool LoadPhysicsProperties( void ); |
|
extern int FindBoneIndex( CStudioHdr *pstudiohdr, const char *pName ); |
|
|
|
|
|
struct collisionpair_t |
|
{ |
|
int object0; |
|
int object1; |
|
collisionpair_t *pNext; |
|
}; |
|
|
|
class CStudioPhysics : public IStudioPhysics |
|
{ |
|
public: |
|
CStudioPhysics( void ) |
|
{ |
|
m_pList = NULL; |
|
m_listCount = 0; |
|
m_mass = 0; |
|
m_noselfCollisions = false; |
|
m_pCollisionPairs = NULL; |
|
memset( &m_edit, 0, sizeof(editparams_t) ); |
|
} |
|
|
|
~CStudioPhysics( void ) |
|
{ |
|
if ( physcollision ) |
|
{ |
|
for ( int i = 0; i < m_listCount; i++ ) |
|
{ |
|
physcollision->DestroyDebugMesh( m_pList[i].m_vertCount, m_pList[i].m_pVerts ); |
|
physcollision->DestroyQueryModel( m_pList[i].m_pCollisionModel ); |
|
} |
|
} |
|
delete[] m_pList; |
|
} |
|
|
|
int Count( void ) |
|
{ |
|
return m_listCount; |
|
} |
|
|
|
CPhysmesh *GetMesh( int index ) |
|
{ |
|
if ( index < m_listCount ) |
|
return m_pList + index; |
|
|
|
return NULL; |
|
} |
|
|
|
float GetMass( void ) { return m_mass; } |
|
|
|
void AddCollisionPair( int index0, int index1 ) |
|
{ |
|
collisionpair_t *pPair = new collisionpair_t; |
|
pPair->object0 = index0; |
|
pPair->object1 = index1; |
|
pPair->pNext = m_pCollisionPairs; |
|
m_pCollisionPairs = pPair; |
|
|
|
} |
|
|
|
void Load( MDLHandle_t handle ); |
|
char *DumpQC( void ); |
|
void ParseKeydata( void ); |
|
|
|
vcollide_t *GetVCollide() |
|
{ |
|
return g_pMDLCache->GetVCollide( m_MDLHandle ); |
|
} |
|
|
|
CPhysmesh *m_pList; |
|
MDLHandle_t m_MDLHandle; |
|
int m_listCount; |
|
|
|
float m_mass; |
|
editparams_t m_edit; |
|
bool m_noselfCollisions; |
|
collisionpair_t *m_pCollisionPairs; |
|
}; |
|
|
|
|
|
void CPhysmesh::Clear( void ) |
|
{ |
|
memset( this, 0, sizeof(*this) ); |
|
memset( &m_constraint, 0, sizeof(m_constraint) ); |
|
m_constraint.parentIndex = -1; |
|
m_constraint.childIndex = -1; |
|
} |
|
|
|
|
|
IStudioPhysics *LoadPhysics( MDLHandle_t mdlHandle ) |
|
{ |
|
CStudioPhysics *pPhysics = new CStudioPhysics; |
|
pPhysics->Load( mdlHandle ); |
|
return pPhysics; |
|
} |
|
|
|
void DestroyPhysics( IStudioPhysics *pStudioPhysics ) |
|
{ |
|
CStudioPhysics *pPhysics = static_cast<CStudioPhysics*>( pStudioPhysics ); |
|
if ( pPhysics ) |
|
{ |
|
delete pPhysics; |
|
} |
|
} |
|
|
|
void CStudioPhysics::Load( MDLHandle_t mdlHandle ) |
|
{ |
|
m_MDLHandle = mdlHandle; |
|
|
|
LoadPhysicsProperties(); |
|
|
|
vcollide_t *pVCollide = GetVCollide( ); |
|
if ( !pVCollide ) |
|
{ |
|
m_pList = NULL; |
|
m_listCount = 0; |
|
return; |
|
} |
|
|
|
m_pList = new CPhysmesh[pVCollide->solidCount]; |
|
m_listCount = pVCollide->solidCount; |
|
|
|
int i; |
|
|
|
for ( i = 0; i < pVCollide->solidCount; i++ ) |
|
{ |
|
m_pList[i].Clear(); |
|
m_pList[i].m_vertCount = physcollision->CreateDebugMesh( pVCollide->solids[i], &m_pList[i].m_pVerts ); |
|
m_pList[i].m_pCollisionModel = physcollision->CreateQueryModel( pVCollide->solids[i] ); |
|
} |
|
|
|
ParseKeydata(); |
|
|
|
CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( mdlHandle ), g_pMDLCache ); |
|
for ( i = 0; i < pVCollide->solidCount; i++ ) |
|
{ |
|
CPhysmesh *pmesh = m_pList + i; |
|
int boneIndex = FindBoneIndex( &studioHdr, pmesh->m_boneName ); |
|
if ( boneIndex < 0 ) |
|
continue; |
|
|
|
if ( pmesh->m_constraint.parentIndex >= 0 ) |
|
{ |
|
CPhysmesh *pparent = m_pList + pmesh->m_constraint.parentIndex; |
|
int parentIndex = FindBoneIndex( &studioHdr, pparent->m_boneName ); |
|
Studio_CalcBoneToBoneTransform( &studioHdr, boneIndex, parentIndex, pmesh->m_matrix ); |
|
} |
|
else |
|
{ |
|
MatrixInvert( studioHdr.pBone(boneIndex)->poseToBone, pmesh->m_matrix ); |
|
} |
|
} |
|
|
|
// doesn't have a root bone? Make it the first bone |
|
if ( !m_edit.rootName[0] ) |
|
{ |
|
strcpy( m_edit.rootName, m_pList[0].m_boneName ); |
|
} |
|
} |
|
|
|
|
|
class CEditParse : public IVPhysicsKeyHandler |
|
{ |
|
public: |
|
virtual void ParseKeyValue( void *pCustom, const char *pKey, const char *pValue ) |
|
{ |
|
editparams_t *pEdit = (editparams_t *)pCustom; |
|
if ( !strcmpi( pKey, "rootname" ) ) |
|
{ |
|
strncpy( pEdit->rootName, pValue, sizeof(pEdit->rootName) ); |
|
} |
|
else if ( !strcmpi( pKey, "totalmass" ) ) |
|
{ |
|
pEdit->totalMass = atof( pValue ); |
|
} |
|
else if ( !strcmpi( pKey, "concave" ) ) |
|
{ |
|
pEdit->concave = atoi( pValue ); |
|
} |
|
else if ( !strcmpi( pKey, "jointmerge" ) ) |
|
{ |
|
char tmp[1024]; |
|
char parentName[512], childName[512]; |
|
Q_strncpy( tmp, pValue, 1024 ); |
|
char *pWord = strtok( tmp, "," ); |
|
Q_strncpy( parentName, pWord, sizeof(parentName) ); |
|
pWord = strtok( NULL, "," ); |
|
Q_strncpy( childName, pWord, sizeof(childName) ); |
|
if ( pEdit->mergeCount < ARRAYSIZE(pEdit->mergeList) ) |
|
{ |
|
merge_t *pMerge = &pEdit->mergeList[pEdit->mergeCount]; |
|
pEdit->mergeCount++; |
|
pMerge->parent = g_pStudioModel->FindBone(parentName); |
|
pMerge->child = g_pStudioModel->FindBone(childName); |
|
} |
|
} |
|
} |
|
virtual void SetDefaults( void *pCustom ) |
|
{ |
|
editparams_t *pEdit = (editparams_t *)pCustom; |
|
memset( pEdit, 0, sizeof(*pEdit) ); |
|
} |
|
}; |
|
|
|
class CRagdollCollisionRulesParse : public IVPhysicsKeyHandler |
|
{ |
|
public: |
|
CRagdollCollisionRulesParse( CStudioPhysics *pStudio ) : m_pStudio(pStudio) |
|
{ |
|
pStudio->m_noselfCollisions = false; |
|
} |
|
|
|
virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue ) |
|
{ |
|
if ( !strcmpi( pKey, "selfcollisions" ) ) |
|
{ |
|
// keys disabled by default |
|
Assert( atoi(pValue) == 0 ); |
|
m_pStudio->m_noselfCollisions = true; |
|
} |
|
else if ( !strcmpi( pKey, "collisionpair" ) ) |
|
{ |
|
if ( !m_pStudio->m_noselfCollisions ) |
|
{ |
|
char tmp[1024]; |
|
Q_strncpy( tmp, pValue, 1024 ); |
|
char *pWord = strtok( tmp, "," ); |
|
int index0 = atoi(pWord); |
|
pWord = strtok( NULL, "," ); |
|
int index1 = atoi(pWord); |
|
m_pStudio->AddCollisionPair( index0, index1 ); |
|
} |
|
else |
|
{ |
|
Assert(0); |
|
} |
|
} |
|
} |
|
virtual void SetDefaults( void *pData ) {} |
|
|
|
private: |
|
CStudioPhysics *m_pStudio; |
|
}; |
|
|
|
class CSolidParse : public IVPhysicsKeyHandler |
|
{ |
|
public: |
|
virtual void ParseKeyValue( void *pCustom, const char *pKey, const char *pValue ) |
|
{ |
|
hlmvsolid_t *pSolid = (hlmvsolid_t *)pCustom; |
|
if ( !strcmpi( pKey, "massbias" ) ) |
|
{ |
|
pSolid->massBias = atof( pValue ); |
|
} |
|
else |
|
{ |
|
printf("Bad key %s!!\n", pKey); |
|
} |
|
} |
|
virtual void SetDefaults( void *pCustom ) |
|
{ |
|
hlmvsolid_t *pSolid = (hlmvsolid_t *)pCustom; |
|
pSolid->massBias = 1.0; |
|
} |
|
}; |
|
|
|
void CStudioPhysics::ParseKeydata( void ) |
|
{ |
|
IVPhysicsKeyParser *pParser = physcollision->VPhysicsKeyParserCreate( GetVCollide()->pKeyValues ); |
|
|
|
while ( !pParser->Finished() ) |
|
{ |
|
const char *pBlock = pParser->GetCurrentBlockName(); |
|
if ( !stricmp( pBlock, "solid" ) ) |
|
{ |
|
hlmvsolid_t solid; |
|
CSolidParse solidParse; |
|
|
|
pParser->ParseSolid( &solid, &solidParse ); |
|
solid.surfacePropIndex = FindPhysprop( solid.surfaceprop ); |
|
|
|
if ( solid.index >= 0 && solid.index < m_listCount ) |
|
{ |
|
strcpy( m_pList[solid.index].m_boneName, solid.name ); |
|
memcpy( &m_pList[solid.index].m_solid, &solid, sizeof(solid) ); |
|
} |
|
} |
|
else if ( !stricmp( pBlock, "ragdollconstraint" ) ) |
|
{ |
|
constraint_ragdollparams_t constraint; |
|
pParser->ParseRagdollConstraint( &constraint, NULL ); |
|
if ( constraint.childIndex >= 0 && constraint.childIndex < m_listCount ) |
|
{ |
|
// In the editor / qc these show up as 5X so that 1.0 is the default |
|
constraint.axes[0].torque *= 5; |
|
constraint.axes[1].torque *= 5; |
|
constraint.axes[2].torque *= 5; |
|
m_pList[constraint.childIndex].m_constraint = constraint; |
|
} |
|
} |
|
else if ( !stricmp( pBlock, "editparams" ) ) |
|
{ |
|
CEditParse editParse; |
|
pParser->ParseCustom( &m_edit, &editParse ); |
|
m_mass = m_edit.totalMass; |
|
} |
|
else if ( !strcmpi( pBlock, "collisionrules" ) ) |
|
{ |
|
CRagdollCollisionRulesParse rules(this); |
|
pParser->ParseCustom( NULL, &rules ); |
|
} |
|
else |
|
{ |
|
pParser->SkipBlock(); |
|
} |
|
} |
|
physcollision->VPhysicsKeyParserDestroy( pParser ); |
|
} |
|
|
|
|
|
int FindPhysprop( const char *pPropname ) |
|
{ |
|
if ( physprop ) |
|
{ |
|
int count = physprop->SurfacePropCount(); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( !strcmpi( pPropname, physprop->GetPropName(i) ) ) |
|
return i; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
|
|
class CTextBuffer |
|
{ |
|
public: |
|
CTextBuffer( void ) {} |
|
~CTextBuffer( void ) {} |
|
|
|
inline int GetSize( void ) { return m_buffer.Size(); } |
|
inline char *GetData( void ) { return m_buffer.Base(); } |
|
|
|
void WriteText( const char *pText ) |
|
{ |
|
int len = strlen( pText ); |
|
CopyData( pText, len ); |
|
} |
|
|
|
void Terminate( void ) { CopyData( "\0", 1 ); } |
|
|
|
void CopyData( const char *pData, int len ) |
|
{ |
|
int offset = m_buffer.AddMultipleToTail( len ); |
|
memcpy( m_buffer.Base() + offset, pData, len ); |
|
} |
|
|
|
private: |
|
CUtlVector<char> m_buffer; |
|
}; |
|
|
|
|
|
struct physdefaults_t |
|
{ |
|
int surfacePropIndex; |
|
float inertia; |
|
float damping; |
|
float rotdamping; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Nasty little routine (that was easy to code) to find the most common |
|
// value in an array of structs containing that as a member |
|
// Input : *pStructArray - pointer to head of struct array |
|
// arrayCount - number of elements in the array |
|
// structSize - size of each element |
|
// fieldOffset - offset to the float we're finding |
|
// Output : static T - most common value |
|
//----------------------------------------------------------------------------- |
|
template< class T > |
|
static T FindCommonValue( void *pStructArray, int arrayCount, int structSize, int fieldOffset ) |
|
{ |
|
int maxCount = 0; |
|
T maxVal = 0; |
|
|
|
// BUGBUG: This is O(n^2), but n is really small |
|
for ( int i = 0; i < arrayCount; i++ ) |
|
{ |
|
// current = struct[i].offset |
|
T current = *(T *)((char *)pStructArray + (i*structSize) + fieldOffset); |
|
int currentCount = 0; |
|
|
|
// if everything is set to the default, this is almost O(n) |
|
if ( current == maxVal ) |
|
continue; |
|
|
|
for ( int j = 0; j < arrayCount; j++ ) |
|
{ |
|
// value = struct[j].offset |
|
T value = *(T *)((char *)pStructArray + (j*structSize) + fieldOffset); |
|
if ( value == current ) |
|
currentCount++; |
|
} |
|
|
|
if ( currentCount > maxCount ) |
|
{ |
|
maxVal = current; |
|
maxCount = currentCount; |
|
} |
|
} |
|
|
|
return maxVal; |
|
} |
|
|
|
static void CalcDefaultProperties( CPhysmesh *pList, int listCount, physdefaults_t &defs ) |
|
{ |
|
defs.surfacePropIndex = FindCommonValue<int>( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.surfacePropIndex) ); |
|
defs.inertia = FindCommonValue<float>( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.params.inertia) ); |
|
defs.damping = FindCommonValue<float>( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.params.damping) ); |
|
defs.rotdamping = FindCommonValue<float>( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.params.rotdamping) ); |
|
} |
|
|
|
static void DumpModelProperties( CTextBuffer &out, float mass, physdefaults_t &defs ) |
|
{ |
|
char tmpbuf[1024]; |
|
sprintf( tmpbuf, "\t$mass %.1f\r\n", mass ); |
|
out.WriteText( tmpbuf ); |
|
sprintf( tmpbuf, "\t$inertia %.2f\r\n", defs.inertia ); |
|
out.WriteText( tmpbuf ); |
|
sprintf( tmpbuf, "\t$damping %.2f\r\n", defs.damping ); |
|
out.WriteText( tmpbuf ); |
|
sprintf( tmpbuf, "\t$rotdamping %.2f\r\n", defs.rotdamping ); |
|
out.WriteText( tmpbuf ); |
|
} |
|
|
|
char *CStudioPhysics::DumpQC( void ) |
|
{ |
|
if ( !m_listCount ) |
|
return NULL; |
|
|
|
CTextBuffer out; |
|
physdefaults_t defs; |
|
|
|
CalcDefaultProperties( m_pList, m_listCount, defs ); |
|
|
|
if ( m_listCount == 1 ) |
|
{ |
|
out.WriteText( "$collisionmodel ragdoll {\r\n\r\n" ); |
|
if ( m_edit.concave ) |
|
{ |
|
out.WriteText( "\t$concave\r\n" ); |
|
} |
|
DumpModelProperties( out, m_mass, defs ); |
|
} |
|
else |
|
{ |
|
int i; |
|
|
|
out.WriteText( "$collisionjoints ragdoll {\r\n\r\n" ); |
|
DumpModelProperties( out, m_mass, defs ); |
|
|
|
// write out the root bone |
|
if ( m_edit.rootName[0] ) |
|
{ |
|
char tmp[128]; |
|
sprintf( tmp, "\t$rootbone \"%s\"\r\n", m_edit.rootName ); |
|
out.WriteText( tmp ); |
|
} |
|
|
|
for ( i = 0; i < m_edit.mergeCount; i++ ) |
|
{ |
|
char tmp[1024]; |
|
if ( m_edit.mergeList[i].parent >= 0 && m_edit.mergeList[i].child >= 0 ) |
|
{ |
|
char const *pParentName = g_pStudioModel->GetStudioHdr()->pBone(m_edit.mergeList[i].parent)->pszName(); |
|
char const *pChildName = g_pStudioModel->GetStudioHdr()->pBone(m_edit.mergeList[i].child)->pszName(); |
|
Q_snprintf( tmp, sizeof(tmp), "\t$jointmerge \"%s\" \"%s\"\r\n", pParentName, pChildName ); |
|
out.WriteText( tmp ); |
|
} |
|
} |
|
char tmpbuf[1024]; |
|
for ( i = 0; i < m_listCount; i++ ) |
|
{ |
|
CPhysmesh *pmesh = m_pList + i; |
|
char jointname[256]; |
|
sprintf( jointname, "\"%s\"", pmesh->m_boneName ); |
|
if ( pmesh->m_solid.massBias != 1.0 ) |
|
{ |
|
sprintf( tmpbuf, "\t$jointmassbias %s %.2f\r\n", jointname, pmesh->m_solid.massBias ); |
|
out.WriteText( tmpbuf ); |
|
} |
|
if ( pmesh->m_solid.params.inertia != defs.inertia ) |
|
{ |
|
sprintf( tmpbuf, "\t$jointinertia %s %.2f\r\n", jointname, pmesh->m_solid.params.inertia ); |
|
out.WriteText( tmpbuf ); |
|
} |
|
if ( pmesh->m_solid.params.damping != defs.damping ) |
|
{ |
|
sprintf( tmpbuf, "\t$jointdamping %s %.2f\r\n", jointname, pmesh->m_solid.params.damping ); |
|
out.WriteText( tmpbuf ); |
|
} |
|
if ( pmesh->m_solid.params.rotdamping != defs.rotdamping ) |
|
{ |
|
sprintf( tmpbuf, "\t$jointrotdamping %s %.2f\r\n", jointname, pmesh->m_solid.params.rotdamping ); |
|
out.WriteText( tmpbuf ); |
|
} |
|
|
|
if ( pmesh->m_constraint.parentIndex >= 0 ) |
|
{ |
|
for ( int j = 0; j < 3; j++ ) |
|
{ |
|
char *pAxis[] = { "x", "y", "z" }; |
|
sprintf( tmpbuf, "\t$jointconstrain %s %s limit %.2f %.2f %.2f\r\n", jointname, pAxis[j], pmesh->m_constraint.axes[j].minRotation, pmesh->m_constraint.axes[j].maxRotation, pmesh->m_constraint.axes[j].torque ); |
|
out.WriteText( tmpbuf ); |
|
} |
|
} |
|
if ( i != m_listCount-1 ) |
|
{ |
|
out.WriteText( "\r\n" ); |
|
} |
|
} |
|
} |
|
|
|
if ( m_noselfCollisions ) |
|
{ |
|
out.WriteText( "\t$noselfcollisions\r\n" ); |
|
} |
|
else if ( m_pCollisionPairs ) |
|
{ |
|
collisionpair_t *pPair = m_pCollisionPairs; |
|
out.WriteText("\r\n"); |
|
while ( pPair ) |
|
{ |
|
out.WriteText( CFmtStr( "\t$jointcollide %s %s\r\n", m_pList[pPair->object0].m_boneName, m_pList[pPair->object1].m_boneName ) ); |
|
pPair = pPair->pNext; |
|
} |
|
} |
|
out.WriteText( "}\r\n" ); |
|
|
|
// only need the pose for ragdolls |
|
if ( m_listCount != 1 ) |
|
{ |
|
out.WriteText( "$sequence ragdoll \t\t\"ragdoll_pose\" \t\tFPS 30 \t\tactivity ACT_DIERAGDOLL 1\r\n" ); |
|
} |
|
|
|
out.Terminate(); |
|
|
|
if ( out.GetSize() ) |
|
{ |
|
char *pOutput = new char[out.GetSize()]; |
|
memcpy( pOutput, out.GetData(), out.GetSize() ); |
|
return pOutput; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static const char *pMaterialFilename = "scripts/surfaceproperties.txt"; |
|
|
|
bool LoadPhysicsProperties( void ) |
|
{ |
|
// already loaded |
|
if ( physprop->SurfacePropCount() ) |
|
return false; |
|
|
|
FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb" ); |
|
if ( fp == FILESYSTEM_INVALID_HANDLE ) |
|
return false; |
|
|
|
int len = g_pFileSystem->Size( fp ); |
|
|
|
char *pText = new char[len+1]; |
|
g_pFileSystem->Read( pText, len, fp ); |
|
g_pFileSystem->Close( fp ); |
|
pText[len]=0; |
|
|
|
physprop->ParseSurfaceData( pMaterialFilename, pText ); |
|
|
|
delete[] pText; |
|
return true; |
|
}
|
|
|