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.
2724 lines
74 KiB
2724 lines
74 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Builds physics collision models from studio model source |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
// NOTE: The term joint here is used to mean a bone, collision model, and a joint. |
|
// Each "joint" is the collision geometry at a named bone (or set of bones that have been merged) |
|
// and the joint (with constraints) between that set and its parent. The root "joint" has |
|
// no constraints. |
|
// I chose to refer to them as joints to avoid confusion. Yes they encompass bones and joints, |
|
// but they use the same names, and the data is actually linked. |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <sys/stat.h> |
|
#include <math.h> |
|
|
|
#include "vphysics/constraints.h" |
|
#include "collisionmodel.h" |
|
#include "cmdlib.h" |
|
#include "scriplib.h" |
|
#include "mathlib/mathlib.h" |
|
#include "studio.h" |
|
#include "studiomdl.h" |
|
#include "physdll.h" |
|
#include "phyfile.h" |
|
#include "utlvector.h" |
|
#include "vcollide_parse.h" |
|
#include "tier1/strtools.h" |
|
#include "tier2/tier2.h" |
|
#include "KeyValues.h" |
|
|
|
#include "tier1/smartptr.h" |
|
#include "tier2/p4helpers.h" |
|
|
|
|
|
// these functions just wrap atoi/atof and check for NULL |
|
static float Safe_atof( const char *pString ); |
|
static int Safe_atoi( const char *pString ); |
|
|
|
IPhysicsCollision *physcollision = NULL; |
|
IPhysicsSurfaceProps *physprops = NULL; |
|
|
|
float g_WeldVertEpsilon = 0.0f; |
|
float g_WeldNormalEpsilon = 0.999f; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Contains a single convex element of a physical collision system |
|
//----------------------------------------------------------------------------- |
|
class CPhysCollisionModel |
|
{ |
|
public: |
|
CPhysCollisionModel( void ) |
|
{ |
|
memset( this, 0, sizeof(*this) ); |
|
} |
|
|
|
const char *m_parent; |
|
const char *m_name; |
|
|
|
// physical properties stored on disk |
|
float m_mass; |
|
float m_volume; |
|
float m_surfaceArea; |
|
float m_damping; |
|
float m_rotdamping; |
|
float m_inertia; |
|
float m_dragCoefficient; |
|
|
|
// these tune the model building process, they don't go in the file |
|
float m_massBias; |
|
|
|
CPhysCollide *m_pCollisionData; |
|
CPhysCollisionModel *m_pNext; |
|
}; |
|
|
|
enum jointlimit_t |
|
{ |
|
JOINT_FREE = 0, |
|
JOINT_FIXED = 1, |
|
JOINT_LIMIT = 2, |
|
}; |
|
|
|
// list of vertex indices that form a convex element |
|
struct convexlist_t |
|
{ |
|
int firstVertIndex; |
|
int numVertIndex; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: element of a list of constraints for a jointed model |
|
//----------------------------------------------------------------------------- |
|
class CJointConstraint |
|
{ |
|
public: |
|
CJointConstraint( void ) |
|
{ |
|
m_pJointName = NULL; |
|
} |
|
|
|
CJointConstraint( const char *pName, int axis, jointlimit_t type, float min, float max, float friction ) |
|
: m_axis(axis), m_jointType(type), m_limitMin(min), m_limitMax(max), m_friction(friction) |
|
{ |
|
m_pJointName = pName; |
|
} |
|
|
|
const char *m_pJointName; |
|
int m_axis; |
|
jointlimit_t m_jointType; |
|
float m_limitMin; |
|
float m_limitMax; |
|
float m_friction; |
|
|
|
CJointConstraint *m_pNext; |
|
}; |
|
|
|
struct mergelist_t |
|
{ |
|
char *pParent; |
|
char *pChild; |
|
}; |
|
|
|
struct collisionpair_t |
|
{ |
|
int obj0; |
|
int obj1; |
|
const char *pName0; |
|
const char *pName1; |
|
collisionpair_t *pNext; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Search a source for a bone with a specified name |
|
// Input : *pSource - |
|
// *pName - |
|
// Output : int boneIndex, -1 if none |
|
//----------------------------------------------------------------------------- |
|
int FindLocalBoneNamed( const s_source_t *pSource, const char *pName ) |
|
{ |
|
if ( pName ) |
|
{ |
|
int i; |
|
for ( i = 0; i < pSource->numbones; i++ ) |
|
{ |
|
if ( !stricmp( pName, pSource->localBone[i].name ) ) |
|
return i; |
|
} |
|
|
|
pName = RenameBone( pName ); |
|
|
|
for ( i = 0; i < pSource->numbones; i++ ) |
|
{ |
|
if ( !stricmp( pName, pSource->localBone[i].name ) ) |
|
return i; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
|
|
// Returns the index to pName in g_bonetable |
|
int FindBoneInTable( const char *pName ) |
|
{ |
|
return findGlobalBone( pName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Contains a complete physical joint system with constraint relationships |
|
//----------------------------------------------------------------------------- |
|
// This class is really just a namespace for a set of globals... |
|
class CJointedModel |
|
{ |
|
public: |
|
s_source_t *m_pModel; |
|
int m_collisionCount; |
|
CPhysCollisionModel *m_pCollisionList; |
|
collisionpair_t *m_pCollisionPairs; |
|
float m_totalMass; |
|
int m_bonemap[MAXSTUDIOSRCBONES]; |
|
CJointConstraint *m_pConstraintList; |
|
int m_constraintCount; |
|
int m_totalVerts; |
|
int m_maxConvex; |
|
char m_rootName[128]; |
|
bool m_allowConcave; |
|
bool m_allowConcaveJoints; |
|
bool m_isMassCenterForced; |
|
bool m_noSelfCollisions; |
|
bool m_remove2d; |
|
Vector m_massCenterForced; |
|
|
|
float m_defaultDamping; |
|
float m_defaultRotdamping; |
|
float m_defaultInertia; |
|
float m_defaultDrag; |
|
CUtlVector<char> m_textCommands; |
|
CUtlVector<mergelist_t> m_mergeList; |
|
|
|
CJointedModel( void ); |
|
|
|
void SetSource( s_source_t *pmodel ); |
|
|
|
void InitBoneMap( void ); |
|
void SkipBone( int boneIndex ); |
|
void MergeBones( int parent, int child ); |
|
void AddMergeCommand( char const *pParent, char const *pChild ); |
|
bool ShouldProcessBone( int boneIndex ); |
|
int BoneIndex( const char *pName ); |
|
int RemapBone( int boneIndex ) const; |
|
void AppendCollisionModel( CPhysCollisionModel *pCollide ); |
|
void UnlinkCollisionModel( CPhysCollisionModel *pCollide ); |
|
CPhysCollisionModel *GetCollisionModel( const char *pName ); |
|
void AppendCollisionPair( const char *pName0, const char *pName1 ); |
|
void AddConstraint( const char *pJointName, int axis, jointlimit_t jointType, float limitMin, float limitMax, float friction ); |
|
int CollisionIndex( const char *pName ); |
|
void SortCollisionList( void ); |
|
void ForceMassCenter( const Vector ¢erOfMass ); |
|
void AllowConcave( void ) { m_allowConcave = true; } |
|
void AllowConcaveJoints() { m_allowConcaveJoints = true; } |
|
void Remove2DConvex() { m_remove2d = true; } |
|
void SetMaxConvex( int newMax ) { m_maxConvex = newMax; } |
|
void Simplify(); |
|
void DefaultDamping( float damping ); |
|
void DefaultRotdamping( float rotdamping ); |
|
void DefaultInertia( float inertia ); |
|
void DefaultDrag( float drag ); |
|
void SetTotalMass( float mass ); |
|
void SetAutoMass( void ); |
|
void SetNoSelfCollisions(); |
|
void SetCollisionModelDefaults( CPhysCollisionModel *pModel ); |
|
|
|
void JointDamping( const char *pJointName, float damping ); |
|
void JointRotdamping( const char *pJointName, float rotdamping ); |
|
void JointInertia( const char *pJointName, float inertia ); |
|
void JointMassBias( const char *pJointName, float massBias ); |
|
|
|
void AddText( const char *pText ) |
|
{ |
|
int len = strlen(pText); |
|
int count = m_textCommands.Size(); |
|
m_textCommands.AddMultipleToTail( len ); |
|
memcpy( m_textCommands.Base() + count, pText, len ); |
|
} |
|
void ComputeMass( void ); |
|
|
|
float m_flFrictionTimeIn; |
|
float m_flFrictionTimeOut; |
|
float m_flFrictionTimeHold; |
|
int m_iMinAnimatedFriction; |
|
int m_iMaxAnimatedFriction; |
|
bool m_bHasAnimatedFriction; |
|
|
|
bool m_bAssumeWorldspace; // assume the model is already declared in worldspace, regardless of bone names |
|
}; |
|
|
|
|
|
CJointedModel g_JointedModel; |
|
bool g_bJointed = false; |
|
|
|
CJointedModel::CJointedModel( void ) |
|
{ |
|
m_pModel = NULL; |
|
|
|
m_collisionCount = 0; |
|
m_pCollisionList = NULL; |
|
m_pCollisionPairs = NULL; |
|
m_totalMass = 1.0; |
|
|
|
memset( m_bonemap, 0, sizeof(m_bonemap) ); |
|
m_pConstraintList = NULL; |
|
m_constraintCount = 0; |
|
|
|
m_totalVerts = 0; |
|
|
|
// UNDONE: Move these defaults elsewhere? They are all overrideable by the QC/script |
|
m_defaultDamping = 0; |
|
m_defaultRotdamping = 0; |
|
m_defaultInertia = 1.0; |
|
m_defaultDrag = -1; |
|
m_allowConcave = false; |
|
m_allowConcaveJoints = false; |
|
m_remove2d = false; |
|
m_maxConvex = 40; |
|
m_isMassCenterForced = false; |
|
m_noSelfCollisions = false; |
|
m_massCenterForced.Init(); |
|
|
|
m_flFrictionTimeIn = 0.0f; |
|
m_flFrictionTimeOut = 0.0f; |
|
m_iMinAnimatedFriction = 1.0f; |
|
m_iMaxAnimatedFriction = 1.0f; |
|
m_bHasAnimatedFriction = false; |
|
} |
|
|
|
|
|
|
|
void CJointedModel::SetSource( s_source_t *pmodel ) |
|
{ |
|
m_pModel = pmodel; |
|
InitBoneMap(); |
|
m_totalVerts = pmodel->numvertices; |
|
} |
|
|
|
void CJointedModel::InitBoneMap( void ) |
|
{ |
|
for ( int i = 0; i < m_pModel->numbones; i++ ) |
|
{ |
|
m_bonemap[i] = i; |
|
} |
|
} |
|
|
|
void CJointedModel::SkipBone( int boneIndex ) |
|
{ |
|
if ( boneIndex >= 0 ) |
|
m_bonemap[boneIndex] = -1; |
|
} |
|
|
|
void CJointedModel::AddMergeCommand( char const *pParent, char const *pChild ) |
|
{ |
|
int i = m_mergeList.AddToTail(); |
|
m_mergeList[i].pParent = strdup(pParent); |
|
m_mergeList[i].pChild = strdup(pChild); |
|
} |
|
|
|
void CJointedModel::MergeBones( int parent, int child ) |
|
{ |
|
if ( parent < 0 || child < 0 ) |
|
return; |
|
|
|
int map = parent; |
|
int safety = 0; |
|
while ( m_bonemap[map] != map ) |
|
{ |
|
map = m_bonemap[map]; |
|
safety++; |
|
// infinite loop? |
|
if ( safety > m_pModel->numbones ) |
|
break; |
|
|
|
if ( map < 0 ) |
|
break; |
|
} |
|
|
|
m_bonemap[child] = map; |
|
} |
|
|
|
|
|
bool CJointedModel::ShouldProcessBone( int boneIndex ) |
|
{ |
|
if ( boneIndex >= 0 ) |
|
{ |
|
if ( m_bonemap[boneIndex] == boneIndex ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
int CJointedModel::BoneIndex( const char *pName ) |
|
{ |
|
pName = RenameBone( pName ); |
|
for ( int boneIndex = 0; boneIndex < m_pModel->numbones; boneIndex++ ) |
|
{ |
|
if ( !stricmp( m_pModel->localBone[boneIndex].name, pName ) ) |
|
return boneIndex; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
int CJointedModel::RemapBone( int boneIndex ) const |
|
{ |
|
if ( boneIndex >= 0 ) |
|
return m_bonemap[boneIndex]; |
|
return boneIndex; |
|
} |
|
|
|
void CJointedModel::AppendCollisionModel( CPhysCollisionModel *pCollide ) |
|
{ |
|
if ( m_isMassCenterForced ) |
|
{ |
|
physcollision->CollideSetMassCenter( pCollide->m_pCollisionData, m_massCenterForced ); |
|
} |
|
|
|
pCollide->m_pNext = m_pCollisionList; |
|
m_pCollisionList = pCollide; |
|
m_collisionCount++; |
|
} |
|
|
|
|
|
void CJointedModel::UnlinkCollisionModel( CPhysCollisionModel *pCollide ) |
|
{ |
|
CPhysCollisionModel **pList = &m_pCollisionList; |
|
|
|
if ( !pCollide ) |
|
return; |
|
|
|
while ( *pList ) |
|
{ |
|
CPhysCollisionModel *pNode = *pList; |
|
if ( pNode == pCollide ) |
|
{ |
|
*pList = pCollide->m_pNext; |
|
m_collisionCount--; |
|
pCollide->m_pNext = NULL; |
|
return; |
|
} |
|
pList = &pNode->m_pNext; |
|
} |
|
} |
|
|
|
int CJointedModel::CollisionIndex( const char *pName ) |
|
{ |
|
CPhysCollisionModel *pList = m_pCollisionList; |
|
int index = 0; |
|
while ( pList ) |
|
{ |
|
if ( !stricmp( pName, pList->m_name ) ) |
|
return index; |
|
|
|
pList = pList->m_pNext; |
|
index++; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sort the list so that parents come before their children |
|
//----------------------------------------------------------------------------- |
|
void CJointedModel::SortCollisionList( void ) |
|
{ |
|
if ( !m_collisionCount ) |
|
return; |
|
|
|
CPhysCollisionModel **pArray; |
|
pArray = new CPhysCollisionModel *[m_collisionCount]; |
|
CPhysCollisionModel *pList = m_pCollisionList; |
|
|
|
// make an array to make sorting easier |
|
int i = 0; |
|
|
|
while ( pList ) |
|
{ |
|
pArray[i++] = pList; |
|
pList = pList->m_pNext; |
|
} |
|
|
|
// really stupid bubble sort! |
|
// this is really inefficient but it was easy to code and there are never |
|
// more than maxConvex elements. |
|
bool swapped = true; |
|
|
|
while ( swapped ) |
|
{ |
|
swapped = false; |
|
// loop over all solids and swap any parent/child pairs that are out of order |
|
for ( i = 0; i < m_collisionCount; i++ ) |
|
{ |
|
CPhysCollisionModel *pPhys = pArray[i]; |
|
if ( !pPhys->m_parent ) |
|
continue; |
|
|
|
// Don't try to move ones where the pPhys and its parent have the same name |
|
// otherwise an infinite loop results |
|
if ( !Q_stricmp( pPhys->m_name, pPhys->m_parent ) ) |
|
continue; |
|
|
|
// find the parent |
|
int j; |
|
for ( j = 0; j < m_collisionCount; j++ ) |
|
{ |
|
if ( j == i ) |
|
continue; |
|
|
|
if ( !stricmp( pPhys->m_parent, pArray[j]->m_name ) ) |
|
break; |
|
} |
|
|
|
// if the child came before the parent, then swap the parent and child positions |
|
if ( j > i && j < m_collisionCount ) |
|
{ |
|
swapped = true; |
|
pArray[i] = pArray[j]; |
|
pArray[j] = pPhys; |
|
} |
|
} |
|
} |
|
|
|
// link up the sorted list |
|
for ( i = 0; i < m_collisionCount-1; i++ ) |
|
{ |
|
pArray[i]->m_pNext = pArray[i+1]; |
|
} |
|
// terminate |
|
pArray[i]->m_pNext = NULL; |
|
// point the list to first joint |
|
m_pCollisionList = pArray[0]; |
|
|
|
// delete the working array |
|
delete[] pArray; |
|
} |
|
|
|
void CJointedModel::AppendCollisionPair( const char *pName0, const char *pName1 ) |
|
{ |
|
collisionpair_t *pPair = new collisionpair_t; |
|
pPair->obj0 = -1; |
|
pPair->obj1 = -1; |
|
int jointIndex0 = FindLocalBoneNamed( m_pModel, pName0 ); |
|
pPair->pName0 = (jointIndex0 >= 0) ? m_pModel->localBone[jointIndex0].name : NULL; |
|
int jointIndex1 = FindLocalBoneNamed( m_pModel, pName1 ); |
|
pPair->pName1 = (jointIndex1 >= 0) ? m_pModel->localBone[jointIndex1].name : NULL; |
|
|
|
pPair->pNext = m_pCollisionPairs; |
|
m_pCollisionPairs = pPair; |
|
} |
|
|
|
void CJointedModel::ForceMassCenter( const Vector ¢erOfMass ) |
|
{ |
|
m_isMassCenterForced = true; |
|
m_massCenterForced = centerOfMass; |
|
} |
|
|
|
// called before processing, after the model has been simplified. |
|
// Update internal state due to simplification |
|
void CJointedModel::Simplify() |
|
{ |
|
for ( int i = 0; i < m_pModel->numbones; i++ ) |
|
{ |
|
if ( m_pModel->boneLocalToGlobal[i] < 0 ) |
|
{ |
|
SkipBone(i); |
|
} |
|
} |
|
|
|
extern int g_rootIndex; |
|
const char *pAnimationRootBone = g_bonetable[g_rootIndex].name; |
|
|
|
// merge this root bone with the root of animation |
|
MergeBones( FindLocalBoneNamed( m_pModel, pAnimationRootBone ), FindLocalBoneNamed( m_pModel, m_rootName ) ); |
|
|
|
} |
|
|
|
|
|
CPhysCollisionModel *CJointedModel::GetCollisionModel( const char *pName ) |
|
{ |
|
CPhysCollisionModel *pList = m_pCollisionList; |
|
while ( pList ) |
|
{ |
|
if ( !stricmp( pName, pList->m_name ) ) |
|
return pList; |
|
|
|
pList = pList->m_pNext; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CJointedModel::AddConstraint( const char *pJointName, int axis, jointlimit_t jointType, float limitMin, float limitMax, float friction ) |
|
{ |
|
// In the editor/qc friction values are shown as 5X so 1.0 can be the default. |
|
CJointConstraint *pConstraint = new CJointConstraint( pJointName, axis, jointType, limitMin, limitMax, friction * (1.0f/5.0f) ); |
|
|
|
// link it in |
|
pConstraint->m_pNext = m_pConstraintList; |
|
m_pConstraintList = pConstraint; |
|
m_constraintCount++; |
|
} |
|
|
|
void CJointedModel::DefaultDamping( float damping ) |
|
{ |
|
m_defaultDamping = damping; |
|
} |
|
|
|
void CJointedModel::DefaultRotdamping( float rotdamping ) |
|
{ |
|
m_defaultRotdamping = rotdamping; |
|
} |
|
|
|
void CJointedModel::DefaultInertia( float inertia ) |
|
{ |
|
m_defaultInertia = inertia; |
|
} |
|
|
|
void CJointedModel::SetTotalMass( float mass ) |
|
{ |
|
m_totalMass = mass; |
|
} |
|
|
|
void CJointedModel::SetAutoMass( void ) |
|
{ |
|
m_totalMass = -1; |
|
} |
|
|
|
void CJointedModel::SetNoSelfCollisions() |
|
{ |
|
m_noSelfCollisions = true; |
|
} |
|
|
|
void CJointedModel::SetCollisionModelDefaults( CPhysCollisionModel *pModel ) |
|
{ |
|
pModel->m_damping = m_defaultDamping; |
|
pModel->m_inertia = m_defaultInertia; |
|
pModel->m_rotdamping = m_defaultRotdamping; |
|
pModel->m_massBias = 1.0; |
|
|
|
// not written unless modified |
|
pModel->m_dragCoefficient = m_defaultDrag; |
|
} |
|
|
|
|
|
|
|
void CJointedModel::ComputeMass( void ) |
|
{ |
|
// already set |
|
if ( m_totalMass >= 0 ) |
|
return; |
|
|
|
CPhysCollisionModel *pList = m_pCollisionList; |
|
m_totalMass = 0; |
|
|
|
while ( pList ) |
|
{ |
|
char* pSurfaceProps = GetSurfaceProp( pList->m_name ); |
|
int index = physprops->GetSurfaceIndex( pSurfaceProps ); |
|
float density, thickness; |
|
physprops->GetPhysicsProperties( index, &density, &thickness, NULL, NULL ); |
|
|
|
if ( thickness > 0 ) |
|
{ |
|
m_totalMass += pList->m_surfaceArea * thickness * CUBIC_METERS_PER_CUBIC_INCH * density; |
|
} |
|
else |
|
{ |
|
// density is in kg/m^3, volume is in in^3 |
|
m_totalMass += pList->m_volume * CUBIC_METERS_PER_CUBIC_INCH * density; |
|
} |
|
pList = pList->m_pNext; |
|
} |
|
|
|
if( !g_quiet ) |
|
{ |
|
printf("Computed Mass: %.2f kg\n", m_totalMass ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates a collision object using the defaults in joints |
|
// Input : &joints - joint system to create the model in |
|
// *pJointName - name to give this model |
|
// Output : static CPhysCollisionModel |
|
//----------------------------------------------------------------------------- |
|
static CPhysCollisionModel *InitCollisionModel( CJointedModel &joints, const char *pJointName ) |
|
{ |
|
CPhysCollisionModel *pModel = joints.GetCollisionModel( pJointName ); |
|
if ( !pModel ) |
|
{ |
|
int boneIndex = joints.BoneIndex( pJointName ); |
|
if ( boneIndex < 0 ) |
|
return NULL; |
|
|
|
pModel = new CPhysCollisionModel; |
|
// this name is the same as pJointName, but guaranteed to be non-volatile (we'd have to copy pJointName) |
|
pModel->m_name = joints.m_pModel->localBone[boneIndex].name; |
|
if ( joints.m_pModel->localBone[boneIndex].parent >= 0 ) |
|
{ |
|
pModel->m_parent = joints.m_pModel->localBone[joints.m_pModel->localBone[boneIndex].parent].name; |
|
} |
|
else |
|
{ |
|
pModel->m_parent = NULL; |
|
} |
|
|
|
joints.SetCollisionModelDefaults( pModel ); |
|
joints.AppendCollisionModel( pModel ); |
|
} |
|
|
|
return pModel; |
|
} |
|
|
|
void CJointedModel::JointDamping( const char *pJointName, float damping ) |
|
{ |
|
CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName ); |
|
if ( pModel ) |
|
{ |
|
pModel->m_damping = damping; |
|
} |
|
} |
|
|
|
void CJointedModel::JointRotdamping( const char *pJointName, float rotdamping ) |
|
{ |
|
CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName ); |
|
if ( pModel ) |
|
{ |
|
pModel->m_rotdamping = rotdamping; |
|
} |
|
} |
|
|
|
void CJointedModel::JointMassBias( const char *pJointName, float massBias ) |
|
{ |
|
CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName ); |
|
if ( pModel ) |
|
{ |
|
pModel->m_massBias = massBias; |
|
} |
|
} |
|
|
|
void CJointedModel::JointInertia( const char *pJointName, float inertia ) |
|
{ |
|
CPhysCollisionModel *pModel = InitCollisionModel( *this, pJointName ); |
|
if ( pModel ) |
|
{ |
|
pModel->m_inertia = inertia; |
|
} |
|
} |
|
|
|
|
|
void CJointedModel::DefaultDrag( float drag ) |
|
{ |
|
m_defaultDrag = drag; |
|
} |
|
|
|
// ---------------------------------------------------------- |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Transforms the source's verts into "world" space |
|
// Input : *psource - |
|
// *worldVerts - |
|
//----------------------------------------------------------------------------- |
|
void ConvertToWorldSpace( CJointedModel &joints, s_source_t *psource, CUtlVector<Vector> &worldVerts ) |
|
{ |
|
int i, n; |
|
|
|
if (!joints.m_bAssumeWorldspace) |
|
{ |
|
matrix3x4_t boneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix |
|
CalcBoneTransforms( g_panimation[0], 0, boneToWorld ); |
|
|
|
for (i = 0; i < psource->numvertices; i++) |
|
{ |
|
Vector tmp,tmp2; |
|
worldVerts[i].Init( 0, 0, 0 ); |
|
|
|
int nBoneCount = psource->vertex[i].boneweight.numbones; |
|
for (n = 0; n < nBoneCount; n++) |
|
{ |
|
// convert to Half-Life world space |
|
// convert vertex into original models' bone local space |
|
int localBone = psource->vertex[i].boneweight.bone[n]; |
|
int globalBone = psource->boneLocalToGlobal[localBone]; |
|
Assert( localBone >= 0 ); |
|
Assert( globalBone >= 0 ); |
|
|
|
matrix3x4_t boneToPose; |
|
ConcatTransforms( psource->boneToPose[localBone], g_bonetable[globalBone].srcRealign, boneToPose ); |
|
VectorITransform( psource->vertex[i].position, boneToPose, tmp2 ); |
|
|
|
// now transform to that bone's world-space position in this animation |
|
VectorTransform(tmp2, boneToWorld[globalBone], tmp ); |
|
VectorMA( worldVerts[i], psource->vertex[i].boneweight.weight[n], tmp, worldVerts[i] ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
matrix3x4_t srcBoneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix |
|
BuildRawTransforms( psource, "BindPose", 0, psource->scale, psource->adjust, psource->rotation, 0, srcBoneToWorld ); |
|
|
|
for (i = 0; i < psource->numvertices; i++) |
|
{ |
|
Vector tmp; |
|
worldVerts[i].Init( 0, 0, 0 ); |
|
|
|
int nBoneCount = psource->vertex[i].boneweight.numbones; |
|
for (n = 0; n < nBoneCount; n++) |
|
{ |
|
int localBone = psource->vertex[i].boneweight.bone[n]; |
|
Assert( localBone >= 0 ); |
|
|
|
// convert vertex into world space |
|
VectorTransform( psource->vertex[i].position, srcBoneToWorld[localBone], tmp ); |
|
// just assume the model is in identity space |
|
// FIXME: shouldn't this do an inverse xform of the default boneToWorld? |
|
|
|
VectorMA( worldVerts[i], psource->vertex[i].boneweight.weight[n], tmp, worldVerts[i] ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Transforms the set of verts into the space of a particular bone |
|
// Input : *psource - |
|
// boneIndex - |
|
// *boneVerts - |
|
//----------------------------------------------------------------------------- |
|
void ConvertToBoneSpace( s_source_t *psource, int boneIndex, CUtlVector<Vector> &boneVerts ) |
|
{ |
|
int i; |
|
|
|
int remapIndex = psource->boneLocalToGlobal[boneIndex]; |
|
matrix3x4_t boneToPose; |
|
if ( remapIndex < 0 ) |
|
{ |
|
MdlWarning("Error! physics for unused bone %s\n", psource->localBone[boneIndex].name ); |
|
MatrixCopy( psource->boneToPose[boneIndex], boneToPose ); |
|
} |
|
else |
|
{ |
|
ConcatTransforms( psource->boneToPose[boneIndex], g_bonetable[remapIndex].srcRealign, boneToPose ); |
|
} |
|
|
|
for (i = 0; i < psource->numvertices; i++) |
|
{ |
|
VectorITransform(psource->vertex[i].position, boneToPose, boneVerts[i] ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Test this face to see if any of its verts are assigned to a particular bone |
|
// Input : &joints - |
|
// *pmodel - |
|
// *face - |
|
// boneIndex - |
|
// Output : Returns true if this face has a vert assigned to boneIndex |
|
//----------------------------------------------------------------------------- |
|
bool FaceHasVertOnBone( const CJointedModel &joints, s_source_t *pSource, s_face_t *face, int boneIndex ) |
|
{ |
|
if ( boneIndex < 0 ) |
|
return true; |
|
|
|
int j; |
|
s_boneweight_t *pweight; |
|
pweight = &pSource->vertex[ face->a ].boneweight; |
|
for ( j = 0; j < pweight->numbones; j++ ) |
|
{ |
|
// assigned to boneIndex? |
|
if ( joints.RemapBone( pweight->bone[j] ) == boneIndex ) |
|
return true; |
|
} |
|
|
|
pweight = &pSource->vertex[ face->b ].boneweight; |
|
for ( j = 0; j < pweight->numbones; j++ ) |
|
{ |
|
// assigned to boneIndex? |
|
if ( joints.RemapBone( pweight->bone[j] ) == boneIndex ) |
|
return true; |
|
} |
|
|
|
pweight = &pSource->vertex[ face->c ].boneweight; |
|
for ( j = 0; j < pweight->numbones; j++ ) |
|
{ |
|
// assigned to boneIndex? |
|
if ( joints.RemapBone( pweight->bone[j] ) == boneIndex ) |
|
return true; |
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fixup the pointers in this face to reference the mesh globally (source relative) |
|
// (faces are mesh relative, each source has several meshes) |
|
// Input : *pout - |
|
// *pmesh - |
|
// *pin - |
|
//----------------------------------------------------------------------------- |
|
void GlobalFace( s_face_t *pout, s_mesh_t *pmesh, s_face_t *pin ) |
|
{ |
|
pout->a = pmesh->vertexoffset + pin->a; |
|
pout->b = pmesh->vertexoffset + pin->b; |
|
pout->c = pmesh->vertexoffset + pin->c; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copy all verts assigned to this bone. |
|
// NOTE: Leaves gaps in the model around joints |
|
// Input : **verts - |
|
// *worldVerts - |
|
// &joints - |
|
// boneIndex - |
|
// Output : int vertCount |
|
//----------------------------------------------------------------------------- |
|
int CopyVertsByBone( Vector **verts, Vector *worldVerts, const CJointedModel &joints, int boneIndex ) |
|
{ |
|
int vertCount = 0; |
|
s_source_t *pmodel = joints.m_pModel; |
|
|
|
// loop through each vert to find those assigned to this bone |
|
for ( int i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
s_boneweight_t *pweight = &pmodel->vertex[ i ].boneweight; |
|
|
|
// look at each assignment for this vert |
|
for ( int j = 0; j < pweight->numbones; j++ ) |
|
{ |
|
// Discover the local bone index for this bone |
|
int localBone = pweight->bone[j]; |
|
|
|
// assigned to boneIndex? |
|
if ( joints.RemapBone( localBone ) == boneIndex ) |
|
{ |
|
// add this vert to model |
|
verts[vertCount++] = &worldVerts[i]; |
|
} |
|
} |
|
} |
|
|
|
return vertCount; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copy all verts that are referenced by a face which has a vert assigned |
|
// to this bone. |
|
// NOTE: convex hulls of each bone will overlap at the joints |
|
// Input : **verts - |
|
// *worldVerts - |
|
// &joints - |
|
// boneIndex - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CopyFaceVertsByBone( Vector **verts, Vector *worldVerts, const CJointedModel &joints, int boneIndex ) |
|
{ |
|
int vertCount = 0; |
|
s_source_t *pmodel = joints.m_pModel; |
|
|
|
int *vertChecked = new int[pmodel->numvertices]; |
|
for ( int b = 0; b < pmodel->numvertices; b++ ) |
|
{ |
|
vertChecked[b] = 0; |
|
} |
|
|
|
for ( int i = 0; i < pmodel->nummeshes; i++ ) |
|
{ |
|
s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i]; |
|
for ( int j = 0; j < pmesh->numfaces; j++ ) |
|
{ |
|
s_face_t *face = pmodel->face + pmesh->faceoffset + j; |
|
s_face_t globalFace; |
|
GlobalFace( &globalFace, pmesh, face ); |
|
if ( FaceHasVertOnBone( joints, pmodel, &globalFace, boneIndex ) ) |
|
{ |
|
if ( !vertChecked[globalFace.a] ) |
|
{ |
|
// add this vert to model |
|
verts[vertCount++] = &worldVerts[globalFace.a]; |
|
} |
|
if ( !vertChecked[globalFace.b] ) |
|
{ |
|
// add this vert to model |
|
verts[vertCount++] = &worldVerts[globalFace.b]; |
|
} |
|
if ( !vertChecked[globalFace.c] ) |
|
{ |
|
// add this vert to model |
|
verts[vertCount++] = &worldVerts[globalFace.c]; |
|
} |
|
// mark these verts so you only add them once |
|
vertChecked[globalFace.a] = 1; |
|
vertChecked[globalFace.b] = 1; |
|
vertChecked[globalFace.c] = 1; |
|
} |
|
} |
|
} |
|
|
|
delete[] vertChecked; |
|
return vertCount; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find all verts that differ only by texture coordinates - this allows |
|
// us to ignore texture coordinates on collision models |
|
// Input : *weldTable - output table |
|
// *pmodel - input model |
|
//----------------------------------------------------------------------------- |
|
void BuildVertWeldTable( int *weldTable, s_source_t *pmodel ) |
|
{ |
|
for ( int i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
bool found = false; |
|
for ( int j = 0; j < i; j++ ) |
|
{ |
|
float dist = (pmodel->vertex[j].position - pmodel->vertex[i].position).Length(); |
|
float normalDist = DotProduct( pmodel->vertex[j].normal, pmodel->vertex[i].normal ); |
|
if ( dist <= g_WeldVertEpsilon && normalDist > g_WeldNormalEpsilon ) |
|
{ |
|
found = true; |
|
weldTable[i] = j; |
|
break; |
|
} |
|
} |
|
|
|
if ( !found ) |
|
{ |
|
weldTable[i] = i; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: marks all verts with a unique ID. Each set of connected verts has |
|
// the same ID. IDs are the index of the lowest numbered face on the |
|
// mesh |
|
// Input : *vertID - array that holds IDs |
|
// *pmodel - model to process |
|
//----------------------------------------------------------------------------- |
|
void MarkConnectedMeshes( int *vertID, s_source_t *pmodel, int *vertMap ) |
|
{ |
|
int i; |
|
|
|
// mark all verts as max faceid + 1 |
|
for ( i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
// If these verts have been welded to a lower-index vert, mark them |
|
// as already processed to avoid making additional convex objects out of them. |
|
if ( vertMap[i] != i ) |
|
{ |
|
vertID[i] = -1; |
|
} |
|
else |
|
{ |
|
vertID[i] = pmodel->numfaces+1; |
|
} |
|
} |
|
|
|
int marked = 0; |
|
int faceid = 0; |
|
// iterate the face list, minimizing the vertID at each vert |
|
// until we have an iteration where no vertIDs are changed |
|
do |
|
{ |
|
marked = 0; |
|
faceid = 0; |
|
|
|
for ( i = 0; i < pmodel->nummeshes; i++ ) |
|
{ |
|
s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i]; |
|
for ( int j = 0; j < pmesh->numfaces; j++ ) |
|
{ |
|
s_face_t *face = pmodel->face + pmesh->faceoffset + j; |
|
s_face_t globalFace; |
|
GlobalFace( &globalFace, pmesh, face ); |
|
// account for welding |
|
globalFace.a = vertMap[globalFace.a]; |
|
globalFace.b = vertMap[globalFace.b]; |
|
globalFace.c = vertMap[globalFace.c]; |
|
|
|
|
|
// find min(faceid, vertID[a], vertID[b], vertID[c]); |
|
int newid = min(faceid, vertID[globalFace.a]); |
|
newid = min( newid, vertID[globalFace.b]); |
|
newid = min( newid, vertID[globalFace.c]); |
|
|
|
// mark all verts with the minimum, count the number we had to mark |
|
if ( vertID[globalFace.a] != newid ) |
|
{ |
|
vertID[globalFace.a] = newid; |
|
marked++; |
|
} |
|
if ( vertID[globalFace.b] != newid ) |
|
{ |
|
vertID[globalFace.b] = newid; |
|
marked++; |
|
} |
|
if ( vertID[globalFace.c] != newid ) |
|
{ |
|
vertID[globalFace.c] = newid; |
|
marked++; |
|
} |
|
faceid++; |
|
} |
|
} |
|
} while ( marked != 0 ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds a CPhysCollisionModel in a linked list of models. |
|
// Input : *pHead - |
|
// *pName - |
|
// Output : CPhysCollisionModel |
|
//----------------------------------------------------------------------------- |
|
CPhysCollisionModel *FindObjectInList( CPhysCollisionModel *pHead, const char *pName ) |
|
{ |
|
while ( pHead ) |
|
{ |
|
if ( !stricmp( pName, pHead->m_name ) ) |
|
break; |
|
pHead = pHead->m_pNext; |
|
} |
|
|
|
return pHead; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fix all bones to reference the remapped/collapsed bone structure |
|
// Input : *pSource - |
|
// *pList - |
|
//----------------------------------------------------------------------------- |
|
void FixBoneList( int *boneMap, const s_source_t *pSource, CPhysCollisionModel *pList ) |
|
{ |
|
if ( !g_bJointed ) |
|
return; |
|
|
|
CPhysCollisionModel *pmodel = pList; |
|
while ( pmodel ) |
|
{ |
|
int nodeIndex = FindLocalBoneNamed( pSource, pmodel->m_name ); |
|
if ( nodeIndex < 0 ) |
|
{ |
|
MdlWarning("Physics for unknown bone %s\n", pmodel->m_name ); |
|
} |
|
else |
|
{ |
|
int count = 0; |
|
// remove simplified bones |
|
while ( pSource->boneLocalToGlobal[nodeIndex] < 0 ) |
|
{ |
|
if ( count++ > MAXSTUDIOSRCBONES ) |
|
break; |
|
|
|
// simplified out, move up to the parent |
|
nodeIndex = pSource->localBone[nodeIndex].parent; |
|
} |
|
|
|
if ( nodeIndex >= 0 ) |
|
{ |
|
// bone collapse may have changed parent hierarchy, and the root name. |
|
// The vertices are converted to the new reference by ConvertToWorldSpace(), as well as RemapVerticesToGlobalBones() |
|
pmodel->m_name = g_bonetable[ pSource->boneLocalToGlobal[nodeIndex] ].name; |
|
pmodel->m_parent = NULL; |
|
int parentIndex = pSource->localBone[nodeIndex].parent; |
|
if ( parentIndex >= 0 && parentIndex != nodeIndex ) |
|
{ |
|
parentIndex = boneMap[parentIndex]; |
|
if (pSource->boneLocalToGlobal[parentIndex] < 0) |
|
{ |
|
pmodel->m_parent = pSource->localBone[parentIndex].name; |
|
} |
|
else |
|
{ |
|
pmodel->m_parent = g_bonetable[ pSource->boneLocalToGlobal[parentIndex] ].name; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
MdlWarning("Physics for unknown bone %s\n", pmodel->m_name ); |
|
} |
|
} |
|
|
|
pmodel = pmodel->m_pNext; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fixup all references to parents by walking up on models whose parents |
|
// have no collision geometry. Bones without geometry cannot be physically |
|
// simulated, so they must be removed. |
|
// NOTE: This is broken. It won't work for tree structures with an empty parent |
|
// (i.e. 2 children attached to a parent bone that has no physics geometry - thus empty) |
|
// It will not convert that parent into a constraint between 2 children |
|
// Input : *pList - |
|
// *pSource - |
|
// *pParentName - |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *FixParent( CPhysCollisionModel *pList, s_source_t *pSource, const char *pParentName ) |
|
{ |
|
while ( pParentName ) |
|
{ |
|
if ( FindObjectInList( pList, pParentName ) ) |
|
{ |
|
return pParentName; |
|
} |
|
int nodeIndex = FindLocalBoneNamed( pSource, pParentName ); |
|
if ( nodeIndex < 0 ) |
|
return NULL; |
|
int parentIndex = pSource->localBone[nodeIndex].parent; |
|
if ( parentIndex < 0 ) |
|
{ |
|
break; |
|
} |
|
|
|
pParentName = pSource->localBone[parentIndex].name; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
struct boundingvolume_t |
|
{ |
|
Vector mins; |
|
Vector maxs; |
|
}; |
|
|
|
|
|
void CreateCollide( CPhysCollisionModel *pBase, CPhysConvex **pElements, int elementCount, const boundingvolume_t &bv ) |
|
{ |
|
int i; |
|
|
|
if ( !pBase ) |
|
return; |
|
|
|
// NOTE: Must do this before building collide |
|
pBase->m_volume = 0; |
|
pBase->m_surfaceArea = 0; |
|
for ( i = 0; i < elementCount; i++ ) |
|
{ |
|
pBase->m_volume += physcollision->ConvexVolume( pElements[i] ); |
|
pBase->m_surfaceArea += physcollision->ConvexSurfaceArea( pElements[i] ); |
|
} |
|
|
|
convertconvexparams_t params; |
|
params.Defaults(); |
|
params.buildOuterConvexHull = true; |
|
params.buildDragAxisAreas = true; |
|
Vector size = bv.maxs - bv.mins; |
|
|
|
int largest = 0; |
|
float minSurfaceArea = -1.0f; |
|
for ( i = 0; i < 3; i++ ) |
|
{ |
|
if ( size[i] > size[largest] ) |
|
{ |
|
largest = i; |
|
} |
|
|
|
int other = (i+1)%3; |
|
int cross = (i+2)%3; |
|
float surfaceArea = size[other] * size[cross]; |
|
if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea ) |
|
{ |
|
minSurfaceArea = surfaceArea; |
|
} |
|
} |
|
// this can be really slow with super-large models and a low error tolerance |
|
// Basically you get a ray cast through each square of epsilon surface area on each OBB side |
|
// So compute it for 0.01% error (on the smallest side, less on larger sides) |
|
params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-4f, 0.25f, 128.0f ); |
|
|
|
Vector tmp = size; |
|
tmp[largest] = 0; |
|
float len = tmp.Length(); |
|
if ( len > 0 ) |
|
{ |
|
float sizeRatio = size[largest] / len; |
|
// HACKHACK: Hardcoded size ratio to induce damping |
|
// This prevents long skinny objects from rolling endlessly |
|
if ( sizeRatio > 9 ) |
|
{ |
|
pBase->m_rotdamping = 1.0f; |
|
} |
|
} |
|
// THIS DESTROYS pConvex!! |
|
pBase->m_pCollisionData = physcollision->ConvertConvexToCollideParams( pElements, elementCount, params ); |
|
|
|
// debug output for the drag area calculations |
|
#if 0 |
|
Msg("Drag epsilon is %.3f\n", params.dragAreaEpsilon ); |
|
Vector areas = physcollision->CollideGetOrthographicAreas( pBase->m_pCollisionData ); |
|
Msg("Drag fractions are %.3f %.3f %.3f\n", areas.x, areas.y, areas.z ); |
|
#endif |
|
} |
|
|
|
|
|
// is this list of verts contained in a slab of epsilon width? If so, it's probably |
|
// an error of some kind - we shouldn't be authoring flat or 2d collision models |
|
bool IsApproximatelyPlanar( Vector **verts, int vertCount, float epsilon ) |
|
{ |
|
if ( vertCount < 4 ) |
|
return true; |
|
|
|
// If we're using an un-welded model, then this may generate a degenerate normal |
|
// loop to search for an actual plane |
|
int v0 = 1, v1 = 2; |
|
Vector normal; |
|
while ( v0 < vertCount && v1 < vertCount ) |
|
{ |
|
Vector edge0 = *verts[v0] - *verts[0]; |
|
Vector edge1 = *verts[v1] - *verts[0]; |
|
|
|
normal = CrossProduct( edge0, edge1 ); |
|
float len = VectorNormalize( normal ); |
|
if ( len > 0.001 ) |
|
break; |
|
if ( edge0.Length() < 0.001 ) |
|
{ |
|
// verts[0] and v0 are coincident, try new verts |
|
v0++; |
|
v1++; |
|
} |
|
else |
|
{ |
|
// v0 seems fine, try a new v1 -- it's probably coincident with v0 |
|
v1++; |
|
} |
|
} |
|
|
|
// form the plane and project all of the verts into it |
|
float minDist = DotProduct( normal, *verts[0] ); |
|
float maxDist = minDist; |
|
|
|
for ( int i = 0; i < vertCount; i++ ) |
|
{ |
|
float d = DotProduct( *verts[i], normal ); |
|
if ( d < minDist ) |
|
{ |
|
minDist = d; |
|
} |
|
else if ( d > maxDist ) |
|
{ |
|
maxDist = d; |
|
} |
|
// at least one vert out of the plane, we've got something 3 dimensional |
|
if ( fabsf(maxDist-minDist) > epsilon ) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
void BuildConvexListByVertID( s_source_t *pmodel, CUtlVector<convexlist_t> &convexList, CUtlVector<int> &vertList, CUtlVector<int> &vertID ) |
|
{ |
|
// loop through each island of verts and append it to the convex list |
|
convexlist_t current; |
|
for ( int i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
// already processed this group |
|
if ( vertID[i] < 0 || vertID[i] > pmodel->numfaces ) |
|
continue; |
|
|
|
current.firstVertIndex = vertList.Count(); |
|
current.numVertIndex = 0; |
|
|
|
int id = vertID[i]; |
|
|
|
for ( int j = i; j < pmodel->numvertices; j++ ) |
|
{ |
|
if ( vertID[j] == id ) |
|
{ |
|
vertList.AddToTail(j); |
|
current.numVertIndex++; |
|
// don't reuse this vert |
|
vertID[j] = -1; |
|
} |
|
} |
|
convexList.AddToTail(current); |
|
} |
|
} |
|
|
|
// build a list of vertex indices for each connected sub-piece |
|
void BuildSingleConvexForFaceList( s_source_t *pmodel, CUtlVector<convexlist_t> &convexList, CUtlVector<int> &vertList, const CUtlVector<s_face_t> &faceList ) |
|
{ |
|
CUtlVector<int> vertID; |
|
vertID.SetCount(pmodel->numvertices); |
|
int i; |
|
for ( i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
vertID[i] = -1; |
|
} |
|
|
|
for ( i = 0; i < faceList.Count(); i++ ) |
|
{ |
|
const s_face_t &globalFace = faceList[i]; |
|
vertID[globalFace.a] = 1; |
|
vertID[globalFace.b] = 1; |
|
vertID[globalFace.c] = 1; |
|
} |
|
BuildConvexListByVertID( pmodel, convexList, vertList, vertID ); |
|
} |
|
|
|
void BuildConvexListForFaceList( s_source_t *pmodel, CUtlVector<convexlist_t> &convexList, CUtlVector<int> &vertList, const CUtlVector<s_face_t> &faceList ) |
|
{ |
|
CUtlVector<int> weldTable; |
|
weldTable.SetCount(pmodel->numvertices); |
|
BuildVertWeldTable( weldTable.Base(), pmodel ); |
|
|
|
int i; |
|
CUtlVector<int> vertID; |
|
vertID.SetCount(pmodel->numvertices); |
|
|
|
// mark all verts as max faceid + 1 |
|
for ( i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
// If these verts have been welded to a lower-index vert, mark them |
|
// as already processed to avoid making additional convex objects out of them. |
|
if ( weldTable[i] != i ) |
|
{ |
|
vertID[i] = -1; |
|
} |
|
else |
|
{ |
|
vertID[i] = pmodel->numfaces+1; |
|
} |
|
} |
|
|
|
Assert(convexList.Count()==0); |
|
Assert(vertList.Count()==0); |
|
|
|
int marked = 0; |
|
int faceid = 0; |
|
// iterate the face list, minimizing the vertID at each vert |
|
// until we have an iteration where no vertIDs are changed |
|
do |
|
{ |
|
marked = 0; |
|
faceid = 0; |
|
|
|
// basically this flood fills ids out to the verts until each island of connected |
|
// verts shares a single id (so new verts got marked) |
|
for ( i = 0; i < faceList.Count(); i++ ) |
|
{ |
|
s_face_t globalFace = faceList[i]; |
|
// account for welding |
|
globalFace.a = weldTable[globalFace.a]; |
|
globalFace.b = weldTable[globalFace.b]; |
|
globalFace.c = weldTable[globalFace.c]; |
|
|
|
|
|
int newid = min(i, vertID[globalFace.a]); |
|
newid = min( newid, vertID[globalFace.b]); |
|
newid = min( newid, vertID[globalFace.c]); |
|
|
|
// mark all verts with the minimum, count the number we had to mark |
|
if ( vertID[globalFace.a] != newid ) |
|
{ |
|
vertID[globalFace.a] = newid; |
|
marked++; |
|
} |
|
if ( vertID[globalFace.b] != newid ) |
|
{ |
|
vertID[globalFace.b] = newid; |
|
marked++; |
|
} |
|
if ( vertID[globalFace.c] != newid ) |
|
{ |
|
vertID[globalFace.c] = newid; |
|
marked++; |
|
} |
|
} |
|
} while ( marked != 0 ); |
|
|
|
BuildConvexListByVertID( pmodel, convexList, vertList, vertID ); |
|
} |
|
|
|
|
|
// take a list of convex elements (lists of vert indices into master vert list) and build CPhysConvex out of them |
|
// return true if there are no errors detected |
|
bool BuildConvexesForLists( CUtlVector<CPhysConvex *> &convexOut, const CUtlVector<convexlist_t> &convexList, const CUtlVector<int> &vertList, const CUtlVector<Vector> &worldspaceVerts, bool bRemove2d ) |
|
{ |
|
bool bValid = true; |
|
CUtlVector<Vector *> vertsThisConvex; |
|
for ( int i = 0; i < convexList.Count(); i++ ) |
|
{ |
|
const convexlist_t &elem = convexList[i]; |
|
vertsThisConvex.RemoveAll(); |
|
for ( int j = 0; j < elem.numVertIndex; j++ ) |
|
{ |
|
// this is ok because physcollision won't modify these, but wants non-const |
|
Vector *pVert = const_cast<Vector *>(&worldspaceVerts[vertList[j + elem.firstVertIndex]]); |
|
vertsThisConvex.AddToTail( pVert ); |
|
} |
|
|
|
// need at least 3 verts to build a CPhysConvex |
|
if ( vertsThisConvex.Count() > 2 ) |
|
{ |
|
const float g_epsilon_2d = 0.5f; |
|
// HACKHACK: A heuristic to detect models without smoothing groups set |
|
// UNDONE: Do a BSP to decompose arbitrary models to convex? |
|
if ( IsApproximatelyPlanar( vertsThisConvex.Base(), vertsThisConvex.Count(), g_epsilon_2d ) ) |
|
{ |
|
if ( bRemove2d ) |
|
continue; |
|
MdlWarning("Model has 2-dimensional geometry (less than %.3f inches thick on any axis)!!!\n", g_epsilon_2d ); |
|
bValid = false; |
|
} |
|
// go ahead and build it out |
|
CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertsThisConvex.Base(), vertsThisConvex.Count() ); |
|
if ( pConvex ) |
|
{ |
|
// Got something valid, attach this convex data to the root model |
|
physcollision->SetConvexGameData( pConvex, 0 ); |
|
convexOut.AddToTail(pConvex); |
|
} |
|
} |
|
} |
|
|
|
return bValid; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Build a jointed collision model with constraints |
|
// Input : &joints - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int ProcessJointedModel( CJointedModel &joints ) |
|
{ |
|
if( !g_quiet ) |
|
{ |
|
printf("Processing jointed collision model\n" ); |
|
} |
|
s_source_t *pmodel = joints.m_pModel; |
|
// loop through each bone and form a collision model |
|
for ( int boneIndex = 0; boneIndex < joints.m_pModel->numbones; boneIndex++ ) |
|
{ |
|
if ( !joints.ShouldProcessBone( boneIndex ) ) |
|
continue; |
|
|
|
CUtlVector<Vector> bonespaceVerts; |
|
bonespaceVerts.SetCount(pmodel->numvertices); |
|
ConvertToBoneSpace( joints.m_pModel, boneIndex, bonespaceVerts ); |
|
CUtlVector<s_face_t> faceList; |
|
CUtlVector<convexlist_t> convexList; |
|
CUtlVector<int> vertList; |
|
CUtlVector<CPhysConvex *> convexOut; |
|
bool bValid = false; |
|
|
|
for ( int i = 0; i < pmodel->nummeshes; i++ ) |
|
{ |
|
s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i]; |
|
for ( int j = 0; j < pmesh->numfaces; j++ ) |
|
{ |
|
s_face_t *face = pmodel->face + pmesh->faceoffset + j; |
|
s_face_t globalFace; |
|
GlobalFace( &globalFace, pmesh, face ); |
|
if ( FaceHasVertOnBone( joints, pmodel, &globalFace, boneIndex ) ) |
|
{ |
|
faceList.AddToTail( globalFace ); |
|
} |
|
} |
|
|
|
if ( joints.m_allowConcaveJoints ) |
|
{ |
|
BuildConvexListForFaceList( pmodel, convexList, vertList, faceList ); |
|
} |
|
else |
|
{ |
|
BuildSingleConvexForFaceList( pmodel, convexList, vertList, faceList ); |
|
} |
|
|
|
bValid = BuildConvexesForLists( convexOut, convexList, vertList, bonespaceVerts, joints.m_remove2d ); |
|
} |
|
|
|
if ( convexOut.Count() > joints.m_maxConvex ) |
|
{ |
|
MdlWarning("COSTLY COLLISION MODEL!!!! (%d parts - %d allowed)\n", convexOut.Count(), joints.m_maxConvex ); |
|
bValid = false; |
|
} |
|
|
|
if ( !bValid && convexOut.Count() ) |
|
{ |
|
MdlWarning("Error with convex elements of %s, building single convex!!!!\n", pmodel->filename ); |
|
for ( int i = 0; i < convexOut.Count(); i++ ) |
|
{ |
|
physcollision->ConvexFree( convexOut[i] ); |
|
} |
|
convexOut.Purge(); |
|
} |
|
|
|
if ( convexOut.Count() ) |
|
{ |
|
int i; |
|
|
|
CPhysCollisionModel *pPhys = InitCollisionModel( joints, pmodel->localBone[boneIndex].name ); |
|
|
|
pPhys->m_mass = 1.0; |
|
pPhys->m_name = joints.m_pModel->localBone[boneIndex].name; |
|
if ( joints.m_pModel->localBone[boneIndex].parent >= 0 ) |
|
{ |
|
pPhys->m_parent = joints.m_pModel->localBone[joints.m_pModel->localBone[boneIndex].parent].name; |
|
} |
|
else |
|
{ |
|
pPhys->m_parent = NULL; |
|
} |
|
|
|
boundingvolume_t bv; |
|
ClearBounds( bv.mins, bv.maxs ); |
|
int vertCount = 0; |
|
for ( i = 0; i < convexList.Count(); i++ ) |
|
{ |
|
const convexlist_t &elem = convexList[i]; |
|
for ( int j = 0; j < elem.numVertIndex; j++ ) |
|
{ |
|
AddPointToBounds( bonespaceVerts[vertList[elem.firstVertIndex+j]], bv.mins, bv.maxs ); |
|
vertCount++; |
|
} |
|
} |
|
for ( i = 0; i < convexOut.Count(); i++ ) |
|
{ |
|
// Attach this convex data to this particular bone |
|
int globalBoneIndex = joints.m_pModel->boneLocalToGlobal[boneIndex]; |
|
physcollision->SetConvexGameData( convexOut[i], globalBoneIndex + 1 ); |
|
} |
|
|
|
CreateCollide( pPhys, convexOut.Base(), convexOut.Count(), bv ); |
|
if( !g_quiet ) |
|
{ |
|
printf("%-24s (%3d verts, %d convex elements) volume: %4.2f\n", pPhys->m_name, vertCount, convexOut.Count(), pPhys->m_volume ); |
|
} |
|
joints.UnlinkCollisionModel( pPhys ); |
|
joints.AppendCollisionModel( pPhys ); |
|
} |
|
} |
|
// remove any non-physical joints at this point |
|
CPhysCollisionModel *pPhys = joints.m_pCollisionList; |
|
while (pPhys) |
|
{ |
|
CPhysCollisionModel *pNext = pPhys->m_pNext; |
|
if ( !pPhys->m_pCollisionData ) |
|
{ |
|
joints.UnlinkCollisionModel(pPhys); |
|
delete pPhys; |
|
} |
|
pPhys = pNext; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
#if 0 |
|
// debug visualization code - use this to dump out intermediate geometry files for visualization in glview.exe |
|
void DumpToGLView( char const *pName, s_source_t *pmodel, Vector *worldVerts, int *used ) |
|
{ |
|
int i; |
|
|
|
for ( i = 0; i < pmodel->numvertices; i++ ) |
|
used[i] = -1; |
|
|
|
FILE *fp = fopen( pName, "w" ); |
|
|
|
// dump the model to a glview file |
|
for ( i = 0; i < pmodel->nummeshes; i++ ) |
|
{ |
|
s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i]; |
|
for ( int j = 0; j < pmesh->numfaces; j++ ) |
|
{ |
|
s_face_t *face = pmodel->face + pmesh->faceoffset + j; |
|
s_face_t globalFace; |
|
GlobalFace( &globalFace, pmesh, face ); |
|
|
|
fprintf( fp, "3\n" ); |
|
fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", worldVerts[globalFace.b].x, worldVerts[globalFace.b].y, worldVerts[globalFace.b].z ); |
|
fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", worldVerts[globalFace.a].x, worldVerts[globalFace.a].y, worldVerts[globalFace.a].z ); |
|
fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", worldVerts[globalFace.c].x, worldVerts[globalFace.c].y, worldVerts[globalFace.c].z ); |
|
used[globalFace.a] = 0; |
|
used[globalFace.b] = 0; |
|
used[globalFace.c] = 0; |
|
} |
|
} |
|
|
|
// dump a triangle expanded around each vert to the file (to show degenerate tris' verts). |
|
for ( i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
if ( used[i] < 0 ) |
|
continue; |
|
|
|
fprintf( fp, "3\n" ); |
|
Vector vert; |
|
vert = worldVerts[i] + Vector(0,0,5); |
|
fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", vert.x, vert.y, vert.z ); |
|
vert = worldVerts[i] + Vector(5,0,-5); |
|
fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", vert.x, vert.y, vert.z ); |
|
vert = worldVerts[i] + Vector(-5,0,-5); |
|
fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", vert.x, vert.y, vert.z ); |
|
|
|
} |
|
|
|
fclose( fp ); |
|
} |
|
#endif |
|
|
|
|
|
int ProcessSingleBody( CJointedModel &joints ) |
|
{ |
|
s_source_t *pmodel = joints.m_pModel; |
|
// THIS CODE IS ONLY EXECUTED ON PROPS - i.e. NON-JOINTED MODELS |
|
CUtlVector<Vector> worldspaceVerts; |
|
worldspaceVerts.SetCount(pmodel->numvertices); |
|
ConvertToWorldSpace( joints, pmodel, worldspaceVerts ); |
|
CUtlVector<s_face_t> faceList; |
|
|
|
CUtlVector<convexlist_t> convexList; |
|
CUtlVector<int> vertList; |
|
CUtlVector<CPhysConvex *> convexOut; |
|
bool bValid = false; |
|
if ( joints.m_allowConcave ) |
|
{ |
|
for ( int i = 0; i < pmodel->nummeshes; i++ ) |
|
{ |
|
s_mesh_t *pmesh = pmodel->mesh + pmodel->meshindex[i]; |
|
for ( int j = 0; j < pmesh->numfaces; j++ ) |
|
{ |
|
s_face_t *face = pmodel->face + pmesh->faceoffset + j; |
|
s_face_t globalFace; |
|
GlobalFace( &globalFace, pmesh, face ); |
|
faceList.AddToTail( globalFace ); |
|
} |
|
} |
|
BuildConvexListForFaceList( pmodel, convexList, vertList, faceList ); |
|
bValid = BuildConvexesForLists( convexOut, convexList, vertList, worldspaceVerts, joints.m_remove2d ); |
|
} |
|
|
|
if ( convexOut.Count() > joints.m_maxConvex ) |
|
{ |
|
MdlWarning("COSTLY COLLISION MODEL!!!! (%d parts - %d allowed)\n", convexOut.Count(), joints.m_maxConvex ); |
|
bValid = false; |
|
} |
|
|
|
if ( !bValid && convexOut.Count() ) |
|
{ |
|
MdlWarning("Error with convex elements of %s, building single convex!!!!\n", pmodel->filename ); |
|
for ( int i = 0; i < convexOut.Count(); i++ ) |
|
{ |
|
physcollision->ConvexFree( convexOut[i] ); |
|
} |
|
convexOut.Purge(); |
|
} |
|
|
|
// either we don't want concave, or there was an error building it |
|
if ( !convexOut.Count() ) |
|
{ |
|
convexlist_t elem; |
|
elem.firstVertIndex = 0; |
|
elem.numVertIndex = pmodel->numvertices; |
|
convexList.AddToTail(elem); |
|
for ( int i = 0; i < pmodel->numvertices; i++ ) |
|
{ |
|
vertList.AddToTail(i); |
|
} |
|
BuildConvexesForLists( convexOut, convexList, vertList, worldspaceVerts, true ); |
|
} |
|
|
|
if ( convexOut.Count() ) |
|
{ |
|
if( !g_quiet ) |
|
{ |
|
printf("Model has %d convex sub-parts\n", convexOut.Count() ); |
|
} |
|
|
|
CPhysCollisionModel *pPhys = new CPhysCollisionModel; |
|
joints.SetCollisionModelDefaults( pPhys ); |
|
|
|
boundingvolume_t bv; |
|
ClearBounds( bv.mins, bv.maxs ); |
|
for ( int i = worldspaceVerts.Count()-1; --i >= 0; ) |
|
{ |
|
AddPointToBounds( worldspaceVerts[i], bv.mins, bv.maxs ); |
|
} |
|
CreateCollide( pPhys, convexOut.Base(), convexOut.Count(), bv ); |
|
|
|
// Init mass, write routine will distribute the total mass |
|
pPhys->m_mass = 1.0; |
|
char tmp[512]; |
|
Q_FileBase( pmodel->filename, tmp, sizeof( tmp ) ); |
|
|
|
// UNDONE: Memory leak |
|
char *out = new char[strlen(tmp)+1]; |
|
strcpy( out, tmp ); |
|
pPhys->m_name = out; |
|
pPhys->m_parent = NULL; |
|
|
|
joints.AppendCollisionModel( pPhys ); |
|
} |
|
return 1; |
|
} |
|
|
|
|
|
|
|
#define MAX_ARGS 16 |
|
#define ARG_SIZE 256 |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: HACKETY HACK - get the args into a buffer. |
|
// This checks for overflow, but it's not very robust - shouldn't be necessary though |
|
// Input : pArgs[][ARG_SIZE] - |
|
// maxCount - array size of pargs |
|
// Output : int - count actually used |
|
//----------------------------------------------------------------------------- |
|
int ReadArgs( char pArgs[][ARG_SIZE], int maxCount ) |
|
{ |
|
int argCount = 0; |
|
|
|
while ( argCount < maxCount && TokenAvailable() ) |
|
{ |
|
GetToken(false); |
|
strncpy( pArgs[argCount], token, ARG_SIZE ); |
|
argCount++; |
|
} |
|
|
|
return argCount; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Simple atof wrapper to keep from crashing on bad user input |
|
// Input : *pString - |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float Safe_atof( const char *pString ) |
|
{ |
|
if ( !pString ) |
|
return 0; |
|
|
|
return atof(pString); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Simple atoi wrapper to avoid crashing on bad user input |
|
// Input : *pString - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int Safe_atoi( const char *pString ) |
|
{ |
|
if ( !pString ) |
|
return 0; |
|
|
|
return atoi(pString); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add a constraint to our joint system |
|
// Input : &joints - |
|
// *pJointName - |
|
// *pJointAxis - |
|
// *pJointType - |
|
// *pLimitMin - |
|
// *pLimitMax - |
|
//----------------------------------------------------------------------------- |
|
void CCmd_JointConstrain( CJointedModel &joints, const char *pJointName, const char *pJointAxis, const char *pJointType, const char *pLimitMin, const char *pLimitMax, const char *pFriction ) |
|
{ |
|
float limitMin = Safe_atof(pLimitMin); |
|
float limitMax = Safe_atof(pLimitMax); |
|
float friction = Safe_atof(pFriction); |
|
|
|
int axis = -1; |
|
int jointIndex = FindLocalBoneNamed( joints.m_pModel, pJointName ); |
|
if ( !g_bCreateMakefile && jointIndex < 0 ) |
|
{ |
|
MdlWarning("Can't find joint %s\n", pJointName ); |
|
return; |
|
} |
|
pJointName = joints.m_pModel->localBone[jointIndex].name; |
|
|
|
if ( pJointAxis ) |
|
{ |
|
axis = tolower(pJointAxis[0]) - 'x'; |
|
} |
|
if ( axis < 0 || axis > 2 || limitMin > limitMax ) |
|
{ |
|
MdlError("Invalid joint constraint for %s\nCan't build ragdoll!\n", pJointName ); |
|
return; |
|
} |
|
|
|
jointlimit_t jointType = JOINT_FREE; |
|
if ( !stricmp( pJointType, "free" ) ) |
|
{ |
|
jointType = JOINT_FREE; |
|
} |
|
else if ( !stricmp( pJointType, "fixed" ) ) |
|
{ |
|
jointType = JOINT_FIXED; |
|
} |
|
else if ( !stricmp( pJointType, "limit" ) ) |
|
{ |
|
jointType = JOINT_LIMIT; |
|
} |
|
else |
|
{ |
|
MdlWarning("Unknown joint type %s (must be free, fixed, or limit)\n", pJointType ); |
|
return; |
|
} |
|
joints.AddConstraint( pJointName, axis, jointType, limitMin, limitMax, friction ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove a joint from the system (don't create physical geometry for it) |
|
// Input : &joints - |
|
// args[][ARG_SIZE] - |
|
// argCount - |
|
//----------------------------------------------------------------------------- |
|
// UNDONE: Automatically skip joints that will have mass that is too low? |
|
void CCmd_JointSkip( CJointedModel &joints, const char *pName ) |
|
{ |
|
int boneIndex = FindLocalBoneNamed( joints.m_pModel, pName ); |
|
if ( boneIndex < 0 ) |
|
{ |
|
MdlWarning("Can't skip joint %s, not found\n", pName ); |
|
} |
|
else |
|
{ |
|
// printf("skipping joint %s\n", pName ); |
|
joints.SkipBone( boneIndex ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets the object's mass. The code will distribute this mass to each |
|
// part based on the collision model's volume |
|
// Input : &joints - |
|
// *pMass - |
|
//----------------------------------------------------------------------------- |
|
void CCmd_TotalMass( CJointedModel &joints, const char *pMass ) |
|
{ |
|
joints.SetTotalMass( Safe_atof(pMass) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: verts from the bone named pChild are added to the collision model of pParent |
|
// Input : *pmodel - source model |
|
// *pParent - destination bone name |
|
// *pChild - source bone name |
|
//----------------------------------------------------------------------------- |
|
void CCmd_JointMerge( CJointedModel &joints, const char *pParent, const char *pChild ) |
|
{ |
|
joints.AddMergeCommand( pParent, pChild ); |
|
joints.MergeBones( FindLocalBoneNamed( joints.m_pModel, pParent ), FindLocalBoneNamed( joints.m_pModel, pChild ) ); |
|
} |
|
|
|
|
|
void CCmd_JointRoot( CJointedModel &joints, const char *pBone ) |
|
{ |
|
// save the root bone name |
|
strcpy( joints.m_rootName, pBone ); |
|
} |
|
|
|
|
|
void CCmd_JoinAnimatedFriction( CJointedModel &joints, const char *pMinFriction, const char *pMaxFriction, const char *pTimeIn, const char *pTimeHold, const char *pTimeOut ) |
|
{ |
|
joints.m_flFrictionTimeIn = Safe_atof( pTimeIn ); |
|
joints.m_flFrictionTimeOut = Safe_atof( pTimeOut ); |
|
joints.m_flFrictionTimeHold = Safe_atof( pTimeHold ); |
|
joints.m_iMinAnimatedFriction = Safe_atoi( pMinFriction ); |
|
joints.m_iMaxAnimatedFriction = Safe_atoi( pMaxFriction ); |
|
joints.m_bHasAnimatedFriction = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parses all legal commands inside the $collisionjoints {} block |
|
// Input : &joints - |
|
//----------------------------------------------------------------------------- |
|
void ParseCollisionCommands( CJointedModel &joints ) |
|
{ |
|
char command[512]; |
|
|
|
char args[MAX_ARGS][ARG_SIZE]; |
|
int argCount; |
|
|
|
while( GetToken( true ) ) |
|
{ |
|
if ( !strcmp( token, "}" ) ) |
|
return; |
|
|
|
strcpy( command, token ); |
|
|
|
if ( !stricmp( command, "$mass" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
CCmd_TotalMass( joints, args[0] ); |
|
} |
|
// default properties |
|
else if ( !stricmp( command, "$automass" ) ) |
|
{ |
|
joints.SetAutoMass(); |
|
} |
|
else if ( !stricmp( command, "$inertia" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
joints.DefaultInertia( Safe_atof( args[0] ) ); |
|
} |
|
else if ( !stricmp( command, "$damping" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
joints.DefaultDamping( Safe_atof( args[0] ) ); |
|
} |
|
else if ( !stricmp( command, "$rotdamping" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
joints.DefaultRotdamping( Safe_atof( args[0] ) ); |
|
} |
|
else if ( !stricmp( command, "$drag" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
joints.DefaultDrag( Safe_atof( args[0] ) ); |
|
} |
|
else if ( !stricmp( command, "$rollingDrag" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
// JAY: Removed this in favor of heuristic/tuning approach |
|
//joints.DefaultRollingDrag( Safe_atof( args[0] ) ); |
|
} |
|
else if ( !stricmp( command, "$maxconvexpieces") ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
joints.SetMaxConvex( Safe_atoi(args[0]) ); |
|
} |
|
else if ( !stricmp( command, "$remove2d") ) |
|
{ |
|
joints.Remove2DConvex(); |
|
} |
|
else if ( !stricmp( command, "$concaveperjoint") ) |
|
{ |
|
joints.AllowConcaveJoints(); |
|
} |
|
else if ( !stricmp( command, "$weldposition") ) |
|
{ |
|
argCount = ReadArgs(args,1); |
|
g_WeldVertEpsilon = Safe_atof( args[0] ); |
|
} |
|
else if ( !stricmp( command, "$weldnormal") ) |
|
{ |
|
argCount = ReadArgs(args,1); |
|
g_WeldNormalEpsilon = Safe_atof( args[0] ); |
|
} |
|
else if ( !stricmp( command, "$concave" ) ) |
|
{ |
|
joints.AllowConcave(); |
|
} |
|
else if ( !stricmp( command, "$masscenter" ) ) |
|
{ |
|
argCount = ReadArgs( args, 3 ); |
|
Vector center; |
|
center.Init( Safe_atof(args[0]), Safe_atof(args[1]), Safe_atof(args[2]) ); |
|
joints.ForceMassCenter( center ); |
|
} |
|
// joint commands |
|
else if ( !stricmp( command, "$jointskip" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
CCmd_JointSkip( joints, args[0] ); |
|
} |
|
else if ( !stricmp( command, "$jointmerge" ) ) |
|
{ |
|
argCount = ReadArgs( args, 2 ); |
|
CCmd_JointMerge( joints, args[0], args[1] ); |
|
} |
|
else if ( !stricmp( command, "$rootbone" ) ) |
|
{ |
|
argCount = ReadArgs( args, 1 ); |
|
CCmd_JointRoot( joints, args[0] ); |
|
} |
|
else if ( !stricmp( command, "$jointconstrain" ) ) |
|
{ |
|
argCount = ReadArgs( args, 6 ); |
|
char *pFriction = args[5]; |
|
if ( argCount < 6 ) |
|
{ |
|
pFriction = "1.0"; |
|
} |
|
CCmd_JointConstrain( joints, args[0], args[1], args[2], args[3], args[4], pFriction ); |
|
} |
|
// joint properties |
|
else if ( !stricmp( command, "$jointinertia" ) ) |
|
{ |
|
argCount = ReadArgs( args, 2 ); |
|
joints.JointInertia( args[0], Safe_atof( args[1] ) ); |
|
} |
|
else if ( !stricmp( command, "$jointdamping" ) ) |
|
{ |
|
argCount = ReadArgs( args, 2 ); |
|
joints.JointDamping( args[0], Safe_atof( args[1] ) ); |
|
} |
|
else if ( !stricmp( command, "$jointrotdamping" ) ) |
|
{ |
|
argCount = ReadArgs( args, 2 ); |
|
joints.JointRotdamping( args[0], Safe_atof( args[1] ) ); |
|
} |
|
else if ( !stricmp( command, "$jointmassbias" ) ) |
|
{ |
|
argCount = ReadArgs( args, 2 ); |
|
joints.JointMassBias( args[0], Safe_atof( args[1] ) ); |
|
} |
|
else if ( !stricmp( command, "$noselfcollisions" ) ) |
|
{ |
|
joints.SetNoSelfCollisions(); |
|
} |
|
else if ( !stricmp( command, "$jointcollide" ) ) |
|
{ |
|
argCount = ReadArgs( args, 2 ); |
|
joints.AppendCollisionPair( args[0], args[1] ); |
|
} |
|
else if ( !stricmp( command, "$animatedfriction" ) ) |
|
{ |
|
argCount = ReadArgs( args, 5 ); |
|
|
|
if ( argCount == 5 ) |
|
{ |
|
CCmd_JoinAnimatedFriction( joints, args[0], args[1], args[2], args[3], args[4] ); |
|
} |
|
} |
|
else if ( !stricmp( command, "$assumeworldspace") ) |
|
{ |
|
joints.m_bAssumeWorldspace = true; |
|
} |
|
else |
|
{ |
|
MdlWarning("Unknown command %s in collision series\n", command ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void Cmd_CollisionText( void ) |
|
{ |
|
int level = 1; |
|
|
|
if ( !GetToken( true ) ) |
|
return; |
|
|
|
if ( token[0] != '{' ) |
|
return; |
|
|
|
|
|
while ( GetToken(true) ) |
|
{ |
|
if ( !strcmp( token, "}" ) ) |
|
{ |
|
level--; |
|
if ( level <= 0 ) |
|
break; |
|
g_JointedModel.AddText( " }\n" ); |
|
} |
|
else if ( !strcmp( token, "{" ) ) |
|
{ |
|
g_JointedModel.AddText( "{" ); |
|
level++; |
|
} |
|
else |
|
{ |
|
// tokens inside braces are quoted |
|
if ( level > 1 ) |
|
{ |
|
g_JointedModel.AddText( "\"" ); |
|
g_JointedModel.AddText( token ); |
|
g_JointedModel.AddText( "\" " ); |
|
} |
|
else |
|
{ |
|
g_JointedModel.AddText( token ); |
|
g_JointedModel.AddText( " " ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static bool LoadSurfaceProps( const char *pMaterialFilename ) |
|
{ |
|
if ( !physprops ) |
|
return false; |
|
|
|
FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb", TOOLS_READ_PATH_ID ); |
|
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; |
|
|
|
physprops->ParseSurfaceData( pMaterialFilename, pText ); |
|
|
|
delete[] pText; |
|
|
|
return true; |
|
} |
|
|
|
void LoadSurfacePropsAll() |
|
{ |
|
// already loaded |
|
if ( physprops->SurfacePropCount() ) |
|
return; |
|
|
|
const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt"; |
|
KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE ); |
|
if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) ) |
|
{ |
|
for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) |
|
{ |
|
if ( !Q_stricmp( sub->GetName(), "file" ) ) |
|
{ |
|
// Add |
|
LoadSurfaceProps( sub->GetString() ); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
manifest->deleteThis(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Entry point for script processing. Delegate to necessary subroutines. |
|
// Parse the collisionmodel {} and collisionjoints {} chunks |
|
// Input : separateJoints - whether this has a constraint system or not (true if it does) |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int DoCollisionModel( bool separateJoints ) |
|
{ |
|
char name[512]; |
|
s_source_t *pmodel; |
|
|
|
// name |
|
if (!GetToken(false)) return 0; |
|
|
|
V_strcpy_safe( name, token ); |
|
|
|
PhysicsDLLPath( "VPHYSICS.DLL" ); |
|
|
|
// CreateInterfaceFn physicsFactory = GetPhysicsFactory(); |
|
CreateInterfaceFn physicsFactory = Sys_GetFactory(Sys_LoadModule( "vphysics.dll" )); |
|
if ( !physicsFactory ) |
|
return 0; |
|
|
|
physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); |
|
physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL ); |
|
LoadSurfacePropsAll(); |
|
|
|
int nummaterials = g_nummaterials; |
|
int numtextures = g_numtextures; |
|
|
|
pmodel = Load_Source( name, "SMD" ); |
|
if ( !pmodel ) |
|
return 0; |
|
|
|
// auto-remove any new materials/textures |
|
if (nummaterials && numtextures && (numtextures != g_numtextures || nummaterials != g_nummaterials)) |
|
{ |
|
g_numtextures = numtextures; |
|
g_nummaterials = nummaterials; |
|
|
|
pmodel->texmap[0] = 0; |
|
} |
|
|
|
// all bones map to themselves by default |
|
g_JointedModel.SetSource( pmodel ); |
|
|
|
bool parseCommands = false; |
|
|
|
// If the next token is a { that means a data block for the collision model |
|
if (GetToken(true)) |
|
{ |
|
if ( !strcmp( token, "{" ) ) |
|
{ |
|
parseCommands = true; |
|
} |
|
else |
|
{ |
|
UnGetToken(); |
|
} |
|
} |
|
|
|
if ( parseCommands ) |
|
{ |
|
ParseCollisionCommands( g_JointedModel ); |
|
} |
|
|
|
g_bJointed = separateJoints; |
|
|
|
// collision script is stored in g_JointedModel for later processing |
|
return 1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Walk the list of models, add up the volume |
|
// Input : *pList - |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float TotalVolume( CPhysCollisionModel *pList ) |
|
{ |
|
float volume = 0; |
|
while ( pList ) |
|
{ |
|
volume += pList->m_volume * pList->m_massBias; |
|
pList = pList->m_pNext; |
|
} |
|
|
|
return volume; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Write key/value pairs out to a file |
|
// Input : *fp - output file |
|
// *pKeyName - key name |
|
// outputData - type specific output data |
|
//----------------------------------------------------------------------------- |
|
void KeyWriteInt( FILE *fp, const char *pKeyName, int outputData ) |
|
{ |
|
fprintf( fp, "\"%s\" \"%d\"\n", pKeyName, outputData ); |
|
} |
|
|
|
void KeyWriteIntPair( FILE *fp, const char *pKeyName, int outputData0, int outputData1 ) |
|
{ |
|
fprintf( fp, "\"%s\" \"%d,%d\"\n", pKeyName, outputData0, outputData1 ); |
|
} |
|
void KeyWriteString( FILE *fp, const char *pKeyName, const char *outputData ) |
|
{ |
|
fprintf( fp, "\"%s\" \"%s\"\n", pKeyName, outputData ); |
|
} |
|
|
|
void KeyWriteVector3( FILE *fp, const char *pKeyName, const Vector& outputData ) |
|
{ |
|
fprintf( fp, "\"%s\" \"%f %f %f\"\n", pKeyName, outputData[0], outputData[1], outputData[2] ); |
|
} |
|
|
|
void KeyWriteQAngle( FILE *fp, const char *pKeyName, const QAngle& outputData ) |
|
{ |
|
fprintf( fp, "\"%s\" \"%f %f %f\"\n", pKeyName, outputData[0], outputData[1], outputData[2] ); |
|
} |
|
|
|
void KeyWriteFloat( FILE *fp, const char *pKeyName, float outputData ) |
|
{ |
|
fprintf( fp, "\"%s\" \"%f\"\n", pKeyName, outputData ); |
|
} |
|
|
|
|
|
void FixCollisionHierarchy( CJointedModel &joints ) |
|
{ |
|
if ( joints.m_pCollisionList ) |
|
{ |
|
CPhysCollisionModel *pPhys = joints.m_pCollisionList; |
|
|
|
FixBoneList( joints.m_bonemap, joints.m_pModel, joints.m_pCollisionList ); |
|
// Point parents at joints that are actually in the model |
|
for ( ;pPhys; pPhys = pPhys->m_pNext ) |
|
{ |
|
pPhys->m_parent = FixParent( joints.m_pCollisionList, joints.m_pModel, pPhys->m_parent ); |
|
} |
|
|
|
// sort the list so parents come before children |
|
joints.SortCollisionList(); |
|
// Now remap the constraints to bones to |
|
// Now that bones are in order, set physics indices in main bone structure |
|
|
|
CJointConstraint *pList = g_JointedModel.m_pConstraintList; |
|
while ( pList ) |
|
{ |
|
pList->m_pJointName = FixParent( joints.m_pCollisionList, joints.m_pModel, pList->m_pJointName ); |
|
pList = pList->m_pNext; |
|
} |
|
|
|
pPhys = joints.m_pCollisionList; |
|
int i; |
|
for ( i = 0; i < g_numbones; i++ ) |
|
{ |
|
g_bonetable[i].physicsBoneIndex = -1; |
|
} |
|
int index = 0; |
|
while ( pPhys ) |
|
{ |
|
int boneIndex = FindBoneInTable( pPhys->m_name ); |
|
if ( boneIndex >= 0 ) |
|
{ |
|
g_bonetable[boneIndex].physicsBoneIndex = index; |
|
} |
|
pPhys = pPhys->m_pNext; |
|
index ++; |
|
} |
|
for ( i = 0; i < g_numbones; i++ ) |
|
{ |
|
// if no bone was set, set to parent bone |
|
if ( g_bonetable[i].physicsBoneIndex < 0 ) |
|
{ |
|
int index = g_bonetable[i].parent; |
|
int bone = -1; |
|
while ( index >= 0 ) |
|
{ |
|
bone = g_bonetable[index].physicsBoneIndex; |
|
if ( bone >= 0 ) |
|
break; |
|
index = g_bonetable[index].parent; |
|
} |
|
|
|
// found one? |
|
if ( bone >= 0 ) |
|
{ |
|
g_bonetable[i].physicsBoneIndex = bone; |
|
} |
|
else |
|
{ |
|
// just set physics to affect root |
|
g_bonetable[i].physicsBoneIndex = 0; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Builds the physics/collision model. |
|
// This must execute after the model has been simplified!! |
|
//----------------------------------------------------------------------------- |
|
void CollisionModel_Build( void ) |
|
{ |
|
// no collision model referenced |
|
if ( !g_JointedModel.m_pModel ) |
|
return; |
|
|
|
g_JointedModel.Simplify(); |
|
if ( g_bJointed ) |
|
{ |
|
ProcessJointedModel( g_JointedModel ); |
|
} |
|
else |
|
{ |
|
ProcessSingleBody( g_JointedModel ); |
|
} |
|
FixCollisionHierarchy( g_JointedModel ); |
|
if( !g_quiet ) |
|
{ |
|
printf("Collision model completed.\n" ); |
|
} |
|
g_JointedModel.ComputeMass(); |
|
} |
|
|
|
void BuildRagdollConstraint( CPhysCollisionModel *pPhys, constraint_ragdollparams_t &ragdoll ) |
|
{ |
|
memset( &ragdoll, 0, sizeof(ragdoll) ); |
|
ragdoll.parentIndex = g_JointedModel.CollisionIndex(pPhys->m_parent); |
|
ragdoll.childIndex = g_JointedModel.CollisionIndex(pPhys->m_name); |
|
if ( ragdoll.parentIndex < 0 || ragdoll.childIndex < 0 ) |
|
{ |
|
MdlWarning("Constraint between bone %s and %s\n", pPhys->m_name, pPhys->m_parent ); |
|
if ( ragdoll.childIndex < 0 ) |
|
MdlWarning("\"%s\" does not appear in collision model!!!\n", pPhys->m_name ); |
|
if ( ragdoll.parentIndex < 0 ) |
|
MdlWarning("\"%s\" does not appear in collision model!!!\n", pPhys->m_parent ); |
|
MdlError("Bad constraint in ragdoll\n"); |
|
} |
|
CJointConstraint *pList = g_JointedModel.m_pConstraintList; |
|
while ( pList ) |
|
{ |
|
int index = g_JointedModel.CollisionIndex(pList->m_pJointName); |
|
CPhysCollisionModel *pListModel = g_JointedModel.GetCollisionModel(pList->m_pJointName); |
|
if ( index < 0 ) |
|
{ |
|
MdlError("Rotation constraint on bone \"%s\" which does not appear in collision model!!!\n", pList->m_pJointName ); |
|
} |
|
else if ( (!pListModel->m_parent || g_JointedModel.CollisionIndex(pListModel->m_parent) < 0) && stricmp( pList->m_pJointName, g_JointedModel.m_rootName ) ) |
|
{ |
|
MdlError("Rotation constraint on bone \"%s\" which has no parent!!!\n", pList->m_pJointName ); |
|
} |
|
else if ( index == ragdoll.childIndex ) |
|
{ |
|
switch ( pList->m_jointType ) |
|
{ |
|
case JOINT_LIMIT: |
|
ragdoll.axes[pList->m_axis].SetAxisFriction( pList->m_limitMin, pList->m_limitMax, pList->m_friction ); |
|
break; |
|
case JOINT_FIXED: |
|
ragdoll.axes[pList->m_axis].SetAxisFriction( 0,0,0 ); |
|
break; |
|
case JOINT_FREE: |
|
ragdoll.axes[pList->m_axis].SetAxisFriction( -360, 360, pList->m_friction ); |
|
break; |
|
} |
|
} |
|
pList = pList->m_pNext; |
|
} |
|
} |
|
|
|
float GetCollisionModelMass() |
|
{ |
|
return g_JointedModel.m_totalMass; |
|
} |
|
|
|
void CollisionModel_ExpandBBox( Vector &mins, Vector &maxs ) |
|
{ |
|
// don't do fixup for ragdolls |
|
if ( g_bJointed ) |
|
return; |
|
|
|
if ( g_JointedModel.m_pCollisionList ) |
|
{ |
|
Vector collideMins, collideMaxs; |
|
|
|
physcollision->CollideGetAABB( &collideMins, &collideMaxs, g_JointedModel.m_pCollisionList->m_pCollisionData, vec3_origin, vec3_angle ); |
|
|
|
// add the 0.25 inch collision separation as well |
|
const float radius = 0.25; |
|
collideMins -= Vector(radius,radius,radius); |
|
collideMaxs += Vector(radius,radius,radius); |
|
|
|
AddPointToBounds( collideMins, mins, maxs ); |
|
AddPointToBounds( collideMaxs, mins, maxs ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Write out any data that's been saved in the globals |
|
//----------------------------------------------------------------------------- |
|
void CollisionModel_Write( long checkSum ) |
|
{ |
|
if ( g_JointedModel.m_pCollisionList ) |
|
{ |
|
CPhysCollisionModel *pPhys = g_JointedModel.m_pCollisionList; |
|
|
|
char filename[MAX_PATH]; |
|
|
|
V_strcpy_safe( filename, gamedir ); |
|
// if( *g_pPlatformName ) |
|
// { |
|
// strcat( filename, "platform_" ); |
|
// strcat( filename, g_pPlatformName ); |
|
// strcat( filename, "/" ); |
|
// } |
|
V_strcat_safe( filename, "models/" ); |
|
V_strcat_safe( filename, outname ); |
|
|
|
float volume = TotalVolume( pPhys ); |
|
if ( volume <= 0 ) |
|
volume = 1; |
|
if( !g_quiet ) |
|
{ |
|
printf("Collision model volume %.2f in^3\n", volume ); |
|
} |
|
|
|
Q_SetExtension( filename, ".phy", sizeof( filename ) ); |
|
CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( filename ) ); |
|
spFile->Edit(); |
|
FILE *fp = fopen( filename, "wb" ); |
|
if ( fp ) |
|
{ |
|
// write out the collision header (size is version) |
|
phyheader_t header; |
|
header.size = sizeof(header); |
|
header.id = 0; |
|
header.checkSum = checkSum; |
|
|
|
header.solidCount = 0; |
|
pPhys = g_JointedModel.m_pCollisionList; |
|
while ( pPhys ) |
|
{ |
|
header.solidCount++; |
|
pPhys = pPhys->m_pNext; |
|
} |
|
|
|
fwrite( &header, sizeof(header), 1, fp ); |
|
|
|
// Write out the binary physics collision data |
|
pPhys = g_JointedModel.m_pCollisionList; |
|
while ( pPhys ) |
|
{ |
|
int size = physcollision->CollideSize( pPhys->m_pCollisionData ); |
|
fwrite( &size, sizeof(int), 1, fp ); |
|
char *buf = (char *)stackalloc( size ); |
|
physcollision->CollideWrite( buf, pPhys->m_pCollisionData ); |
|
fwrite( buf, size, 1, fp ); |
|
pPhys = pPhys->m_pNext; |
|
} |
|
|
|
// write out the properties of each solid |
|
int solidIndex = 0; |
|
pPhys = g_JointedModel.m_pCollisionList; |
|
while ( pPhys ) |
|
{ |
|
pPhys->m_mass = ((pPhys->m_volume * pPhys->m_massBias) / volume) * g_JointedModel.m_totalMass; |
|
if ( pPhys->m_mass < 1.0 ) |
|
pPhys->m_mass = 1.0; |
|
|
|
fprintf( fp, "solid {\n" ); |
|
KeyWriteInt( fp, "index", solidIndex ); |
|
KeyWriteString( fp, "name", pPhys->m_name ); |
|
if ( pPhys->m_parent ) |
|
{ |
|
KeyWriteString( fp, "parent", pPhys->m_parent ); |
|
} |
|
|
|
KeyWriteFloat( fp, "mass", pPhys->m_mass ); |
|
//KeyWriteFloat( fp, "volume", pPhys->m_volume ); |
|
|
|
char* pSurfaceProps = GetSurfaceProp( pPhys->m_name ); |
|
|
|
KeyWriteString( fp, "surfaceprop", pSurfaceProps ); |
|
KeyWriteFloat( fp, "damping", pPhys->m_damping ); |
|
KeyWriteFloat( fp, "rotdamping", pPhys->m_rotdamping ); |
|
|
|
if ( pPhys->m_dragCoefficient != -1 ) |
|
{ |
|
KeyWriteFloat( fp, "drag", pPhys->m_dragCoefficient ); |
|
} |
|
KeyWriteFloat( fp, "inertia", pPhys->m_inertia ); |
|
KeyWriteFloat( fp, "volume", pPhys->m_volume ); |
|
if ( pPhys->m_massBias != 1.0f ) |
|
{ |
|
KeyWriteFloat( fp, "massbias", pPhys->m_massBias ); |
|
} |
|
|
|
fprintf( fp, "}\n" ); |
|
pPhys = pPhys->m_pNext; |
|
solidIndex++; |
|
|
|
} |
|
|
|
// by default, write constraints from each limb to its parent |
|
pPhys = g_JointedModel.m_pCollisionList; |
|
while ( pPhys ) |
|
{ |
|
// check to see if bone collapse/remap has left this with parent pointing at itself |
|
if ( pPhys->m_parent ) |
|
{ |
|
constraint_ragdollparams_t ragdoll; |
|
BuildRagdollConstraint( pPhys, ragdoll ); |
|
if ( ragdoll.parentIndex != ragdoll.childIndex ) |
|
{ |
|
fprintf( fp, "ragdollconstraint {\n" ); |
|
KeyWriteInt( fp, "parent", ragdoll.parentIndex ); |
|
KeyWriteInt( fp, "child", ragdoll.childIndex ); |
|
KeyWriteFloat( fp, "xmin", ragdoll.axes[0].minRotation ); |
|
KeyWriteFloat( fp, "xmax", ragdoll.axes[0].maxRotation ); |
|
KeyWriteFloat( fp, "xfriction", ragdoll.axes[0].torque ); |
|
KeyWriteFloat( fp, "ymin", ragdoll.axes[1].minRotation ); |
|
KeyWriteFloat( fp, "ymax", ragdoll.axes[1].maxRotation ); |
|
KeyWriteFloat( fp, "yfriction", ragdoll.axes[1].torque ); |
|
KeyWriteFloat( fp, "zmin", ragdoll.axes[2].minRotation ); |
|
KeyWriteFloat( fp, "zmax", ragdoll.axes[2].maxRotation ); |
|
KeyWriteFloat( fp, "zfriction", ragdoll.axes[2].torque ); |
|
fprintf( fp, "}\n" ); |
|
} |
|
} |
|
pPhys = pPhys->m_pNext; |
|
|
|
} |
|
if ( g_JointedModel.m_noSelfCollisions ) |
|
{ |
|
fprintf(fp, "collisionrules {\n" ); |
|
KeyWriteInt( fp, "selfcollisions", 0 ); |
|
fprintf(fp, "}\n"); |
|
} |
|
else if ( g_JointedModel.m_pCollisionPairs ) |
|
{ |
|
fprintf(fp, "collisionrules {\n" ); |
|
collisionpair_t *pPair = g_JointedModel.m_pCollisionPairs; |
|
while ( pPair ) |
|
{ |
|
pPair->obj0 = g_JointedModel.CollisionIndex( pPair->pName0 ); |
|
pPair->obj1 = g_JointedModel.CollisionIndex( pPair->pName1 ); |
|
if ( pPair->obj0 >= 0 && pPair->obj1 >= 0 && pPair->obj0 != pPair->obj1 ) |
|
{ |
|
KeyWriteIntPair( fp, "collisionpair", pPair->obj0, pPair->obj1 ); |
|
} |
|
else |
|
{ |
|
MdlWarning("Invalid collision pair (%s, %s)\n", pPair->pName0, pPair->pName1 ); |
|
} |
|
pPair = pPair->pNext; |
|
} |
|
fprintf(fp, "}\n"); |
|
} |
|
|
|
if ( g_JointedModel.m_bHasAnimatedFriction == true ) |
|
{ |
|
fprintf( fp, "animatedfriction {\n" ); |
|
KeyWriteFloat( fp, "animfrictionmin", g_JointedModel.m_iMinAnimatedFriction ); |
|
KeyWriteFloat( fp, "animfrictionmax", g_JointedModel.m_iMaxAnimatedFriction ); |
|
KeyWriteFloat( fp, "animfrictiontimein", g_JointedModel.m_flFrictionTimeIn ); |
|
KeyWriteFloat( fp, "animfrictiontimeout", g_JointedModel.m_flFrictionTimeOut ); |
|
KeyWriteFloat( fp, "animfrictiontimehold", g_JointedModel.m_flFrictionTimeHold ); |
|
fprintf( fp, "}\n" ); |
|
} |
|
|
|
// block that is only parsed by the editor |
|
fprintf( fp, "editparams {\n" ); |
|
KeyWriteString( fp, "rootname", g_JointedModel.m_rootName ); |
|
KeyWriteFloat( fp, "totalmass", g_JointedModel.m_totalMass ); |
|
if ( g_JointedModel.m_allowConcave ) |
|
{ |
|
KeyWriteInt( fp, "concave", 1 ); |
|
} |
|
for ( int k = 0; k < g_JointedModel.m_mergeList.Count(); k++ ) |
|
{ |
|
char buf[512]; |
|
Q_snprintf( buf, sizeof(buf), "%s,%s", g_JointedModel.m_mergeList[k].pParent, g_JointedModel.m_mergeList[k].pChild ); |
|
KeyWriteString( fp, "jointmerge", buf ); |
|
} |
|
|
|
fprintf( fp, "}\n" ); |
|
|
|
char terminator = 0; |
|
if ( g_JointedModel.m_textCommands.Size() ) |
|
{ |
|
fwrite( g_JointedModel.m_textCommands.Base(), g_JointedModel.m_textCommands.Size(), 1, fp ); |
|
} |
|
fwrite( &terminator, sizeof(terminator), 1, fp ); |
|
fclose( fp ); |
|
spFile->Add(); |
|
} |
|
else |
|
{ |
|
MdlWarning("Error writing %s!!!\n", filename ); |
|
} |
|
} |
|
} |
|
|
|
|
|
|