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.
459 lines
13 KiB
459 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Utility methods for mdl files |
|
// |
|
//===========================================================================// |
|
|
|
#include "tier3/mdlutils.h" |
|
#include "tier0/dbg.h" |
|
#include "tier1/callqueue.h" |
|
#include "tier3/tier3.h" |
|
#include "studio.h" |
|
#include "istudiorender.h" |
|
#include "bone_setup.h" |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the bounding box for the model |
|
//----------------------------------------------------------------------------- |
|
void GetMDLBoundingBox( Vector *pMins, Vector *pMaxs, MDLHandle_t h, int nSequence ) |
|
{ |
|
if ( h == MDLHANDLE_INVALID || !g_pMDLCache ) |
|
{ |
|
pMins->Init(); |
|
pMaxs->Init(); |
|
return; |
|
} |
|
|
|
pMins->Init( FLT_MAX, FLT_MAX ); |
|
pMaxs->Init( -FLT_MAX, -FLT_MAX ); |
|
|
|
studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( h ); |
|
if ( !VectorCompare( vec3_origin, pStudioHdr->view_bbmin ) || !VectorCompare( vec3_origin, pStudioHdr->view_bbmax )) |
|
{ |
|
// look for view clip |
|
*pMins = pStudioHdr->view_bbmin; |
|
*pMaxs = pStudioHdr->view_bbmax; |
|
} |
|
else if ( !VectorCompare( vec3_origin, pStudioHdr->hull_min ) || !VectorCompare( vec3_origin, pStudioHdr->hull_max )) |
|
{ |
|
// look for hull |
|
*pMins = pStudioHdr->hull_min; |
|
*pMaxs = pStudioHdr->hull_max; |
|
} |
|
|
|
// Else use the sequence box |
|
mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSequence ); |
|
VectorMin( seqdesc.bbmin, *pMins, *pMins ); |
|
VectorMax( seqdesc.bbmax, *pMaxs, *pMaxs ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the radius of the model as measured from the origin |
|
//----------------------------------------------------------------------------- |
|
float GetMDLRadius( MDLHandle_t h, int nSequence ) |
|
{ |
|
Vector vecMins, vecMaxs; |
|
GetMDLBoundingBox( &vecMins, &vecMaxs, h, nSequence ); |
|
float flRadius = vecMaxs.Length(); |
|
float flRadius2 = vecMins.Length(); |
|
if ( flRadius2 > flRadius ) |
|
{ |
|
flRadius = flRadius2; |
|
} |
|
return flRadius; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a more accurate bounding sphere |
|
//----------------------------------------------------------------------------- |
|
void GetMDLBoundingSphere( Vector *pVecCenter, float *pRadius, MDLHandle_t h, int nSequence ) |
|
{ |
|
Vector vecMins, vecMaxs; |
|
GetMDLBoundingBox( &vecMins, &vecMaxs, h, nSequence ); |
|
VectorAdd( vecMins, vecMaxs, *pVecCenter ); |
|
*pVecCenter *= 0.5f; |
|
*pRadius = vecMaxs.DistTo( *pVecCenter ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CMDL::CMDL() |
|
{ |
|
m_MDLHandle = MDLHANDLE_INVALID; |
|
m_Color.SetColor( 255, 255, 255, 255 ); |
|
m_nSkin = 0; |
|
m_nBody = 0; |
|
m_nSequence = 0; |
|
m_nLOD = 0; |
|
m_flPlaybackRate = 30.0f; |
|
m_flTime = 0.0f; |
|
m_vecViewTarget.Init( 0, 0, 0 ); |
|
m_bWorldSpaceViewTarget = false; |
|
memset( m_pFlexControls, 0, sizeof(m_pFlexControls) ); |
|
m_pProxyData = NULL; |
|
} |
|
|
|
CMDL::~CMDL() |
|
{ |
|
UnreferenceMDL(); |
|
} |
|
|
|
void CMDL::SetMDL( MDLHandle_t h ) |
|
{ |
|
UnreferenceMDL(); |
|
m_MDLHandle = h; |
|
if ( m_MDLHandle != MDLHANDLE_INVALID ) |
|
{ |
|
g_pMDLCache->AddRef( m_MDLHandle ); |
|
|
|
studiohdr_t *pHdr = g_pMDLCache->LockStudioHdr( m_MDLHandle ); |
|
|
|
if ( pHdr ) |
|
{ |
|
for ( LocalFlexController_t i = LocalFlexController_t(0); i < pHdr->numflexcontrollers; ++i ) |
|
{ |
|
if ( pHdr->pFlexcontroller( i )->localToGlobal == -1 ) |
|
{ |
|
pHdr->pFlexcontroller( i )->localToGlobal = i; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
MDLHandle_t CMDL::GetMDL() const |
|
{ |
|
return m_MDLHandle; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Release the MDL handle |
|
//----------------------------------------------------------------------------- |
|
void CMDL::UnreferenceMDL() |
|
{ |
|
if ( !g_pMDLCache ) |
|
return; |
|
|
|
if ( m_MDLHandle != MDLHANDLE_INVALID ) |
|
{ |
|
// XXX need to figure out where it is safe to flush the queue during map change to not crash |
|
#if 0 |
|
if ( ICallQueue *pCallQueue = materials->GetRenderContext()->GetCallQueue() ) |
|
{ |
|
// Parallel rendering: don't unlock model data until end of rendering |
|
pCallQueue->QueueCall( g_pMDLCache, &IMDLCache::UnlockStudioHdr, m_MDLHandle ); |
|
pCallQueue->QueueCall( g_pMDLCache, &IMDLCache::Release, m_MDLHandle ); |
|
} |
|
else |
|
#endif |
|
{ |
|
// Immediate-mode rendering, can unlock immediately |
|
g_pMDLCache->UnlockStudioHdr( m_MDLHandle ); |
|
g_pMDLCache->Release( m_MDLHandle ); |
|
} |
|
m_MDLHandle = MDLHANDLE_INVALID; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets the studiohdr |
|
//----------------------------------------------------------------------------- |
|
studiohdr_t *CMDL::GetStudioHdr() |
|
{ |
|
if ( !g_pMDLCache ) |
|
return NULL; |
|
return g_pMDLCache->GetStudioHdr( m_MDLHandle ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Draws the mesh |
|
//----------------------------------------------------------------------------- |
|
void CMDL::Draw( const matrix3x4_t& rootToWorld, const matrix3x4_t *pBoneToWorld ) |
|
{ |
|
if ( !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender ) |
|
return; |
|
|
|
if ( m_MDLHandle == MDLHANDLE_INVALID ) |
|
return; |
|
|
|
// Color + alpha modulation |
|
Vector white( m_Color.r() / 255.0f, m_Color.g() / 255.0f, m_Color.b() / 255.0f ); |
|
g_pStudioRender->SetColorModulation( white.Base() ); |
|
g_pStudioRender->SetAlphaModulation( m_Color.a() / 255.0f ); |
|
|
|
DrawModelInfo_t info; |
|
info.m_pStudioHdr = g_pMDLCache->GetStudioHdr( m_MDLHandle ); |
|
info.m_pHardwareData = g_pMDLCache->GetHardwareData( m_MDLHandle ); |
|
info.m_Decals = STUDIORENDER_DECAL_INVALID; |
|
info.m_Skin = m_nSkin; |
|
info.m_Body = m_nBody; |
|
info.m_HitboxSet = 0; |
|
info.m_pClientEntity = m_pProxyData; |
|
info.m_pColorMeshes = NULL; |
|
info.m_bStaticLighting = false; |
|
info.m_Lod = m_nLOD; |
|
|
|
Vector vecWorldViewTarget; |
|
if ( m_bWorldSpaceViewTarget ) |
|
{ |
|
vecWorldViewTarget = m_vecViewTarget; |
|
} |
|
else |
|
{ |
|
VectorTransform( m_vecViewTarget, rootToWorld, vecWorldViewTarget ); |
|
} |
|
g_pStudioRender->SetEyeViewTarget( info.m_pStudioHdr, info.m_Body, vecWorldViewTarget ); |
|
|
|
// FIXME: Why is this necessary!?!?!? |
|
CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); |
|
|
|
// Set default flex values |
|
float *pFlexWeights = NULL; |
|
const int nFlexDescCount = info.m_pStudioHdr->numflexdesc; |
|
if ( nFlexDescCount ) |
|
{ |
|
CStudioHdr cStudioHdr( info.m_pStudioHdr, g_pMDLCache ); |
|
|
|
g_pStudioRender->LockFlexWeights( info.m_pStudioHdr->numflexdesc, &pFlexWeights ); |
|
cStudioHdr.RunFlexRules( m_pFlexControls, pFlexWeights ); |
|
g_pStudioRender->UnlockFlexWeights(); |
|
} |
|
|
|
Vector vecModelOrigin; |
|
MatrixGetColumn( rootToWorld, 3, vecModelOrigin ); |
|
g_pStudioRender->DrawModel( NULL, info, const_cast<matrix3x4_t*>( pBoneToWorld ), |
|
pFlexWeights, NULL, vecModelOrigin, STUDIORENDER_DRAW_ENTIRE_MODEL ); |
|
} |
|
|
|
void CMDL::Draw( const matrix3x4_t &rootToWorld ) |
|
{ |
|
if ( !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender ) |
|
return; |
|
|
|
if ( m_MDLHandle == MDLHANDLE_INVALID ) |
|
return; |
|
|
|
studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( m_MDLHandle ); |
|
|
|
matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( pStudioHdr->numbones ); |
|
SetUpBones( rootToWorld, pStudioHdr->numbones, pBoneToWorld ); |
|
g_pStudioRender->UnlockBoneMatrices(); |
|
|
|
Draw( rootToWorld, pBoneToWorld ); |
|
} |
|
|
|
|
|
void CMDL::SetUpBones( const matrix3x4_t& rootToWorld, int nMaxBoneCount, matrix3x4_t *pBoneToWorld, const float *pPoseParameters, MDLSquenceLayer_t *pSequenceLayers, int nNumSequenceLayers ) |
|
{ |
|
CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache ); |
|
|
|
float pPoseParameter[MAXSTUDIOPOSEPARAM]; |
|
if ( pPoseParameters ) |
|
{ |
|
V_memcpy( pPoseParameter, pPoseParameters, sizeof(pPoseParameter) ); |
|
} |
|
else |
|
{ |
|
// Default to middle of the pose parameter range |
|
int nPoseCount = studioHdr.GetNumPoseParameters(); |
|
for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i ) |
|
{ |
|
pPoseParameter[i] = 0.5f; |
|
if ( i < nPoseCount ) |
|
{ |
|
const mstudioposeparamdesc_t &Pose = studioHdr.pPoseParameter( i ); |
|
|
|
// Want to try for a zero state. If one doesn't exist set it to .5 by default. |
|
if ( Pose.start < 0.0f && Pose.end > 0.0f ) |
|
{ |
|
float flPoseDelta = Pose.end - Pose.start; |
|
pPoseParameter[i] = -Pose.start / flPoseDelta; |
|
} |
|
} |
|
} |
|
} |
|
|
|
int nFrameCount = Studio_MaxFrame( &studioHdr, m_nSequence, pPoseParameter ); |
|
if ( nFrameCount == 0 ) |
|
{ |
|
nFrameCount = 1; |
|
} |
|
float flCycle = ( m_flTime * m_flPlaybackRate ) / nFrameCount; |
|
|
|
// FIXME: We're always wrapping; may want to determing if we should clamp |
|
flCycle -= (int)(flCycle); |
|
|
|
Vector pos[MAXSTUDIOBONES]; |
|
Quaternion q[MAXSTUDIOBONES]; |
|
|
|
IBoneSetup boneSetup( &studioHdr, BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ), pPoseParameter, NULL ); |
|
boneSetup.InitPose( pos, q ); |
|
boneSetup.AccumulatePose( pos, q, m_nSequence, flCycle, 1.0f, m_flTime, NULL ); |
|
|
|
// Accumulate the additional layers if specified. |
|
if ( pSequenceLayers ) |
|
{ |
|
int nNumSeq = studioHdr.GetNumSeq(); |
|
for ( int i = 0; i < nNumSequenceLayers; ++i ) |
|
{ |
|
int nSeqIndex = pSequenceLayers[ i ].m_nSequenceIndex; |
|
if ( ( nSeqIndex >= 0 ) && ( nSeqIndex < nNumSeq ) ) |
|
{ |
|
float flWeight = pSequenceLayers[ i ].m_flWeight; |
|
|
|
float flLayerCycle; |
|
int nLayerFrameCount = MAX( 1, Studio_MaxFrame( &studioHdr, nSeqIndex, pPoseParameter ) ); |
|
|
|
if ( pSequenceLayers[i].m_bNoLoop ) |
|
{ |
|
if ( pSequenceLayers[i].m_flCycleBeganAt == 0 ) |
|
{ |
|
pSequenceLayers[i].m_flCycleBeganAt = m_flTime; |
|
} |
|
|
|
float flElapsedTime = m_flTime - pSequenceLayers[i].m_flCycleBeganAt; |
|
flLayerCycle = ( flElapsedTime * m_flPlaybackRate ) / nLayerFrameCount; |
|
|
|
// Should we keep playing layers that have ended? |
|
//if ( flLayerCycle >= 1.0 ) |
|
//continue; |
|
} |
|
else |
|
{ |
|
flLayerCycle = ( m_flTime * m_flPlaybackRate ) / nLayerFrameCount; |
|
|
|
// FIXME: We're always wrapping; may want to determing if we should clamp |
|
flLayerCycle -= (int)(flLayerCycle); |
|
} |
|
|
|
boneSetup.AccumulatePose( pos, q, nSeqIndex, flLayerCycle, flWeight, m_flTime, NULL ); |
|
} |
|
} |
|
} |
|
|
|
// FIXME: Try enabling this? |
|
// CalcAutoplaySequences( pStudioHdr, NULL, pos, q, pPoseParameter, BONE_USED_BY_VERTEX_AT_LOD( m_nLOD ), flTime ); |
|
|
|
matrix3x4_t temp; |
|
|
|
if ( nMaxBoneCount > studioHdr.numbones() ) |
|
{ |
|
nMaxBoneCount = studioHdr.numbones(); |
|
} |
|
|
|
for ( int i = 0; i < nMaxBoneCount; i++ ) |
|
{ |
|
// If it's not being used, fill with NAN for errors |
|
#ifdef _DEBUG |
|
if ( !(studioHdr.pBone( i )->flags & BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ) ) ) |
|
{ |
|
int j, k; |
|
for (j = 0; j < 3; j++) |
|
{ |
|
for (k = 0; k < 4; k++) |
|
{ |
|
pBoneToWorld[i][j][k] = VEC_T_NAN; |
|
} |
|
} |
|
continue; |
|
} |
|
#endif |
|
|
|
matrix3x4_t boneMatrix; |
|
QuaternionMatrix( q[i], boneMatrix ); |
|
MatrixSetColumn( pos[i], 3, boneMatrix ); |
|
|
|
if ( studioHdr.pBone(i)->parent == -1 ) |
|
{ |
|
ConcatTransforms( rootToWorld, boneMatrix, pBoneToWorld[i] ); |
|
} |
|
else |
|
{ |
|
ConcatTransforms( pBoneToWorld[ studioHdr.pBone(i)->parent ], boneMatrix, pBoneToWorld[i] ); |
|
} |
|
} |
|
Studio_RunBoneFlexDrivers( m_pFlexControls, &studioHdr, pos, pBoneToWorld, rootToWorld ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CMDL::SetupBonesWithBoneMerge( const CStudioHdr *pMergeHdr, matrix3x4_t *pMergeBoneToWorld, |
|
const CStudioHdr *pFollow, const matrix3x4_t *pFollowBoneToWorld, |
|
const matrix3x4_t &matModelToWorld ) |
|
{ |
|
// Default to middle of the pose parameter range |
|
int nPoseCount = pMergeHdr->GetNumPoseParameters(); |
|
float pPoseParameter[MAXSTUDIOPOSEPARAM]; |
|
for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i ) |
|
{ |
|
pPoseParameter[i] = 0.5f; |
|
if ( i < nPoseCount ) |
|
{ |
|
const mstudioposeparamdesc_t &Pose = ((CStudioHdr *)pMergeHdr)->pPoseParameter( i ); |
|
|
|
// Want to try for a zero state. If one doesn't exist set it to .5 by default. |
|
if ( Pose.start < 0.0f && Pose.end > 0.0f ) |
|
{ |
|
float flPoseDelta = Pose.end - Pose.start; |
|
pPoseParameter[i] = -Pose.start / flPoseDelta; |
|
} |
|
} |
|
} |
|
|
|
int nFrameCount = Studio_MaxFrame( pMergeHdr, m_nSequence, pPoseParameter ); |
|
if ( nFrameCount == 0 ) |
|
{ |
|
nFrameCount = 1; |
|
} |
|
float flCycle = ( m_flTime * m_flPlaybackRate ) / nFrameCount; |
|
|
|
// FIXME: We're always wrapping; may want to determing if we should clamp |
|
flCycle -= (int)(flCycle); |
|
|
|
Vector pos[MAXSTUDIOBONES]; |
|
Quaternion q[MAXSTUDIOBONES]; |
|
|
|
IBoneSetup boneSetup( pMergeHdr, BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ), pPoseParameter ); |
|
boneSetup.InitPose( pos, q ); |
|
boneSetup.AccumulatePose( pos, q, m_nSequence, flCycle, 1.0f, m_flTime, NULL ); |
|
|
|
// Get the merge bone list. |
|
mstudiobone_t *pMergeBones = pMergeHdr->pBone( 0 ); |
|
for ( int iMergeBone = 0; iMergeBone < pMergeHdr->numbones(); ++iMergeBone ) |
|
{ |
|
// Now find the bone in the parent entity. |
|
bool bMerged = false; |
|
int iParentBoneIndex = Studio_BoneIndexByName( pFollow, pMergeBones[iMergeBone].pszName() ); |
|
if ( iParentBoneIndex >= 0 ) |
|
{ |
|
MatrixCopy( pFollowBoneToWorld[iParentBoneIndex], pMergeBoneToWorld[iMergeBone] ); |
|
bMerged = true; |
|
} |
|
|
|
if ( !bMerged ) |
|
{ |
|
// If we get down here, then the bone wasn't merged. |
|
matrix3x4_t matBone; |
|
QuaternionMatrix( q[iMergeBone], pos[iMergeBone], matBone ); |
|
|
|
if ( pMergeBones[iMergeBone].parent == -1 ) |
|
{ |
|
ConcatTransforms( matModelToWorld, matBone, pMergeBoneToWorld[iMergeBone] ); |
|
} |
|
else |
|
{ |
|
ConcatTransforms( pMergeBoneToWorld[pMergeBones[iMergeBone].parent], matBone, pMergeBoneToWorld[iMergeBone] ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|