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.
594 lines
14 KiB
594 lines
14 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
|
|
#include <limits.h> |
|
#include "studio.h" |
|
#include "tier1/utlmap.h" |
|
#include "tier1/utldict.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "filesystem.h" |
|
#include "tier0/icommandline.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern IFileSystem * g_pFileSystem; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
// a string table to speed up searching for sequences in the current virtual model |
|
struct modellookup_t |
|
{ |
|
CUtlDict<short,short> seqTable; |
|
CUtlDict<short,short> animTable; |
|
}; |
|
|
|
static CUtlVector<modellookup_t> g_ModelLookup; |
|
static int g_ModelLookupIndex = -1; |
|
|
|
inline bool HasLookupTable() |
|
{ |
|
return g_ModelLookupIndex >= 0 ? true : false; |
|
} |
|
|
|
inline CUtlDict<short,short> *GetSeqTable() |
|
{ |
|
return &g_ModelLookup[g_ModelLookupIndex].seqTable; |
|
} |
|
|
|
inline CUtlDict<short,short> *GetAnimTable() |
|
{ |
|
return &g_ModelLookup[g_ModelLookupIndex].animTable; |
|
} |
|
|
|
class CModelLookupContext |
|
{ |
|
public: |
|
CModelLookupContext(int group, const studiohdr_t *pStudioHdr); |
|
~CModelLookupContext(); |
|
|
|
private: |
|
int m_lookupIndex; |
|
}; |
|
|
|
CModelLookupContext::CModelLookupContext(int group, const studiohdr_t *pStudioHdr) |
|
{ |
|
m_lookupIndex = -1; |
|
if ( group == 0 && pStudioHdr->numincludemodels ) |
|
{ |
|
m_lookupIndex = g_ModelLookup.AddToTail(); |
|
g_ModelLookupIndex = g_ModelLookup.Count()-1; |
|
} |
|
} |
|
|
|
CModelLookupContext::~CModelLookupContext() |
|
{ |
|
if ( m_lookupIndex >= 0 ) |
|
{ |
|
Assert(m_lookupIndex == (g_ModelLookup.Count()-1)); |
|
g_ModelLookup.FastRemove(m_lookupIndex); |
|
g_ModelLookupIndex = g_ModelLookup.Count()-1; |
|
} |
|
} |
|
|
|
void virtualmodel_t::AppendModels( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
|
|
// build a search table if necesary |
|
CModelLookupContext ctx(group, pStudioHdr); |
|
|
|
AppendSequences( group, pStudioHdr ); |
|
AppendAnimations( group, pStudioHdr ); |
|
AppendBonemap( group, pStudioHdr ); |
|
AppendAttachments( group, pStudioHdr ); |
|
AppendPoseParameters( group, pStudioHdr ); |
|
AppendNodes( group, pStudioHdr ); |
|
AppendIKLocks( group, pStudioHdr ); |
|
|
|
struct HandleAndHeader_t |
|
{ |
|
void *handle; |
|
const studiohdr_t *pHdr; |
|
}; |
|
HandleAndHeader_t list[64]; |
|
|
|
// determine quantity of valid include models in one pass only |
|
// temporarily cache results off, otherwise FindModel() causes ref counting problems |
|
int j; |
|
int nValidIncludes = 0; |
|
for (j = 0; j < pStudioHdr->numincludemodels; j++) |
|
{ |
|
// find model (increases ref count) |
|
void *tmp = NULL; |
|
const studiohdr_t *pTmpHdr = pStudioHdr->FindModel( &tmp, pStudioHdr->pModelGroup( j )->pszName() ); |
|
if ( pTmpHdr ) |
|
{ |
|
if ( nValidIncludes >= ARRAYSIZE( list ) ) |
|
{ |
|
// would cause stack overflow |
|
Assert( 0 ); |
|
break; |
|
} |
|
|
|
list[nValidIncludes].handle = tmp; |
|
list[nValidIncludes].pHdr = pTmpHdr; |
|
nValidIncludes++; |
|
} |
|
} |
|
|
|
if ( nValidIncludes ) |
|
{ |
|
m_group.EnsureCapacity( m_group.Count() + nValidIncludes ); |
|
for (j = 0; j < nValidIncludes; j++) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
int group = m_group.AddToTail(); |
|
m_group[group].cache = list[j].handle; |
|
AppendModels( group, list[j].pHdr ); |
|
} |
|
} |
|
|
|
UpdateAutoplaySequences( pStudioHdr ); |
|
} |
|
|
|
void virtualmodel_t::AppendSequences( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int numCheck = m_seq.Count(); |
|
|
|
int j, k; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
CUtlVector< virtualsequence_t > seq; |
|
|
|
seq = m_seq; |
|
|
|
m_group[ group ].masterSeq.SetCount( pStudioHdr->numlocalseq ); |
|
|
|
for (j = 0; j < pStudioHdr->numlocalseq; j++) |
|
{ |
|
const mstudioseqdesc_t *seqdesc = pStudioHdr->pLocalSeqdesc( j ); |
|
char *s1 = seqdesc->pszLabel(); |
|
|
|
if ( HasLookupTable() ) |
|
{ |
|
k = numCheck; |
|
short index = GetSeqTable()->Find( s1 ); |
|
if ( index != GetSeqTable()->InvalidIndex() ) |
|
{ |
|
k = GetSeqTable()->Element(index); |
|
} |
|
} |
|
else |
|
{ |
|
for (k = 0; k < numCheck; k++) |
|
{ |
|
const studiohdr_t *hdr = m_group[ seq[k].group ].GetStudioHdr(); |
|
char *s2 = hdr->pLocalSeqdesc( seq[k].index )->pszLabel(); |
|
if ( !stricmp( s1, s2 ) ) |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
// no duplication |
|
if (k == numCheck) |
|
{ |
|
virtualsequence_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
tmp.flags = seqdesc->flags; |
|
tmp.activity = seqdesc->activity; |
|
k = seq.AddToTail( tmp ); |
|
} |
|
else if (m_group[ seq[k].group ].GetStudioHdr()->pLocalSeqdesc( seq[k].index )->flags & STUDIO_OVERRIDE) |
|
{ |
|
// the one in memory is a forward declared sequence, override it |
|
virtualsequence_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
tmp.flags = seqdesc->flags; |
|
tmp.activity = seqdesc->activity; |
|
seq[k] = tmp; |
|
} |
|
m_group[ group ].masterSeq[ j ] = k; |
|
} |
|
|
|
if ( HasLookupTable() ) |
|
{ |
|
for ( j = numCheck; j < seq.Count(); j++ ) |
|
{ |
|
const studiohdr_t *hdr = m_group[ seq[j].group ].GetStudioHdr(); |
|
const char *s1 = hdr->pLocalSeqdesc( seq[j].index )->pszLabel(); |
|
GetSeqTable()->Insert( s1, j ); |
|
} |
|
} |
|
|
|
m_seq = seq; |
|
} |
|
|
|
|
|
void virtualmodel_t::UpdateAutoplaySequences( const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int autoplayCount = pStudioHdr->CountAutoplaySequences(); |
|
m_autoplaySequences.SetCount( autoplayCount ); |
|
pStudioHdr->CopyAutoplaySequences( m_autoplaySequences.Base(), autoplayCount ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
void virtualmodel_t::AppendAnimations( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int numCheck = m_anim.Count(); |
|
|
|
CUtlVector< virtualgeneric_t > anim; |
|
anim = m_anim; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
int j, k; |
|
|
|
m_group[ group ].masterAnim.SetCount( pStudioHdr->numlocalanim ); |
|
|
|
for (j = 0; j < pStudioHdr->numlocalanim; j++) |
|
{ |
|
char *s1 = pStudioHdr->pLocalAnimdesc( j )->pszName(); |
|
if ( HasLookupTable() ) |
|
{ |
|
k = numCheck; |
|
short index = GetAnimTable()->Find( s1 ); |
|
if ( index != GetAnimTable()->InvalidIndex() ) |
|
{ |
|
k = GetAnimTable()->Element(index); |
|
} |
|
} |
|
else |
|
{ |
|
for (k = 0; k < numCheck; k++) |
|
{ |
|
char *s2 = m_group[ anim[k].group ].GetStudioHdr()->pLocalAnimdesc( anim[k].index )->pszName(); |
|
if (stricmp( s1, s2 ) == 0) |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
// no duplication |
|
if (k == numCheck) |
|
{ |
|
virtualgeneric_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
k = anim.AddToTail( tmp ); |
|
} |
|
|
|
m_group[ group ].masterAnim[ j ] = k; |
|
} |
|
|
|
if ( HasLookupTable() ) |
|
{ |
|
for ( j = numCheck; j < anim.Count(); j++ ) |
|
{ |
|
const char *s1 = m_group[ anim[j].group ].GetStudioHdr()->pLocalAnimdesc( anim[j].index )->pszName(); |
|
GetAnimTable()->Insert( s1, j ); |
|
} |
|
} |
|
|
|
m_anim = anim; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
void virtualmodel_t::AppendBonemap( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
MEM_ALLOC_CREDIT(); |
|
|
|
const studiohdr_t *pBaseStudioHdr = m_group[ 0 ].GetStudioHdr( ); |
|
|
|
m_group[ group ].boneMap.SetCount( pBaseStudioHdr->numbones ); |
|
m_group[ group ].masterBone.SetCount( pStudioHdr->numbones ); |
|
|
|
int j, k; |
|
|
|
if (group == 0) |
|
{ |
|
for (j = 0; j < pStudioHdr->numbones; j++) |
|
{ |
|
m_group[ group ].boneMap[ j ] = j; |
|
m_group[ group ].masterBone[ j ] = j; |
|
} |
|
} |
|
else |
|
{ |
|
for (j = 0; j < pBaseStudioHdr->numbones; j++) |
|
{ |
|
m_group[ group ].boneMap[ j ] = -1; |
|
} |
|
for (j = 0; j < pStudioHdr->numbones; j++) |
|
{ |
|
// NOTE: studiohdr has a bone table - using the table is ~5% faster than this for alyx.mdl on a P4/3.2GHz |
|
for (k = 0; k < pBaseStudioHdr->numbones; k++) |
|
{ |
|
if (stricmp( pStudioHdr->pBone( j )->pszName(), pBaseStudioHdr->pBone( k )->pszName() ) == 0) |
|
{ |
|
break; |
|
} |
|
} |
|
if (k < pBaseStudioHdr->numbones) |
|
{ |
|
m_group[ group ].masterBone[ j ] = k; |
|
m_group[ group ].boneMap[ k ] = j; |
|
|
|
// FIXME: these runtime messages don't display in hlmv |
|
if ((pStudioHdr->pBone( j )->parent == -1) || (pBaseStudioHdr->pBone( k )->parent == -1)) |
|
{ |
|
if ((pStudioHdr->pBone( j )->parent != -1) || (pBaseStudioHdr->pBone( k )->parent != -1)) |
|
{ |
|
Warning( "%s/%s : missmatched parent bones on \"%s\"\n", pBaseStudioHdr->pszName(), pStudioHdr->pszName(), pStudioHdr->pBone( j )->pszName() ); |
|
} |
|
} |
|
else if (m_group[ group ].masterBone[ pStudioHdr->pBone( j )->parent ] != m_group[ 0 ].masterBone[ pBaseStudioHdr->pBone( k )->parent ]) |
|
{ |
|
Warning( "%s/%s : missmatched parent bones on \"%s\"\n", pBaseStudioHdr->pszName(), pStudioHdr->pszName(), pStudioHdr->pBone( j )->pszName() ); |
|
} |
|
} |
|
else |
|
{ |
|
m_group[ group ].masterBone[ j ] = -1; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
void virtualmodel_t::AppendAttachments( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int numCheck = m_attachment.Count(); |
|
|
|
CUtlVector< virtualgeneric_t > attachment; |
|
attachment = m_attachment; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
int j, k, n; |
|
|
|
m_group[ group ].masterAttachment.SetCount( pStudioHdr->numlocalattachments ); |
|
|
|
for (j = 0; j < pStudioHdr->numlocalattachments; j++) |
|
{ |
|
|
|
n = m_group[ group ].masterBone[ pStudioHdr->pLocalAttachment( j )->localbone ]; |
|
|
|
// skip if the attachments bone doesn't exist in the root model |
|
if (n == -1) |
|
{ |
|
continue; |
|
} |
|
|
|
|
|
char *s1 = pStudioHdr->pLocalAttachment( j )->pszName(); |
|
for (k = 0; k < numCheck; k++) |
|
{ |
|
char *s2 = m_group[ attachment[k].group ].GetStudioHdr()->pLocalAttachment( attachment[k].index )->pszName(); |
|
|
|
if (stricmp( s1, s2 ) == 0) |
|
{ |
|
break; |
|
} |
|
} |
|
// no duplication |
|
if (k == numCheck) |
|
{ |
|
virtualgeneric_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
k = attachment.AddToTail( tmp ); |
|
|
|
// make sure bone flags are set so attachment calculates |
|
if ((m_group[ 0 ].GetStudioHdr()->pBone( n )->flags & BONE_USED_BY_ATTACHMENT) == 0) |
|
{ |
|
while (n != -1) |
|
{ |
|
m_group[ 0 ].GetStudioHdr()->pBone( n )->flags |= BONE_USED_BY_ATTACHMENT; |
|
|
|
if (m_group[ 0 ].GetStudioHdr()->pLinearBones()) |
|
{ |
|
*m_group[ 0 ].GetStudioHdr()->pLinearBones()->pflags(n) |= BONE_USED_BY_ATTACHMENT; |
|
} |
|
|
|
n = m_group[ 0 ].GetStudioHdr()->pBone( n )->parent; |
|
} |
|
continue; |
|
} |
|
} |
|
|
|
m_group[ group ].masterAttachment[ j ] = k; |
|
} |
|
|
|
m_attachment = attachment; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
void virtualmodel_t::AppendPoseParameters( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int numCheck = m_pose.Count(); |
|
|
|
CUtlVector< virtualgeneric_t > pose; |
|
pose = m_pose; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
int j, k; |
|
|
|
m_group[ group ].masterPose.SetCount( pStudioHdr->numlocalposeparameters ); |
|
|
|
for (j = 0; j < pStudioHdr->numlocalposeparameters; j++) |
|
{ |
|
char *s1 = pStudioHdr->pLocalPoseParameter( j )->pszName(); |
|
for (k = 0; k < numCheck; k++) |
|
{ |
|
char *s2 = m_group[ pose[k].group ].GetStudioHdr()->pLocalPoseParameter( pose[k].index )->pszName(); |
|
|
|
if (stricmp( s1, s2 ) == 0) |
|
{ |
|
break; |
|
} |
|
} |
|
if (k == numCheck) |
|
{ |
|
// no duplication |
|
virtualgeneric_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
k = pose.AddToTail( tmp ); |
|
} |
|
else |
|
{ |
|
// duplicate, reset start and end to fit full dynamic range |
|
mstudioposeparamdesc_t *pPose1 = pStudioHdr->pLocalPoseParameter( j ); |
|
mstudioposeparamdesc_t *pPose2 = m_group[ pose[k].group ].GetStudioHdr()->pLocalPoseParameter( pose[k].index ); |
|
float start = min( pPose2->end, min( pPose1->end, min( pPose2->start, pPose1->start ) ) ); |
|
float end = max( pPose2->end, max( pPose1->end, max( pPose2->start, pPose1->start ) ) ); |
|
pPose2->start = start; |
|
pPose2->end = end; |
|
} |
|
|
|
m_group[ group ].masterPose[ j ] = k; |
|
} |
|
|
|
m_pose = pose; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
void virtualmodel_t::AppendNodes( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int numCheck = m_node.Count(); |
|
|
|
CUtlVector< virtualgeneric_t > node; |
|
node = m_node; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
int j, k; |
|
|
|
m_group[ group ].masterNode.SetCount( pStudioHdr->numlocalnodes ); |
|
|
|
for (j = 0; j < pStudioHdr->numlocalnodes; j++) |
|
{ |
|
char *s1 = pStudioHdr->pszLocalNodeName( j ); |
|
for (k = 0; k < numCheck; k++) |
|
{ |
|
char *s2 = m_group[ node[k].group ].GetStudioHdr()->pszLocalNodeName( node[k].index ); |
|
|
|
if (stricmp( s1, s2 ) == 0) |
|
{ |
|
break; |
|
} |
|
} |
|
// no duplication |
|
if (k == numCheck) |
|
{ |
|
virtualgeneric_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
k = node.AddToTail( tmp ); |
|
} |
|
|
|
m_group[ group ].masterNode[ j ] = k; |
|
} |
|
|
|
m_node = node; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
void virtualmodel_t::AppendIKLocks( int group, const studiohdr_t *pStudioHdr ) |
|
{ |
|
AUTO_LOCK( m_Lock ); |
|
int numCheck = m_iklock.Count(); |
|
|
|
CUtlVector< virtualgeneric_t > iklock; |
|
iklock = m_iklock; |
|
|
|
int j, k; |
|
|
|
for (j = 0; j < pStudioHdr->numlocalikautoplaylocks; j++) |
|
{ |
|
int chain1 = pStudioHdr->pLocalIKAutoplayLock( j )->chain; |
|
for (k = 0; k < numCheck; k++) |
|
{ |
|
int chain2 = m_group[ iklock[k].group ].GetStudioHdr()->pLocalIKAutoplayLock( iklock[k].index )->chain; |
|
|
|
if (chain1 == chain2) |
|
{ |
|
break; |
|
} |
|
} |
|
// no duplication |
|
if (k == numCheck) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
|
|
virtualgeneric_t tmp; |
|
tmp.group = group; |
|
tmp.index = j; |
|
k = iklock.AddToTail( tmp ); |
|
} |
|
} |
|
|
|
m_iklock = iklock; |
|
|
|
// copy knee directions for uninitialized knees |
|
if ( group != 0 ) |
|
{ |
|
studiohdr_t *pBaseHdr = (studiohdr_t *)m_group[ 0 ].GetStudioHdr(); |
|
if ( pStudioHdr->numikchains == pBaseHdr->numikchains ) |
|
{ |
|
for (j = 0; j < pStudioHdr->numikchains; j++) |
|
{ |
|
if ( pBaseHdr->pIKChain( j )->pLink(0)->kneeDir.LengthSqr() == 0.0f ) |
|
{ |
|
if ( pStudioHdr->pIKChain( j )->pLink(0)->kneeDir.LengthSqr() > 0.0f ) |
|
{ |
|
pBaseHdr->pIKChain( j )->pLink(0)->kneeDir = pStudioHdr->pIKChain( j )->pLink(0)->kneeDir; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|