//========= 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 #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( 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 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( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.surfacePropIndex) ); defs.inertia = FindCommonValue( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.params.inertia) ); defs.damping = FindCommonValue( pList, listCount, sizeof(CPhysmesh), offsetof(CPhysmesh, m_solid.params.damping) ); defs.rotdamping = FindCommonValue( 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; }