|
|
//====== Copyright c 1996-2007, 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 < MAX_EDICTS ); if ( iEntNum < MAX_EDICTS ) m_uiMaskShowModels.Set( iEntNum, bShow ); }
bool CPoseDebuggerImpl::IsModelShown( int iEntNum ) const { Assert( 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 ( !ThreadInMainThread() ) { ExecuteOnce( "Turn of threading when using pose debugger\n" ); 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;
if ( !ThreadInMainThread() ) { 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; Q_snprintf( txt.m_chActivity, ARRAYSIZE( txt.m_chActivity ), "%s", seqdesc.pszActivityName() ); Q_snprintf( txt.m_chLabel, ARRAYSIZE( 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] ) { Q_snprintf( txt.m_chActivity, ARRAYSIZE( txt.m_chActivity ), "%s", lastSeenTxt.m_chActivity ); break; } } }
// The layer information
ModelPoseDebugInfo::InfoText *pOldTxt = pMpiOld ? pMpiOld->LookupInfoText( &txt ) : NULL; Q_snprintf( txt.m_chTextLines[numLines], ARRAYSIZE( 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 ) { Q_snprintf( chBuffer, ARRAYSIZE( 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 );
Q_snprintf( chBuffer + strlen( chBuffer ), ARRAYSIZE( chBuffer ) - strlen( chBuffer ), "%s ", pchain->pszName() ); // plock->flPosWeight;
// plock->flLocalQWeight;
}
Q_snprintf( txt.m_chTextLines[numLines], ARRAYSIZE( txt.m_chTextLines[numLines] ), "%-*s", widthIks, chBuffer ); ++ numLines; }
if ( seqdesc.numikrules ) { Q_snprintf( chBuffer, ARRAYSIZE( chBuffer ), "ikrules : %-2d", seqdesc.numikrules );
Q_snprintf( txt.m_chTextLines[numLines], ARRAYSIZE( txt.m_chTextLines[numLines] ), "%-*s", widthIks, chBuffer ); ++ numLines; }
// Now add the accumulated text into the container
mpi.AddInfoText( &txt, pMpiOld ); mpi.PrintPendingInfoText( m_nPosPrint ); }
#ifdef _DEBUG
//////////////////////////////////////////////////////////////////////////
//
// 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 );
#endif
|