//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "tier0/dbg.h" #include "tier0/platform.h" #include "mathlib/mathlib.h" #include "tier0/tslist.h" #include "tier1/utlmap.h" #include "tier1/convar.h" #include "bone_setup.h" #include "con_nprint.h" #include "cdll_int.h" #include "globalvars_base.h" #include "posedebugger.h" #include "iclientnetworkable.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern IVEngineClient *engine; extern CGlobalVarsBase *gpGlobals; static ConVar ui_posedebug_fade_in_time( "ui_posedebug_fade_in_time", "0.2", FCVAR_CHEAT | FCVAR_DONTRECORD, "Time during which a new pose activity layer is shown in green in +posedebug UI" ); static ConVar ui_posedebug_fade_out_time( "ui_posedebug_fade_out_time", "0.8", FCVAR_CHEAT | FCVAR_DONTRECORD, "Time to keep a no longer active pose activity layer in red until removing it from +posedebug UI" ); ////////////////////////////////////////////////////////////////////////// // // CPoseDebuggerStub : IPoseDebugger // empty interface implementation // ////////////////////////////////////////////////////////////////////////// class CPoseDebuggerStub : public IPoseDebugger { public: virtual void StartBlending( IClientNetworkable *pEntity, const CStudioHdr *pStudioHdr ) { } virtual void AccumulatePose( const CStudioHdr *pStudioHdr, CIKContext *pIKContext, Vector pos[], Quaternion q[], int sequence, float cycle, const float poseParameter[], int boneMask, float flWeight, float flTime ) { } }; static CPoseDebuggerStub s_PoseDebuggerStub; IPoseDebugger *g_pPoseDebugger = &s_PoseDebuggerStub; ////////////////////////////////////////////////////////////////////////// // // CPoseDebuggerImpl : IPoseDebugger // Purpose: Main implementation of the pose debugger // Declaration // ////////////////////////////////////////////////////////////////////////// class ModelPoseDebugInfo { public: ModelPoseDebugInfo() : m_iEntNum( 0 ), m_iCurrentText( 0 ) { } public: // Entity number int m_iEntNum; // Currently processed text int m_iCurrentText; // Info Text Flags enum InfoTextFlags { F_SEEN_THIS_FRAME = 1 << 0, F_SEEN_LAST_FRAME = 1 << 1, }; struct InfoText { InfoText() { memset( this, 0, sizeof( *this ) ); } // Flags uint32 m_uiFlags; // Time seen float m_flTimeToLive, m_flTimeAlive; // Activity/label int m_iActivity; char m_chActivity[100]; char m_chLabel[100]; // Text char m_chTextLines[4][256]; enum { MAX_TEXT_LINES = 4 }; }; CCopyableUtlVector< InfoText > m_arrTxt; public: // Add an info text void AddInfoText( InfoText *x, ModelPoseDebugInfo *pOld ); // Lookup an info text InfoText *LookupInfoText( InfoText *x ); // Print pending info text void PrintPendingInfoText( int &rnPosPrint ); }; void ModelPoseDebugInfo::AddInfoText( InfoText *x, ModelPoseDebugInfo *pOld ) { if ( x ) { // Try to set the proper flags on the info text x->m_uiFlags &= ~F_SEEN_LAST_FRAME; x->m_uiFlags |= F_SEEN_THIS_FRAME; } // If we have smth to compare against if ( pOld ) { // Search for the same activity/label in the other model pose debug info ModelPoseDebugInfo &o = *pOld; int k = o.m_iCurrentText; if ( x ) { for ( ; k < o.m_arrTxt.Count(); ++ k ) { InfoText &txt = o.m_arrTxt[k]; if ( ( txt.m_uiFlags & F_SEEN_THIS_FRAME ) && !stricmp( x->m_chActivity, txt.m_chActivity ) && !stricmp( x->m_chLabel, txt.m_chLabel ) && ( x->m_iActivity == txt.m_iActivity ) ) { x->m_flTimeAlive = txt.m_flTimeAlive; break; } } } else { k = o.m_arrTxt.Count(); } // Range of finished activities int iFinishedRange[2] = { o.m_iCurrentText, k }; // Check whether this is a new message if ( k == o.m_arrTxt.Count() ) { if ( !x ) { o.m_iCurrentText = k; } else { // Don't update the current when insertion happens and don't have finished commands iFinishedRange[1] = iFinishedRange[0]; } } else { o.m_iCurrentText = k + 1; if ( x ) { x->m_uiFlags |= F_SEEN_LAST_FRAME; x->m_flTimeAlive += gpGlobals->frametime; } } // Everything before finished for ( int iFinished = iFinishedRange[0]; iFinished < iFinishedRange[1]; ++ iFinished ) { InfoText &txtFinished = o.m_arrTxt[ iFinished ]; if ( txtFinished.m_uiFlags & F_SEEN_THIS_FRAME ) txtFinished.m_uiFlags |= F_SEEN_LAST_FRAME; txtFinished.m_uiFlags &= ~F_SEEN_THIS_FRAME; txtFinished.m_flTimeToLive -= gpGlobals->frametime; txtFinished.m_flTimeAlive += gpGlobals->frametime; if ( txtFinished.m_flTimeToLive >= 0.0f ) m_arrTxt.AddToTail( txtFinished ); } } if ( x ) { // Now add it to the array x->m_flTimeToLive = ui_posedebug_fade_out_time.GetFloat(); m_arrTxt.AddToTail( *x ); } } ModelPoseDebugInfo::InfoText * ModelPoseDebugInfo::LookupInfoText( InfoText *x ) { int k = m_iCurrentText; if ( x ) { for ( ; k < m_arrTxt.Count(); ++ k ) { InfoText &txt = m_arrTxt[k]; if ( ( txt.m_uiFlags & F_SEEN_THIS_FRAME ) && !stricmp( x->m_chActivity, txt.m_chActivity ) && !stricmp( x->m_chLabel, txt.m_chLabel ) && ( x->m_iActivity == txt.m_iActivity ) ) { return &txt; } } } return NULL; } void ModelPoseDebugInfo::PrintPendingInfoText( int &rnPosPrint ) { con_nprint_s nxPrn = { 0 }; nxPrn.time_to_live = -1; nxPrn.color[0] = 1.0f, nxPrn.color[1] = 1.0f, nxPrn.color[2] = 1.0f; nxPrn.fixed_width_font = true; float const flFadeInTime = ui_posedebug_fade_in_time.GetFloat(); float const flFadeOutTime = ui_posedebug_fade_out_time.GetFloat(); // Now print all the accumulated spew for ( int k = m_iCurrentText; k < m_arrTxt.Count(); ++ k ) { InfoText &prntxt = m_arrTxt[k]; switch( prntxt.m_uiFlags & ( F_SEEN_LAST_FRAME | F_SEEN_THIS_FRAME ) ) { case ( F_SEEN_LAST_FRAME | F_SEEN_THIS_FRAME ) : nxPrn.color[0] = 1.f; nxPrn.color[1] = 1.f; nxPrn.color[2] = 1.f; if ( prntxt.m_flTimeAlive > flFadeInTime ) break; else NULL; // Fall-through to keep showing in green case F_SEEN_THIS_FRAME : if ( flFadeInTime > 0.f ) { nxPrn.color[0] = 1.f * prntxt.m_flTimeAlive / flFadeInTime; nxPrn.color[1] = 1.f; nxPrn.color[2] = 1.f * prntxt.m_flTimeAlive / flFadeInTime; } else { nxPrn.color[0] = ( prntxt.m_flTimeAlive > 0.0f ) ? 1.f : 0.f; nxPrn.color[1] = 1.f; nxPrn.color[2] = ( prntxt.m_flTimeAlive > 0.0f ) ? 1.f : 0.f; } break; case F_SEEN_LAST_FRAME : case 0: if ( flFadeOutTime > 0.f ) nxPrn.color[0] = 1.f * prntxt.m_flTimeToLive / flFadeOutTime; else nxPrn.color[0] = ( prntxt.m_flTimeToLive > 0.0f ) ? 1.f : 0.f; nxPrn.color[1] = 0.f; nxPrn.color[2] = 0.f; break; } nxPrn.index = ( rnPosPrint += 1 ); engine->Con_NXPrintf( &nxPrn, "%s", prntxt.m_chTextLines[0] ); for ( int iLine = 1; iLine < ModelPoseDebugInfo::InfoText::MAX_TEXT_LINES; ++ iLine) { if ( !prntxt.m_chTextLines[iLine][0] ) break; nxPrn.index = ( rnPosPrint += 1 ); engine->Con_NXPrintf( &nxPrn, "%s", prntxt.m_chTextLines[iLine] ); } } m_iCurrentText = m_arrTxt.Count(); } class CPoseDebuggerImpl : public IPoseDebugger { public: CPoseDebuggerImpl(); ~CPoseDebuggerImpl(); public: void ShowAllModels( bool bShow ); void ShowModel( int iEntNum, bool bShow ); bool IsModelShown( int iEntNum ) const; public: virtual void StartBlending( IClientNetworkable *pEntity, const CStudioHdr *pStudioHdr ); virtual void AccumulatePose( const CStudioHdr *pStudioHdr, CIKContext *pIKContext, Vector pos[], Quaternion q[], int sequence, float cycle, const float poseParameter[], int boneMask, float flWeight, float flTime ); protected: typedef CUtlMap< CStudioHdr const *, ModelPoseDebugInfo > MapModel; MapModel m_mapModel, m_mapModelOld; int m_nPosPrint; CBitVec< MAX_EDICTS > m_uiMaskShowModels; CStudioHdr const *m_pLastModel; }; static CPoseDebuggerImpl s_PoseDebuggerImpl; ////////////////////////////////////////////////////////////////////////// // // CPoseDebuggerImpl // Implementation // ////////////////////////////////////////////////////////////////////////// CPoseDebuggerImpl::CPoseDebuggerImpl() : m_mapModel( DefLessFunc( CStudioHdr const * ) ), m_mapModelOld( DefLessFunc( CStudioHdr const * ) ), m_nPosPrint( 0 ), m_pLastModel( NULL ) { m_uiMaskShowModels.SetAll(); } CPoseDebuggerImpl::~CPoseDebuggerImpl() { // g_pPoseDebugger = &s_PoseDebuggerStub; } void CPoseDebuggerImpl::ShowAllModels( bool bShow ) { bShow ? m_uiMaskShowModels.SetAll() : m_uiMaskShowModels.ClearAll(); } void CPoseDebuggerImpl::ShowModel( int iEntNum, bool bShow ) { Assert( iEntNum >= 0 && iEntNum < MAX_EDICTS ); if ( iEntNum >= 0 && iEntNum < MAX_EDICTS ) m_uiMaskShowModels.Set( iEntNum, bShow ); } bool CPoseDebuggerImpl::IsModelShown( int iEntNum ) const { Assert( iEntNum >= 0 && iEntNum < MAX_EDICTS ); if ( iEntNum >= 0 && iEntNum < MAX_EDICTS ) return m_uiMaskShowModels.IsBitSet( iEntNum ); else return false; } void CPoseDebuggerImpl::StartBlending( IClientNetworkable *pEntity, const CStudioHdr *pStudioHdr ) { // virtualmodel_t const *pVMdl = pStudioHdr->GetVirtualModel(); // if ( !pVMdl ) // return; // If we are starting a new model then finalize the previous one if ( pStudioHdr != m_pLastModel && m_pLastModel ) { MapModel::IndexType_t idx = m_mapModel.Find( m_pLastModel ); if ( idx != m_mapModel.InvalidIndex() ) { ModelPoseDebugInfo &mpi = m_mapModel.Element( idx ); ModelPoseDebugInfo *pMpiOld = NULL; MapModel::IndexType_t idxMapModelOld = m_mapModelOld.Find( m_pLastModel ); if ( idxMapModelOld != m_mapModelOld.InvalidIndex() ) { pMpiOld = &m_mapModelOld.Element( idxMapModelOld ); } mpi.AddInfoText( NULL, pMpiOld ); mpi.PrintPendingInfoText( m_nPosPrint ); } } m_pLastModel = pStudioHdr; // Go ahead with the new model studiohdr_t const *pRMdl = pStudioHdr->GetRenderHdr(); if ( !pRMdl || !pRMdl->numincludemodels ) return; // Entity number int iEntNum = pEntity->entindex(); if ( !IsModelShown( iEntNum ) ) return; // Check if we saw the model if ( m_mapModel.Find( pStudioHdr ) != m_mapModel.InvalidIndex() ) { // Initialize the printing position m_nPosPrint = 9; // Swap the maps m_mapModelOld.RemoveAll(); m_mapModelOld.Swap( m_mapModel ); // Zero out the text on the old map for ( int k = m_mapModelOld.FirstInorder(); k != m_mapModelOld.InvalidIndex(); k = m_mapModelOld.NextInorder( k ) ) { ModelPoseDebugInfo &mpi = m_mapModelOld[k]; mpi.m_iCurrentText = 0; } } else { // Next model m_nPosPrint += 3; } ModelPoseDebugInfo mpi; mpi.m_iEntNum = iEntNum; m_mapModel.Insert( pStudioHdr, mpi ); con_nprint_s nxPrn = { 0 }; nxPrn.index = m_nPosPrint; nxPrn.time_to_live = -1; nxPrn.color[0] = 0.9f, nxPrn.color[1] = 1.0f, nxPrn.color[2] = 0.9f; nxPrn.fixed_width_font = false; engine->Con_NXPrintf( &nxPrn, "[ %2d ] Model: %s", iEntNum, pRMdl->pszName() ); m_nPosPrint += 3; } void CPoseDebuggerImpl::AccumulatePose( const CStudioHdr *pStudioHdr, CIKContext *pIKContext, Vector pos[], Quaternion q[], int sequence, float cycle, const float poseParameter[], int boneMask, float flWeight, float flTime ) { // virtualmodel_t const *pVMdl = pStudioHdr->GetVirtualModel(); // if ( !pVMdl ) // return; studiohdr_t const *pRMdl = pStudioHdr->GetRenderHdr(); if ( !pRMdl || !pRMdl->numincludemodels ) return; MapModel::IndexType_t idxMapModel = m_mapModel.Find( pStudioHdr ); if ( idxMapModel == m_mapModel.InvalidIndex() ) return; ModelPoseDebugInfo &mpi = m_mapModel.Element( idxMapModel ); if ( !IsModelShown( mpi.m_iEntNum ) ) return; ModelPoseDebugInfo *pMpiOld = NULL; MapModel::IndexType_t idxMapModelOld = m_mapModelOld.Find( pStudioHdr ); if ( idxMapModelOld != m_mapModelOld.InvalidIndex() ) { pMpiOld = &m_mapModelOld.Element( idxMapModelOld ); } // // Actual processing // mstudioseqdesc_t &seqdesc = ((CStudioHdr *)pStudioHdr)->pSeqdesc( sequence ); if ( sequence >= pStudioHdr->GetNumSeq() ) { sequence = 0; seqdesc = ((CStudioHdr *)pStudioHdr)->pSeqdesc( sequence ); } enum { widthActivity = 35, widthLayer = 35, widthIks = 60, widthPercent = 6, }; // Prepare the text char chBuffer[256]; ModelPoseDebugInfo::InfoText txt; int numLines = 0; txt.m_iActivity = seqdesc.activity; sprintf( txt.m_chActivity, "%s", seqdesc.pszActivityName() ); sprintf( txt.m_chLabel, "%s", seqdesc.pszLabel() ); if ( !txt.m_chActivity[0] ) { // Try to find the last seen activity and re-use it for ( int iLast = mpi.m_arrTxt.Count(); iLast --> 0; ) { ModelPoseDebugInfo::InfoText &lastSeenTxt = mpi.m_arrTxt[iLast]; if ( lastSeenTxt.m_uiFlags & ModelPoseDebugInfo::F_SEEN_THIS_FRAME && lastSeenTxt.m_chActivity[0] ) { sprintf( txt.m_chActivity, "%s", lastSeenTxt.m_chActivity ); break; } } } // The layer information ModelPoseDebugInfo::InfoText *pOldTxt = pMpiOld ? pMpiOld->LookupInfoText( &txt ) : NULL; sprintf( txt.m_chTextLines[numLines], "%-*s %-*s %*.2f %*.1f/%-*d %*.0f%% ", widthActivity, seqdesc.pszActivityName(), widthLayer, seqdesc.pszLabel(), 7, pOldTxt ? pOldTxt->m_flTimeAlive : 0.f, 5, cycle * ( ((CStudioHdr *)pStudioHdr)->pAnimdesc( seqdesc.anim( 0, 0 ) ).numframes - 1 ), 3, ((CStudioHdr *)pStudioHdr)->pAnimdesc( seqdesc.anim( 0, 0 ) ).numframes, widthPercent, flWeight * 100.0f ); ++ numLines; if ( seqdesc.numiklocks ) { sprintf( chBuffer, "iklocks : %-2d : ", seqdesc.numiklocks ); for ( int k = 0; k < seqdesc.numiklocks; ++ k ) { mstudioiklock_t *plock = seqdesc.pIKLock( k ); mstudioikchain_t *pchain = pStudioHdr->pIKChain( plock->chain ); sprintf( chBuffer + strlen( chBuffer ), "%s ", pchain->pszName() ); // plock->flPosWeight; // plock->flLocalQWeight; } sprintf( txt.m_chTextLines[numLines], "%-*s", widthIks, chBuffer ); ++ numLines; } if ( seqdesc.numikrules ) { sprintf( chBuffer, "ikrules : %-2d", seqdesc.numikrules ); sprintf( txt.m_chTextLines[numLines], "%-*s", widthIks, chBuffer ); ++ numLines; } // Now add the accumulated text into the container mpi.AddInfoText( &txt, pMpiOld ); mpi.PrintPendingInfoText( m_nPosPrint ); } ////////////////////////////////////////////////////////////////////////// // // Con-commands // ////////////////////////////////////////////////////////////////////////// static void IN_PoseDebuggerStart( const CCommand &args ) { if ( args.ArgC() <= 1 ) { // No args, enable all s_PoseDebuggerImpl.ShowAllModels( true ); } else { // If explicitly showing the pose debugger when it was disabled if ( g_pPoseDebugger != &s_PoseDebuggerImpl ) { s_PoseDebuggerImpl.ShowAllModels( false ); } // Show only specific models for ( int k = 1; k < args.ArgC(); ++ k ) { int iEntNum = atoi( args.Arg( k ) ); s_PoseDebuggerImpl.ShowModel( iEntNum, true ); } } g_pPoseDebugger = &s_PoseDebuggerImpl; } static void IN_PoseDebuggerEnd( const CCommand &args ) { if ( args.ArgC() <= 1 ) { // No args, disable all s_PoseDebuggerImpl.ShowAllModels( false ); // Set the stub pointer g_pPoseDebugger = &s_PoseDebuggerStub; } else { // Hide only specific models for ( int k = 1; k < args.ArgC(); ++ k ) { int iEntNum = atoi( args.Arg( k ) ); s_PoseDebuggerImpl.ShowModel( iEntNum, false ); } } } static ConCommand posedebuggerstart( "+posedebug", IN_PoseDebuggerStart, "Turn on pose debugger or add ents to pose debugger UI", FCVAR_CHEAT ); static ConCommand posedebuggerend ( "-posedebug", IN_PoseDebuggerEnd, "Turn off pose debugger or hide ents from pose debugger UI", FCVAR_CHEAT );