//===== Copyright � 2005-2013, Valve Corporation, All rights reserved. ======// // // Purpose: Utility methods for mdl files // //===========================================================================// #include "tier3/mdlutils.h" #include "tier0/dbg.h" #include "tier3/tier3.h" #include "studio.h" #include "istudiorender.h" #include "bone_setup.h" #include "bone_accessor.h" #include "materialsystem/imaterialvar.h" #include "vcollide_parse.h" #include "renderparm.h" #include "tier2/renderutils.h" #include "mathlib/camera.h" // NOTE: This has to be the last file included! #include "tier0/memdbgon.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 ); } //----------------------------------------------------------------------------- // Determines which pose parameters are used by the specified sequence //----------------------------------------------------------------------------- void FindSequencePoseParameters( CStudioHdr &hdr, int nSequence, bool *pPoseParameters, int nCount ) { if ( ( nSequence < 0 ) && ( nSequence >= hdr.GetNumSeq() ) ) return; const mstudioseqdesc_t &seqdesc = hdr.pSeqdesc( nSequence ); // Add the pose parameters that are directly referenced by this sequence int nParamIndex; nParamIndex = hdr.GetSharedPoseParameter( nSequence, seqdesc.paramindex[ 0 ] ); if ( ( nParamIndex >= 0 ) && ( nParamIndex < nCount ) ) { pPoseParameters[ nParamIndex ] = true; } nParamIndex = hdr.GetSharedPoseParameter( nSequence, seqdesc.paramindex[ 1 ] ); if ( ( nParamIndex >= 0 ) && ( nParamIndex < nCount ) ) { pPoseParameters[ nParamIndex ] = true; } if ( seqdesc.flags & STUDIO_CYCLEPOSE ) { nParamIndex = hdr.GetSharedPoseParameter( nSequence, seqdesc.cycleposeindex ); if ( ( nParamIndex >= 0 ) && ( nParamIndex < nCount ) ) { pPoseParameters[ nParamIndex ] = true; } } // Now recursively add the parameters for the auto layers for ( int i = 0; i < seqdesc.numautolayers; ++i ) { const mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i ); int nLayerSequence = hdr.iRelativeSeq( nSequence, pLayer->iSequence ); if ( nLayerSequence != nSequence ) { FindSequencePoseParameters( hdr, nLayerSequence, pPoseParameters, nCount ); } } } //----------------------------------------------------------------------------- // 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; m_bUseSequencePlaybackFPS = false; m_flTimeBasisAdjustment = 0.0f; // Deal with the default cubemap ITexture *pCubemapTexture = g_pMaterialSystem->FindTexture( "editor/cubemap", NULL, true ); m_DefaultEnvCubemap.Init( pCubemapTexture ); pCubemapTexture = g_pMaterialSystem->FindTexture( "editor/cubemap.hdr", NULL, true ); m_DefaultHDREnvCubemap.Init( pCubemapTexture ); m_pSimpleMaterialOverride = NULL; } CMDL::~CMDL() { m_DefaultEnvCubemap.Shutdown( ); m_DefaultHDREnvCubemap.Shutdown(); if ( m_pSimpleMaterialOverride != NULL ) { m_pSimpleMaterialOverride.Shutdown(); m_pSimpleMaterialOverride = NULL; } UnreferenceMDL(); } ITexture *CMDL::GetEnvCubeMap() { if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE ) { return m_DefaultEnvCubemap; } else { return m_DefaultHDREnvCubemap; } } 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->GetStudioHdr( 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; } } if ( m_Attachments.Count() != pHdr->GetNumAttachments() ) { m_Attachments.SetSize( pHdr->GetNumAttachments() ); // This is to make sure we don't use the attachment before its been set up for ( int i=0; i < m_Attachments.Count(); i++ ) { m_Attachments[i].m_bValid = false; #ifdef _DEBUG m_Attachments[i].m_AttachmentToWorld.Invalidate(); #endif } } } } } MDLHandle_t CMDL::GetMDL() const { return m_MDLHandle; } //----------------------------------------------------------------------------- // Release the MDL handle //----------------------------------------------------------------------------- void CMDL::UnreferenceMDL() { if ( !g_pMDLCache ) return; if ( m_MDLHandle != MDLHANDLE_INVALID ) { 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 ); } void CMDL::SetSimpleMaterialOverride( IMaterial *pNewMaterial ) { m_pSimpleMaterialOverride.Init( pNewMaterial ); } //----------------------------------------------------------------------------- // Draws the mesh //----------------------------------------------------------------------------- void CMDL::Draw( const matrix3x4_t& rootToWorld, const matrix3x4_t *pBoneToWorld, int flags ) { 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 ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); CMatRenderData< float > rdFlexWeights( pRenderContext ); // Set default flex values float *pFlexWeights = NULL; const int nFlexDescCount = info.m_pStudioHdr->numflexdesc; if ( nFlexDescCount ) { CStudioHdr cStudioHdr( info.m_pStudioHdr, g_pMDLCache ); pFlexWeights = rdFlexWeights.Lock( info.m_pStudioHdr->numflexdesc ); cStudioHdr.RunFlexRules( m_pFlexControls, pFlexWeights ); } Vector vecModelOrigin; MatrixGetColumn( rootToWorld, 3, vecModelOrigin ); bool bOverride = false; static ConVarRef cl_custom_material_override( "cl_custom_material_override" ); if ( cl_custom_material_override.IsValid() && cl_custom_material_override.GetBool() && !g_pStudioRender->IsForcedMaterialOverride() ) { for ( int i = 0; i < GetCustomMaterialCount(); i++ ) { if ( IsCustomMaterialValid( i ) ) { g_pStudioRender->ForcedMaterialOverride( GetCustomMaterial( i )->GetMaterial(), OVERRIDE_SELECTIVE, i ); bOverride = true; } } } if ( m_pSimpleMaterialOverride != NULL ) { bOverride = true; g_pStudioRender->ForcedMaterialOverride( m_pSimpleMaterialOverride ); } g_pStudioRender->DrawModel( NULL, info, const_cast( pBoneToWorld ), pFlexWeights, NULL, vecModelOrigin, STUDIORENDER_DRAW_ENTIRE_MODEL | flags ); if ( bOverride ) { g_pStudioRender->ForcedMaterialOverride( NULL ); } } 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 ); CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); CMatRenderData< matrix3x4_t > rdBoneToWorld( pRenderContext, pStudioHdr->numbones ); SetUpBones( rootToWorld, pStudioHdr->numbones, rdBoneToWorld.Base() ); Draw( rootToWorld, rdBoneToWorld.Base() ); } void CMDL::SetUpBones( const matrix3x4_t& rootToWorld, int nMaxBoneCount, matrix3x4_t *pBoneToWorld, const float *pPoseParameters, MDLSquenceLayer_t *pSequenceLayers, int nNumSequenceLayers ) { MDLCACHE_CRITICAL_SECTION(); CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache ); // Default to middle of the pose parameter range float defaultPoseParameters[MAXSTUDIOPOSEPARAM]; if ( pPoseParameters == NULL ) { Studio_CalcDefaultPoseParameters( &studioHdr, defaultPoseParameters, MAXSTUDIOPOSEPARAM ); pPoseParameters = defaultPoseParameters; } int nFrameCount = Studio_MaxFrame( &studioHdr, m_nSequence, pPoseParameters ); if ( nFrameCount == 0 ) { nFrameCount = 1; } float flPlaybackRate = m_bUseSequencePlaybackFPS ? Studio_FPS( &studioHdr, m_nSequence, pPoseParameters ) : m_flPlaybackRate; float flAdjustedTime = m_flTime - m_flTimeBasisAdjustment; float flCycle = ( flAdjustedTime * flPlaybackRate ) / nFrameCount; m_flCurrentAnimEndTime = flPlaybackRate > 0.0f ? float( nFrameCount ) / flPlaybackRate : float( nFrameCount ); if ( flCycle > 1.0f ) { // We need to rollover into the next sequence followup if ( flPlaybackRate > 0.0f && flAdjustedTime < float(flPlaybackRate) ) m_flTimeBasisAdjustment += float( nFrameCount ) / float( flPlaybackRate ); else m_flTimeBasisAdjustment = m_flTime; if ( m_arrSequenceFollowLoop.Count() ) { m_nSequence = m_arrSequenceFollowLoop.Head(); m_arrSequenceFollowLoop.RemoveMultipleFromHead( 1 ); // Recurse with the updated sequence SetUpBones( rootToWorld, nMaxBoneCount, pBoneToWorld, pPoseParameters, pSequenceLayers, nNumSequenceLayers ); return; } else { flAdjustedTime = m_flTime - m_flTimeBasisAdjustment; flCycle = ( flAdjustedTime * flPlaybackRate ) / nFrameCount; } } // FIXME: We're always wrapping; may want to determing if we should clamp flCycle = SubtractIntegerPart(flCycle); BoneVector pos[MAXSTUDIOBONES]; BoneQuaternionAligned q[MAXSTUDIOBONES]; IBoneSetup boneSetup( &studioHdr, BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ), pPoseParameters, NULL ); boneSetup.InitPose( pos, q ); boneSetup.AccumulatePose( pos, q, m_nSequence, flCycle, 1.0f, flAdjustedTime, 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; int nFrameCount = MAX( 1, Studio_MaxFrame( &studioHdr, nSeqIndex, pPoseParameters ) ); float flLayerCycle = ( flAdjustedTime * flPlaybackRate ) / nFrameCount; // FIXME: We're always wrapping; may want to determing if we should clamp flLayerCycle = SubtractIntegerPart(flLayerCycle); boneSetup.AccumulatePose( pos, q, nSeqIndex, flLayerCycle, flWeight, flAdjustedTime, 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 ); // WARNING: converting from matrix3x4_t to matrix3x4a_t is going to asplode on a console. // Calculate helper bones! AssertAligned( pBoneToWorld ); CBoneAccessor tempCBoneAccessor( ( matrix3x4a_t * )pBoneToWorld ); if ( CalcProceduralBone( &studioHdr, i, tempCBoneAccessor ) ) { } else 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 ); SetupBones_AttachmentHelper( &studioHdr, pBoneToWorld ); } //----------------------------------------------------------------------------- // 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 float flPoseParameter[MAXSTUDIOPOSEPARAM]; Studio_CalcDefaultPoseParameters( pMergeHdr, flPoseParameter, MAXSTUDIOPOSEPARAM ); int nFrameCount = Studio_MaxFrame( pMergeHdr, m_nSequence, flPoseParameter ); if ( nFrameCount == 0 ) { nFrameCount = 1; } float flPlaybackRate = m_bUseSequencePlaybackFPS ? Studio_FPS( pMergeHdr, m_nSequence, flPoseParameter ) : m_flPlaybackRate; float flAdjustedTime = m_flTime - m_flTimeBasisAdjustment; float flCycle = ( flAdjustedTime * flPlaybackRate ) / nFrameCount; m_flCurrentAnimEndTime = flPlaybackRate > 0.0f ? float( nFrameCount ) / flPlaybackRate : float( nFrameCount ); if ( flCycle > 1.0f ) { // We need to rollover into the next sequence followup if ( flPlaybackRate > 0.0f && flAdjustedTime < float(flPlaybackRate) ) m_flTimeBasisAdjustment += float( nFrameCount ) / float( flPlaybackRate ); else m_flTimeBasisAdjustment = m_flTime; if ( m_arrSequenceFollowLoop.Count() ) { m_nSequence = m_arrSequenceFollowLoop.Head(); m_arrSequenceFollowLoop.RemoveMultipleFromHead( 1 ); // Recurse with the updated sequence SetupBonesWithBoneMerge( pMergeHdr, pMergeBoneToWorld, pFollow, pFollowBoneToWorld, matModelToWorld ); return; } else { flAdjustedTime = m_flTime - m_flTimeBasisAdjustment; flCycle = ( flAdjustedTime * flPlaybackRate ) / nFrameCount; } } // FIXME: We're always wrapping; may want to determing if we should clamp flCycle = SubtractIntegerPart(flCycle); BoneVector pos[MAXSTUDIOBONES]; BoneQuaternionAligned q[MAXSTUDIOBONES]; IBoneSetup boneSetup( pMergeHdr, BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ), flPoseParameter ); boneSetup.InitPose( pos, q ); boneSetup.AccumulatePose( pos, q, m_nSequence, flCycle, 1.0f, flAdjustedTime, NULL ); // Get the merge bone list. const 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] ); } } } } void CMDL::SetupBones_AttachmentHelper( CStudioHdr *hdr, matrix3x4_t *pBoneToWorld ) { if ( !hdr || !hdr->GetNumAttachments() ) return; // calculate attachment points matrix3x4_t world; for (int i = 0; i < hdr->GetNumAttachments(); i++) { const mstudioattachment_t &pattachment = hdr->pAttachment( i ); int iBone = hdr->GetAttachmentBone( i ); if ( (pattachment.flags & ATTACHMENT_FLAG_WORLD_ALIGN) == 0 ) { ConcatTransforms( pBoneToWorld[iBone], pattachment.local, world ); } else { Vector vecLocalBonePos, vecWorldBonePos; MatrixGetColumn( pattachment.local, 3, vecLocalBonePos ); VectorTransform( vecLocalBonePos, pBoneToWorld[iBone], vecWorldBonePos ); SetIdentityMatrix( world ); MatrixSetColumn( vecWorldBonePos, 3, world ); } PutAttachment( i + 1, world ); } } //----------------------------------------------------------------------------- // Purpose: Put a value into an attachment point by index // Input : number - which point // Output : float * - the attachment point //----------------------------------------------------------------------------- bool CMDL::PutAttachment( int number, const matrix3x4_t &attachmentToWorld ) { if ( number < 1 || number > m_Attachments.Count() ) return false; CMDLAttachmentData *pAtt = &m_Attachments[number-1]; pAtt->m_AttachmentToWorld = attachmentToWorld; pAtt->m_bValid = true; return true; } bool CMDL::GetAttachment( int number, matrix3x4_t& matrix ) { if ( number < 1 || number > m_Attachments.Count() ) return false; if ( !m_Attachments[number-1].m_bValid ) return false; matrix = m_Attachments[number-1].m_AttachmentToWorld; return true; } bool CMDL::GetAttachment( const char *pszAttachment, matrix3x4_t& matrixOut ) { if ( GetMDL() == MDLHANDLE_INVALID ) return false; CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( GetMDL() ), g_pMDLCache ); int iAttachmentNum = Studio_FindAttachment( &studioHdr, pszAttachment ); if ( iAttachmentNum == -1 ) return false; return GetAttachment( iAttachmentNum + 1, matrixOut ); } bool CMDL::GetBoundingSphere( Vector &vecCenter, float &flRadius ) { // Check to see if we have a valid model to look at. if ( m_MDLHandle == MDLHANDLE_INVALID ) return false; GetMDLBoundingSphere( &vecCenter, &flRadius, m_MDLHandle, m_nSequence ); return true; } void CMDL::AdjustTime( float flAmount ) { m_flTime += flAmount; } MDLData_t::MDLData_t() { SetIdentityMatrix( m_MDLToWorld ); m_bRequestBoneMergeTakeover = false; } //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CMergedMDL::CMergedMDL() { m_nNumSequenceLayers = 0; } CMergedMDL::~CMergedMDL() { m_aMergeMDLs.Purge(); } //----------------------------------------------------------------------------- // Stores the clip //----------------------------------------------------------------------------- void CMergedMDL::SetMDL( MDLHandle_t handle, CCustomMaterialOwner* pCustomMaterialOwner, void *pProxyData ) { m_RootMDL.m_MDL.SetMDL( handle ); m_RootMDL.m_MDL.m_pProxyData = pProxyData; Vector vecMins, vecMaxs; GetMDLBoundingBox( &vecMins, &vecMaxs, handle, m_RootMDL.m_MDL.m_nSequence ); m_RootMDL.m_MDL.m_bWorldSpaceViewTarget = false; m_RootMDL.m_MDL.m_vecViewTarget.Init( 100.0f, 0.0f, vecMaxs.z ); if ( pCustomMaterialOwner ) { pCustomMaterialOwner->DuplicateCustomMaterialsToOther( &m_RootMDL.m_MDL ); } // Set the pose parameters to the default for the mdl SetPoseParameters( NULL, 0 ); // Clear any sequence layers SetSequenceLayers( NULL, 0 ); } //----------------------------------------------------------------------------- // An MDL was selected //----------------------------------------------------------------------------- void CMergedMDL::SetMDL( const char *pMDLName, CCustomMaterialOwner* pCustomMaterialOwner, void *pProxyData ) { MDLHandle_t hMDL = pMDLName ? g_pMDLCache->FindMDL( pMDLName ) : MDLHANDLE_INVALID; if ( g_pMDLCache->IsErrorModel( hMDL ) ) { hMDL = MDLHANDLE_INVALID; } SetMDL( hMDL, pCustomMaterialOwner, pProxyData ); } //----------------------------------------------------------------------------- // Purpose: Returns a model bounding box. //----------------------------------------------------------------------------- bool CMergedMDL::GetBoundingBox( Vector &vecBoundsMin, Vector &vecBoundsMax ) { // Check to see if we have a valid model to look at. if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return false; GetMDLBoundingBox( &vecBoundsMin, &vecBoundsMax, m_RootMDL.m_MDL.GetMDL(), m_RootMDL.m_MDL.m_nSequence ); return true; } //----------------------------------------------------------------------------- // Purpose: Returns a more accurate bounding sphere //----------------------------------------------------------------------------- bool CMergedMDL::GetBoundingSphere( Vector &vecCenter, float &flRadius ) { // Check to see if we have a valid model to look at. if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return false; Vector vecEngineCenter; GetMDLBoundingSphere( &vecEngineCenter, &flRadius, m_RootMDL.m_MDL.GetMDL(), m_RootMDL.m_MDL.m_nSequence ); VectorTransform( vecEngineCenter, m_RootMDL.m_MDLToWorld, vecCenter ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMergedMDL::GetAttachment( const char *pszAttachment, matrix3x4_t& matrixOut ) { if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return false; CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache ); int iAttachmentNum = Studio_FindAttachment( &studioHdr, pszAttachment ); if ( iAttachmentNum == -1 ) return false; return GetAttachment( iAttachmentNum + 1, matrixOut ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMergedMDL::GetAttachment( int iAttachmentNum, matrix3x4_t& matrixOut ) { if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return false; return m_RootMDL.m_MDL.GetAttachment( iAttachmentNum, matrixOut ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMergedMDL::SetModelAnglesAndPosition( const QAngle &angRot, const Vector &vecPos ) { SetIdentityMatrix( m_RootMDL.m_MDLToWorld ); AngleMatrix( angRot, vecPos, m_RootMDL.m_MDLToWorld ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMergedMDL::SetupBonesForAttachmentQueries( void ) { if ( !g_pMDLCache ) return; if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return; CMatRenderContextPtr pRenderContext( materials ); CStudioHdr *pRootStudioHdr = new CStudioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache ); CMatRenderData< matrix3x4_t > rdBoneToWorld( pRenderContext, pRootStudioHdr->numbones() ); m_RootMDL.m_MDL.SetUpBones( m_RootMDL.m_MDLToWorld, pRootStudioHdr->numbones(), rdBoneToWorld.Base(), m_PoseParameters, m_SequenceLayers, m_nNumSequenceLayers ); delete pRootStudioHdr; } //----------------------------------------------------------------------------- // paint it! //----------------------------------------------------------------------------- void CMergedMDL::Draw() { if ( !g_pMDLCache ) return; if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return; CMatRenderContextPtr pRenderContext( materials ); // Draw the MDL CStudioHdr *pRootStudioHdr = new CStudioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache ); CMatRenderData< matrix3x4_t > rdBoneToWorld( pRenderContext, pRootStudioHdr->numbones() ); const matrix3x4_t *pRootMergeHdrModelToWorld = &m_RootMDL.m_MDLToWorld; const matrix3x4_t *pFollowBoneToWorld = rdBoneToWorld.Base(); m_RootMDL.m_MDL.SetUpBones( m_RootMDL.m_MDLToWorld, pRootStudioHdr->numbones(), rdBoneToWorld.Base(), m_PoseParameters, m_SequenceLayers, m_nNumSequenceLayers ); OnPostSetUpBonesPreDraw(); int nFlags = STUDIORENDER_DRAW_NO_SHADOWS; OnModelDrawPassStart( 0, pRootStudioHdr, nFlags ); m_RootMDL.m_MDL.Draw( m_RootMDL.m_MDLToWorld, rdBoneToWorld.Base(), nFlags ); OnModelDrawPassFinished( 0, pRootStudioHdr, nFlags ); // Draw the merge MDLs. matrix3x4_t *pStackCopyOfRootMergeHdrModelToWorld = NULL; matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES]; int nMergeCount = m_aMergeMDLs.Count(); for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge ) { matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0]; // Get the merge studio header. CStudioHdr *pMergeHdr = new CStudioHdr( g_pMDLCache->GetStudioHdr( m_aMergeMDLs[iMerge].m_MDL.GetMDL() ), g_pMDLCache ); m_aMergeMDLs[iMerge].m_MDL.SetupBonesWithBoneMerge( pMergeHdr, pMergeBoneToWorld, pRootStudioHdr, pFollowBoneToWorld, *pRootMergeHdrModelToWorld ); OnModelDrawPassStart( 0, pMergeHdr, nFlags ); m_aMergeMDLs[iMerge].m_MDL.Draw( m_aMergeMDLs[iMerge].m_MDLToWorld, pMergeBoneToWorld, nFlags ); OnModelDrawPassFinished( 0, pMergeHdr, nFlags ); if ( m_aMergeMDLs[iMerge].m_bRequestBoneMergeTakeover && ( iMerge + 1 < nMergeCount ) ) { // This model is requesting bonemerge takeover and we have more models to render after it delete pRootStudioHdr; pRootStudioHdr = pMergeHdr; pRootMergeHdrModelToWorld = &m_aMergeMDLs[iMerge].m_MDLToWorld; // Make a copy of bone to world transforms in a separate stack buffer and repoint root transforms // for future bonemerge into that buffer if ( !pStackCopyOfRootMergeHdrModelToWorld ) pStackCopyOfRootMergeHdrModelToWorld = ( matrix3x4_t * ) stackalloc( sizeof( matMergeBoneToWorld ) ); Q_memcpy( pStackCopyOfRootMergeHdrModelToWorld, matMergeBoneToWorld, sizeof( matMergeBoneToWorld ) ); pFollowBoneToWorld = pStackCopyOfRootMergeHdrModelToWorld; } else { delete pMergeHdr; } } rdBoneToWorld.Release(); delete pRootStudioHdr; } void CMergedMDL::Draw( const matrix3x4_t &rootToWorld ) { m_RootMDL.m_MDLToWorld = rootToWorld; Draw(); } //----------------------------------------------------------------------------- // Sets the current sequence //----------------------------------------------------------------------------- void CMergedMDL::SetSequence( int nSequence, bool bUseSequencePlaybackFPS ) { m_RootMDL.m_MDL.m_nSequence = nSequence; m_RootMDL.m_MDL.m_bUseSequencePlaybackFPS = bUseSequencePlaybackFPS; m_RootMDL.m_MDL.m_flTimeBasisAdjustment = m_RootMDL.m_MDL.m_flTime; } //----------------------------------------------------------------------------- // Add a follow loop sequence //----------------------------------------------------------------------------- void CMergedMDL::AddSequenceFollowLoop( int nSequence, bool bUseSequencePlaybackFPS ) { Assert( bUseSequencePlaybackFPS == m_RootMDL.m_MDL.m_bUseSequencePlaybackFPS ); m_RootMDL.m_MDL.m_arrSequenceFollowLoop.AddToTail( nSequence ); } //----------------------------------------------------------------------------- // Clear any follow loop sequences //----------------------------------------------------------------------------- void CMergedMDL::ClearSequenceFollowLoop() { m_RootMDL.m_MDL.m_arrSequenceFollowLoop.RemoveAll(); } //----------------------------------------------------------------------------- // Set the current pose parameters. If NULL the pose parameters will be reset // to the default values. //----------------------------------------------------------------------------- void CMergedMDL::SetPoseParameters( const float *pPoseParameters, int nCount ) { if ( pPoseParameters ) { int nParameters = MIN( MAXSTUDIOPOSEPARAM, nCount ); for ( int iParam = 0; iParam < nParameters; ++iParam ) { m_PoseParameters[ iParam ] = pPoseParameters[ iParam ]; } } else if ( m_RootMDL.m_MDL.GetMDL() != MDLHANDLE_INVALID ) { CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache ); Studio_CalcDefaultPoseParameters( &studioHdr, m_PoseParameters, MAXSTUDIOPOSEPARAM ); } } //----------------------------------------------------------------------------- // Set the overlay sequence layers //----------------------------------------------------------------------------- void CMergedMDL::SetSequenceLayers( const MDLSquenceLayer_t *pSequenceLayers, int nCount ) { if ( pSequenceLayers ) { m_nNumSequenceLayers = MIN( MAX_SEQUENCE_LAYERS, nCount ); for ( int iLayer = 0; iLayer < m_nNumSequenceLayers; ++iLayer ) { m_SequenceLayers[ iLayer ] = pSequenceLayers[ iLayer ]; } } else { m_nNumSequenceLayers = 0; V_memset( m_SequenceLayers, 0, sizeof( m_SequenceLayers ) ); } } void CMergedMDL::SetSkin( int nSkin ) { m_RootMDL.m_MDL.m_nSkin = nSkin; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMergedMDL::SetMergeMDL( MDLHandle_t handle, CCustomMaterialOwner* pCustomMaterialOwner, void *pProxyData, bool bRequestBonemergeTakeover ) { // Verify that we have a root model to merge to. if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID ) return; int iIndex = m_aMergeMDLs.AddToTail(); if ( !m_aMergeMDLs.IsValidIndex( iIndex ) ) return; m_aMergeMDLs[iIndex].m_MDL.SetMDL( handle ); m_aMergeMDLs[iIndex].m_MDL.m_pProxyData = pProxyData; m_aMergeMDLs[iIndex].m_bRequestBoneMergeTakeover = bRequestBonemergeTakeover; if ( pCustomMaterialOwner ) { pCustomMaterialOwner->DuplicateCustomMaterialsToOther( &m_aMergeMDLs[iIndex].m_MDL ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- MDLHandle_t CMergedMDL::SetMergeMDL( const char *pMDLName, CCustomMaterialOwner* pCustomMaterialOwner, void *pProxyData, bool bRequestBonemergeTakeover ) { if ( g_pMDLCache == NULL ) return MDLHANDLE_INVALID; MDLHandle_t hMDL = pMDLName ? g_pMDLCache->FindMDL( pMDLName ) : MDLHANDLE_INVALID; if ( g_pMDLCache->IsErrorModel( hMDL ) ) { hMDL = MDLHANDLE_INVALID; } SetMergeMDL( hMDL, pCustomMaterialOwner, pProxyData, bRequestBonemergeTakeover ); return hMDL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CMergedMDL::GetMergeMDLIndex( MDLHandle_t handle ) { int nMergeCount = m_aMergeMDLs.Count(); for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge ) { if ( m_aMergeMDLs[iMerge].m_MDL.GetMDL() == handle ) return iMerge; } return -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMDL *CMergedMDL::GetMergeMDL( MDLHandle_t handle ) { int nMergeCount = m_aMergeMDLs.Count(); for ( int iMerge = 0; iMerge < nMergeCount; ++iMerge ) { if ( m_aMergeMDLs[iMerge].m_MDL.GetMDL() == handle ) return (&m_aMergeMDLs[iMerge].m_MDL); } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMergedMDL::ClearMergeMDLs( void ) { m_aMergeMDLs.Purge(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMergedMDL::UpdateModelCustomMaterials( MDLHandle_t handle, CCustomMaterialOwner* pCustomMaterialOwner ) { CMDL* pMDL = (handle != MDLHANDLE_INVALID) ? GetMergeMDL( handle ) : NULL; if ( pMDL ) { if ( pCustomMaterialOwner ) { pCustomMaterialOwner->DuplicateCustomMaterialsToOther( pMDL ); } else { pMDL->ClearCustomMaterials(); } } }