source-engine/utils/hlmv/physmesh.cpp

620 lines
15 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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;
}