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