|
|
//========= 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; }
|