//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #include "AnimationBrowser.h" #include "hlfaceposer.h" #include "ChoreoView.h" #include "StudioModel.h" #include "ViewerSettings.h" #include "choreowidgetdrawhelper.h" #include "faceposer_models.h" #include "tabwindow.h" #include "inputproperties.h" #include "keyvalues.h" #include "FileSystem.h" #include "tier1/KeyValues.h" #include "tier1/UtlBuffer.h" #define MAX_THUMBNAILSIZE 256 #define MIN_THUMBNAILSIZE 64 #define THUMBNAIL_SIZE_STEP 4 #define DEFAULT_THUMBNAIL_SIZE 128 #define TOP_GAP 70 AnimationBrowser *g_pAnimationBrowserTool = 0; extern double realtime; void CreatePath( const char *pPath ); void CCustomAnim::LoadFromFile() { char fn[ 512 ]; if ( !filesystem->String( m_Handle, fn, sizeof( fn ) ) ) return; KeyValues *kv = new KeyValues( "CustomAnimation" ); if ( kv->LoadFromFile( filesystem, fn, "MOD" ) ) { for ( KeyValues *sub = kv->GetFirstSubKey(); sub ; sub = sub->GetNextKey() ) { CUtlSymbol anim; anim = sub->GetString(); m_Animations.AddToTail( anim ); } } kv->deleteThis(); } void CCustomAnim::SaveToFile() { char fn[ 512 ]; if ( !filesystem->String( m_Handle, fn, sizeof( fn ) ) ) return; CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); buf.Printf( "\"%s\"\n", m_ShortName.String() ); buf.Printf( "{\n" ); for ( int i = 0; i < m_Animations.Count(); ++i ) { buf.Printf( "\t\"item%d\" \"%s\"\n", i + 1, m_Animations[ i ].String() ); } buf.Printf( "}\n" ); CreatePath( fn ); filesystem->WriteFile( fn, "MOD", buf ); } bool CCustomAnim::HasAnimation( char const *search ) { CUtlSymbol searchSym; searchSym = search; if ( m_Animations.Find( searchSym ) != m_Animations.InvalidIndex() ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CAnimBrowserTab : public CTabWindow { typedef CTabWindow BaseClass; public: CAnimBrowserTab( AnimationBrowser *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) : CTabWindow( (mxWindow *)parent, x, y, w, h, id, style ) { // SetInverted( true ); } void Init( void ) { add( "all" ); add( "gestures" ); add( "postures" ); add( "search results" ); } virtual void ShowRightClickMenu( int mx, int my ) { POINT pt; GetCursorPos( &pt ); ScreenToClient( (HWND)getHandle(), &pt ); // New scene, edit comments mxPopupMenu *pop = new mxPopupMenu(); pop->add ("&New Group...", IDC_AB_CREATE_CUSTOM ); mxPopupMenu *sub = NULL; for ( int i = 0; i < m_CustomGroups.Count(); ++i ) { if ( !sub ) { sub = new mxPopupMenu(); } sub->add( va( "%s", m_CustomGroups[ i ].String() ), IDC_AB_DELETEGROUPSTART + i ); } if ( sub ) { pop->addMenu( "Delete Group", sub ); } pop->addSeparator(); sub = new mxPopupMenu(); for ( int i = 0; i < m_CustomGroups.Count(); ++i ) { sub->add( va( "%s", m_CustomGroups[ i ].String() ), IDC_AB_RENAMEGROUPSTART + i ); } pop->addMenu( "Rename Group", sub ); pop->popup( getParent(), pt.x, pt.y ); } void UpdateCustomTabs( CUtlVector< CCustomAnim * >& list ) { m_CustomGroups.Purge(); while ( getItemCount() > AnimationBrowser::FILTER_FIRST_CUSTOM ) { remove( getItemCount() - 1 ); } for ( int i = 0; i < list.Count(); ++i ) { const CCustomAnim *anim = list[ i ]; add( anim->m_ShortName.String() ); m_CustomGroups.AddToTail( anim->m_ShortName ); } } private: CUtlVector< CUtlSymbol > m_CustomGroups; }; AnimationBrowser::AnimationBrowser( mxWindow *parent, int id /*=0*/ ) : IFacePoserToolWindow( "AnimationBrowser", "Animations" ), mxWindow( parent, 0, 0, 0, 0, "AnimationBrowser", id ) { setId( id ); m_nTopOffset = 0; slScrollbar = new mxScrollbar( this, 0, 0, 18, 100, IDC_AB_TRAYSCROLL, mxScrollbar::Vertical ); m_nLastNumAnimations = -1; m_nGranularity = 10; m_nCurCell = -1; m_nClickedCell = -1; m_nGap = 4; m_nDescriptionHeight = 34; m_nSnapshotWidth = g_viewerSettings.thumbnailsizeanim; m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth ); m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth ); g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth; m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; m_bDragging = false; m_nDragCell = -1; m_szSearchString[0]=0; m_pFilterTab = new CAnimBrowserTab( this, 5, 5, 240, 20, IDC_AB_FILTERTAB ); m_pFilterTab->Init(); m_pSearchEntry = new mxLineEdit( this, 0, 0, 0, 0, "" ); m_pThumbnailIncreaseButton = new mxButton( this, 0, 0, 18, 18, "+", IDC_AB_THUMBNAIL_INCREASE ); m_pThumbnailDecreaseButton = new mxButton( this, 0, 0, 18, 18, "-", IDC_AB_THUMBNAIL_DECREASE ); m_nCurFilter = FILTER_NONE; m_flDragTime = 0.0f; OnFilter(); } //----------------------------------------------------------------------------- // Purpose: // Output : AnimationBrowser::~AnimationBrowser //----------------------------------------------------------------------------- AnimationBrowser::~AnimationBrowser ( void ) { g_pAnimationBrowserTool = NULL; } void AnimationBrowser::Shutdown() { PurgeCustom(); } //----------------------------------------------------------------------------- // Purpose: // Input : cellsize - //----------------------------------------------------------------------------- void AnimationBrowser::SetCellSize( int cellsize ) { m_nSnapshotWidth = cellsize; m_nSnapshotHeight = cellsize + m_nDescriptionHeight; redraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void AnimationBrowser::Deselect( void ) { m_nCurCell = -1; redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : exp - //----------------------------------------------------------------------------- void AnimationBrowser::Select( int sequence ) { m_nCurCell = sequence; redraw(); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int AnimationBrowser::ComputePixelsNeeded( void ) { int seqcount = GetSequenceCount(); if ( !seqcount ) return 100; // Remove scroll bar int w = this->w2() - 16; int colsperrow; colsperrow = ( w - m_nGap ) / ( m_nSnapshotWidth + m_nGap ); // At least one colsperrow = max( 1, colsperrow ); int rowsneeded = ( ( seqcount + colsperrow - 1 ) / colsperrow ); return rowsneeded * ( m_nSnapshotHeight + m_nGap ) + m_nGap + TOP_GAP + GetCaptionHeight(); } bool AnimationBrowser::ComputeRect( int cell, int& rcx, int& rcy, int& rcw, int& rch ) { // Remove scroll bar int w = this->w2() - 16; int colsperrow; colsperrow = ( w - m_nGap ) / ( m_nSnapshotWidth + m_nGap ); // At least one colsperrow = max( 1, colsperrow ); int row, col; row = cell / colsperrow; col = cell % colsperrow; // don't allow partial columns rcx = m_nGap + col * ( m_nSnapshotWidth + m_nGap ); rcy = GetCaptionHeight() + TOP_GAP + ( -m_nTopOffset * m_nGranularity ) + m_nGap + row * ( m_nSnapshotHeight + m_nGap ); // Starts off screen if ( rcx < 0 ) return false; // Ends off screen if ( rcx + m_nSnapshotWidth + m_nGap > this->w2() ) return false; // Allow partial in y direction if ( rcy > this->h2() ) return false; if ( rcy + m_nSnapshotHeight + m_nGap < 0 ) return false; // Some portion is onscreen rcw = m_nSnapshotWidth; rch = m_nSnapshotHeight; return true; } void AnimationBrowser::DrawSequenceFocusRect( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, const Color& clr ) { helper.DrawOutlinedRect( clr, PS_SOLID, 4, x, y, x + w, y + h ); } void AnimationBrowser::DrawSequenceDescription( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, int sequence, mstudioseqdesc_t &seqdesc ) { int textheight = 15; RECT textRect; textRect.left = x + 5; textRect.top = y + h - 2 * textheight - 12; textRect.right = x + w - 10; textRect.bottom = y + h - 12; helper.DrawColoredText( "Arial", 9, FW_NORMAL, Color( 63, 63, 63 ), textRect, "%s", seqdesc.pszLabel() ); StudioModel *mdl = models->GetActiveStudioModel(); if ( !mdl ) return; OffsetRect( &textRect, 0, textheight ); helper.DrawColoredText( "Arial", 9, FW_NORMAL, Color( 63, 63, 63 ), textRect, "%.2f seconds", mdl->GetDuration( sequence ) ); textRect.top = y + h - 4 * textheight - 1; textRect.bottom = textRect.top + textheight; helper.DrawColoredText( "Arial", 9, FW_NORMAL, Color( 50, 200, 255 ), textRect, "frames %i", mdl->GetNumFrames( sequence ) ); OffsetRect( &textRect, 0, textheight - 4 ); helper.DrawColoredText( "Arial", 9, FW_NORMAL, Color( 50, 200, 255 ), textRect, "fps %.2f", (float)mdl->GetFPS( sequence ) ); } bool AnimationBrowser::PaintBackground( void ) { redraw(); return false; } void AnimationBrowser::DrawThumbNail( int sequence, CChoreoWidgetDrawHelper& helper, int rcx, int rcy, int rcw, int rch ) { HDC dc = helper.GrabDC(); helper.DrawFilledRect( RGBToColor( GetSysColor( COLOR_BTNFACE ) ), rcx, rcy, rcw + rcx, rch + rcy ); mstudioseqdesc_t *pseqdesc = GetSeqDesc( sequence ); if ( !pseqdesc ) return; mxbitmapdata_t *bm = models->GetBitmapForSequence( models->GetActiveModelIndex(), TranslateSequenceNumber( sequence ) ); if ( bm && bm->valid ) { DrawBitmapToDC( dc, rcx, rcy, rcw, rch - m_nDescriptionHeight, *bm ); helper.DrawOutlinedRect( Color( 127, 127, 127 ), PS_SOLID, 1, rcx, rcy, rcx + rcw, rcy + rch - m_nDescriptionHeight ); } DrawSequenceDescription( helper, rcx, rcy, rcw, rch, TranslateSequenceNumber( sequence ), *pseqdesc ); if ( sequence == m_nCurCell ) { DrawSequenceFocusRect( helper, rcx, rcy, rcw, rch - m_nDescriptionHeight, Color( 255, 100, 63 ) ); } } void AnimationBrowser::redraw() { if ( !ToolCanDraw() ) return; bool updateSelection = false; int curcount = GetSequenceCount(); if ( curcount != m_nLastNumAnimations ) { m_nTopOffset = 0; RepositionSlider(); m_nLastNumAnimations = curcount; updateSelection = true; } CChoreoWidgetDrawHelper helper( this, RGBToColor( GetSysColor( COLOR_BTNFACE ) ) ); HandleToolRedraw( helper ); int w, h; w = w2(); h = h2(); RECT clipRect; helper.GetClientRect( clipRect ); clipRect.top += TOP_GAP + GetCaptionHeight(); helper.StartClipping( clipRect ); int rcx, rcy, rcw, rch; EnableStickySnapshotMode( ); int c = curcount; for ( int i = 0; i < c; i++ ) { if ( !ComputeRect( i, rcx, rcy, rcw, rch ) ) { // Cache in .bmp no matter what // This was too slow, so turning it back off //models->GetBitmapForSequence( models->GetActiveModelIndex(), TranslateSequenceNumber( i ) ); continue; } DrawThumbNail( i, helper, rcx, rcy, rcw, rch ); } DisableStickySnapshotMode( ); helper.StopClipping(); RECT rcText; rcText.right = w2(); rcText.left = rcText.right - 120; rcText.top = 8; rcText.bottom = rcText.top + 15; helper.DrawColoredText( "Arial", 9, FW_NORMAL, Color( 63, 63, 63 ), rcText, "%i sequences", curcount ); } int AnimationBrowser::GetCellUnderPosition( int x, int y ) { int count = GetSequenceCount(); if ( !count ) return -1; int rcx, rcy, rcw, rch; int c = 0; while ( c < count ) { if ( !ComputeRect( c, rcx, rcy, rcw, rch ) ) { c++; continue; } if ( x >= rcx && x <= rcx + rcw && y >= rcy && y <= rcy + rch ) { return c; } c++; } return -1; } void AnimationBrowser::RepositionSlider( void ) { int trueh = h2() - GetCaptionHeight(); int heightpixels = trueh / m_nGranularity; int rangepixels = ComputePixelsNeeded() / m_nGranularity; if ( rangepixels < heightpixels ) { m_nTopOffset = 0; slScrollbar->setVisible( false ); } else { slScrollbar->setVisible( true ); } slScrollbar->setBounds( w2() - 16, GetCaptionHeight() + TOP_GAP, 16, trueh - TOP_GAP ); m_nTopOffset = max( 0, m_nTopOffset ); m_nTopOffset = min( rangepixels, m_nTopOffset ); slScrollbar->setRange( 0, rangepixels ); slScrollbar->setValue( m_nTopOffset ); slScrollbar->setPagesize( heightpixels ); } void AnimationBrowser::SetClickedCell( int cell ) { m_nClickedCell = cell; Select( cell ); } void AnimationBrowser::ShowRightClickMenu( int mx, int my ) { mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nCurCell ); if ( !pseqdesc ) return; mxPopupMenu *pop = new mxPopupMenu(); Assert( pop ); pop->add( va( "New Group..." ), IDC_AB_CREATE_CUSTOM ); if ( m_CustomAnimationTabs.Count() > 0 ) { mxPopupMenu *ca = new mxPopupMenu(); Assert( ca ); for ( int i = 0; i < m_CustomAnimationTabs.Count() ; ++i ) { CCustomAnim *anim = m_CustomAnimationTabs[ i ]; ca->add( va( "%s", anim->m_ShortName.String() ), IDC_AB_ADDTOGROUPSTART + i ); } pop->addMenu( "Add to Group", ca ); ca = new mxPopupMenu(); bool useMenu = false; for ( int i = 0; i < m_CustomAnimationTabs.Count() ; ++i ) { CCustomAnim *anim = m_CustomAnimationTabs[ i ]; if ( anim->HasAnimation( pseqdesc->pszLabel() ) ) { ca->add( va( "%s", anim->m_ShortName.String() ), IDC_AB_REMOVEFROMGROUPSTART + i ); useMenu = true; } } if ( useMenu ) { pop->addMenu( "Remove from Group", ca ); } else { delete ca; } } pop->addSeparator(); pop->add( va( "Re-create thumbnail for '%s'", pseqdesc->pszLabel() ), IDC_AB_CONTEXT_CREATEBITMAP ); pop->add( va( "Re-create all thumbnails" ), IDC_AB_CONTEXT_CREATEALLBITMAPS ); pop->popup( this, mx, my ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void AnimationBrowser::DrawFocusRect( void ) { HDC dc = GetDC( NULL ); ::DrawFocusRect( dc, &m_rcFocus ); ReleaseDC( NULL, dc ); } static bool IsWindowOrChild( mxWindow *parent, HWND test ) { HWND parentHwnd = (HWND)parent->getHandle(); if ( test == parentHwnd || IsChild( parentHwnd, test ) ) { return true; } return false; } int AnimationBrowser::handleEvent (mxEvent *event) { MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); int iret = 0; if ( HandleToolEvent( event ) ) { return iret; } switch ( event->event ) { case mxEvent::Action: { iret = 1; switch ( event->action ) { default: if ( event->action >= IDC_AB_ADDTOGROUPSTART && event->action <= IDC_AB_ADDTOGROUPEND ) { int index = event->action - IDC_AB_ADDTOGROUPSTART; mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nCurCell ); if ( pseqdesc ) { AddAnimationToCustomFile( index, pseqdesc->pszLabel() ); } } else if ( event->action >= IDC_AB_REMOVEFROMGROUPSTART && event->action <= IDC_AB_REMOVEFROMGROUPEND ) { int index = event->action - IDC_AB_REMOVEFROMGROUPSTART; mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nCurCell ); if ( pseqdesc ) { RemoveAnimationFromCustomFile( index, pseqdesc->pszLabel() ); } } else if ( event->action >= IDC_AB_DELETEGROUPSTART && event->action <= IDC_AB_DELETEGROUPEND ) { int index = event->action - IDC_AB_DELETEGROUPSTART; DeleteCustomFile( index ); } else if ( event->action >= IDC_AB_RENAMEGROUPSTART && event->action <= IDC_AB_RENAMEGROUPEND ) { int index = event->action - IDC_AB_RENAMEGROUPSTART; RenameCustomFile( index ); } else { iret = 0; } break; case IDC_AB_CREATE_CUSTOM: { OnAddCustomAnimationFilter(); } break; case IDC_AB_FILTERTAB: { int index = m_pFilterTab->getSelectedIndex(); if ( index >= 0 ) { m_nCurFilter = index; OnFilter(); } } break; case IDC_AB_TRAYSCROLL: { if (event->modifiers == SB_THUMBTRACK) { int offset = event->height; slScrollbar->setValue( offset ); m_nTopOffset = offset; redraw(); } else if ( event->modifiers == SB_PAGEUP ) { int offset = slScrollbar->getValue(); offset -= m_nGranularity; offset = max( offset, slScrollbar->getMinValue() ); slScrollbar->setValue( offset ); InvalidateRect( (HWND)slScrollbar->getHandle(), NULL, TRUE ); m_nTopOffset = offset; redraw(); } else if ( event->modifiers == SB_PAGEDOWN ) { int offset = slScrollbar->getValue(); offset += m_nGranularity; offset = min( offset, slScrollbar->getMaxValue() ); slScrollbar->setValue( offset ); InvalidateRect( (HWND)slScrollbar->getHandle(), NULL, TRUE ); m_nTopOffset = offset; redraw(); } } break; case IDC_AB_THUMBNAIL_INCREASE: { ThumbnailIncrease(); } break; case IDC_AB_THUMBNAIL_DECREASE: { ThumbnailDecrease(); } break; case IDC_AB_CONTEXT_CREATEBITMAP: { int current_model = models->GetActiveModelIndex(); if ( m_nClickedCell >= 0 ) { models->RecreateAnimationBitmap( current_model, TranslateSequenceNumber( m_nClickedCell ) ); } redraw(); } break; case IDC_AB_CONTEXT_CREATEALLBITMAPS: { int current_model = models->GetActiveModelIndex(); models->RecreateAllAnimationBitmaps( current_model ); redraw(); } break; } break; } case mxEvent::MouseDown: { if ( !( event->buttons & mxEvent::MouseRightButton ) ) { // Figure out cell # int cell = GetCellUnderPosition( event->x, event->y ); if ( cell >= 0 && cell < GetSequenceCount() ) { int cx, cy, cw, ch; if ( ComputeRect( cell, cx, cy, cw, ch ) ) { m_flDragTime = realtime; m_bDragging = true; m_nDragCell = cell; m_nXStart = (short)event->x; m_nYStart = (short)event->y; m_rcFocus.left = cx; m_rcFocus.top = cy; m_rcFocus.right = cx + cw; m_rcFocus.bottom = cy + ch - m_nDescriptionHeight; POINT pt; pt.x = pt.y = 0; ClientToScreen( (HWND)getHandle(), &pt ); OffsetRect( &m_rcFocus, pt.x, pt.y ); m_rcOrig = m_rcFocus; DrawFocusRect(); Select( cell ); m_nClickedCell = cell; } } else { Deselect(); redraw(); } } iret = 1; } break; case mxEvent::MouseDrag: { if ( m_bDragging ) { // Draw drag line of some kind DrawFocusRect(); // update pos m_rcFocus = m_rcOrig; OffsetRect( &m_rcFocus, ( (short)event->x - m_nXStart ), ( (short)event->y - m_nYStart ) ); DrawFocusRect(); } iret = 1; } break; case mxEvent::MouseUp: { iret = 1; if ( event->buttons & mxEvent::MouseRightButton ) { SetClickedCell( GetCellUnderPosition( (short)event->x, (short)event->y ) ); ShowRightClickMenu( (short)event->x, (short)event->y ); return iret; } if ( m_bDragging && m_nClickedCell >= 0 ) { mstudioseqdesc_t *pseqdesc = GetSeqDesc( m_nClickedCell ); DrawFocusRect(); m_bDragging = false; // See if we let go on top of the choreo view // Convert x, y to screen space POINT pt; pt.x = (short)event->x; pt.y = (short)event->y; ClientToScreen( (HWND)getHandle(), &pt ); HWND maybeTool = WindowFromPoint( pt ); // Now tell choreo view if ( maybeTool && pseqdesc ) { if ( IsWindowOrChild( g_pChoreoView, maybeTool ) ) { if ( g_pChoreoView->CreateAnimationEvent( pt.x, pt.y, pseqdesc->pszLabel() ) ) { return iret; } } } } } break; case mxEvent::Size: { int width = w2(); // int height = h2(); int ch = GetCaptionHeight() + 10; m_pSearchEntry->setBounds( 5, ch, width - 10 - 170, 18 ); m_pThumbnailIncreaseButton->setBounds( width - 40, 4 + ch, 16, 16 ); m_pThumbnailDecreaseButton->setBounds( width - 20, 4 + ch, 16, 16 ); m_pFilterTab->setBounds( 5, ch + 20, width - 10, 20 ); m_nTopOffset = 0; RepositionSlider(); redraw(); iret = 1; } break; case mxEvent::MouseWheeled: { // Figure out cell # POINT pt; pt.x = event->x; pt.y = event->y; ScreenToClient( (HWND)getHandle(), &pt ); if ( event->height < 0 ) { m_nTopOffset = min( m_nTopOffset + 10, slScrollbar->getMaxValue() ); } else { m_nTopOffset = max( m_nTopOffset - 10, 0 ); } RepositionSlider(); redraw(); iret = 1; } break; case mxEvent::KeyDown: case mxEvent::KeyUp: { bool search = false; // int n = 3; if ( event->key == VK_ESCAPE && m_szSearchString[ 0 ] ) { m_pSearchEntry->setLabel( "" ); m_szSearchString[ 0 ] = 0; m_pFilterTab->select( FILTER_NONE ); m_nCurFilter = FILTER_NONE; OnFilter(); } else { // Text changed? char sz[ 512 ]; m_pSearchEntry->getText( sz, sizeof( sz ) ); if ( Q_stricmp( sz, m_szSearchString ) ) { Q_strncpy( m_szSearchString, sz, sizeof( m_szSearchString ) ); search = true; } } if ( search ) { if ( Q_strlen( m_szSearchString ) > 0 ) { m_pFilterTab->select( FILTER_STRING ); m_nCurFilter = FILTER_STRING; } else { m_pFilterTab->select( FILTER_NONE ); m_nCurFilter = FILTER_NONE; } OnFilter(); } } break; }; if ( iret ) { SetActiveTool( this ); } return iret; } // HACK HACK: VS2005 is generating bogus code for this little operation in the function below... #pragma optimize( "g", off ) float roundcycle( float cycle ) { int rounded = (int)(cycle); float cy2 = cycle - rounded; return cy2; } #pragma optimize( "", on ) void AnimationBrowser::Think( float dt ) { if ( !m_bDragging ) return; if ( m_nClickedCell < 0 ) return; StudioModel *model = models->GetActiveStudioModel(); if ( model ) { int iSequence = TranslateSequenceNumber( m_nClickedCell ); float dur = model->GetDuration( iSequence ); if ( dur > 0.0f ) { float elapsed = (float)realtime - m_flDragTime; float flFrameRate = 0.0f; float flGroundSpeed = 0.0f; model->GetSequenceInfo( iSequence, &flFrameRate, &flGroundSpeed ); float cycle = roundcycle( elapsed * flFrameRate ); // This should be the only thing on the model!!! model->ClearAnimationLayers(); // FIXME: shouldn't sequences always be lower priority than gestures? int iLayer = model->GetNewAnimationLayer( 0 ); model->SetOverlaySequence( iLayer, iSequence, 1.0f ); model->SetOverlayRate( iLayer, cycle, 0.0f ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void AnimationBrowser::ThumbnailIncrease( void ) { if ( m_nSnapshotWidth + THUMBNAIL_SIZE_STEP <= MAX_THUMBNAILSIZE ) { m_nSnapshotWidth += THUMBNAIL_SIZE_STEP; g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth; m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth ); redraw(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void AnimationBrowser::ThumbnailDecrease( void ) { if ( m_nSnapshotWidth - THUMBNAIL_SIZE_STEP >= MIN_THUMBNAILSIZE ) { m_nSnapshotWidth -= THUMBNAIL_SIZE_STEP; g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth; m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth ); redraw(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void AnimationBrowser::RestoreThumbnailSize( void ) { m_nSnapshotWidth = g_viewerSettings.thumbnailsizeanim; m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth ); m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth ); g_viewerSettings.thumbnailsizeanim = m_nSnapshotWidth; m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; redraw(); } void AnimationBrowser::ReloadBitmaps( void ) { Assert( 0 ); redraw(); } //----------------------------------------------------------------------------- // Purpose: // Input : *model - // sequence - // Output : static bool //----------------------------------------------------------------------------- static bool IsTypeOfSequence( StudioModel *model, int sequence, char const *typestring ) { bool match = false; if ( !model->GetStudioHdr() ) return match; KeyValues *seqKeyValues = new KeyValues(""); if ( seqKeyValues->LoadFromBuffer( model->GetFileName( ), model->GetKeyValueText( sequence ) ) ) { // Do we have a build point section? KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer"); if ( pkvAllFaceposer ) { char const *t = pkvAllFaceposer->GetString( "type", "" ); if ( t && !Q_stricmp( t, typestring ) ) { match = true; } } } seqKeyValues->deleteThis(); return match; } bool AnimationBrowser::SequencePassesFilter( StudioModel *model, int sequence, mstudioseqdesc_t &seqdesc ) { if (model->IsHidden( sequence )) return false; switch ( m_nCurFilter ) { default: { int offset = m_nCurFilter - FILTER_FIRST_CUSTOM; if ( offset >= 0 && offset < m_CustomAnimationTabs.Count() ) { // Find the name CCustomAnim *anim = m_CustomAnimationTabs[ offset ]; return anim->HasAnimation( seqdesc.pszLabel() ); } return true; } break; case FILTER_NONE: { return true; } break; case FILTER_GESTURES: if ( IsTypeOfSequence( model, sequence, "gesture" ) ) { return true; } break; case FILTER_POSTURES: if ( IsTypeOfSequence( model, sequence, "posture" ) ) { return true; } break; case FILTER_STRING: if ( Q_stristr( seqdesc.pszLabel(), m_szSearchString ) ) { return true; } } return false; } void AnimationBrowser::OnFilter() { m_Filtered.RemoveAll(); StudioModel *model = models->GetActiveStudioModel(); if ( !model ) return; CStudioHdr *hdr = model->GetStudioHdr(); if ( !hdr ) return; int count = hdr->GetNumSeq(); for ( int i = 0; i < count; i++ ) { mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( i ); // if it passes the filter, add it if ( SequencePassesFilter( model, i, seqdesc ) ) { m_Filtered.AddToTail( i ); } } redraw(); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int AnimationBrowser::GetSequenceCount() { return m_Filtered.Count(); } //----------------------------------------------------------------------------- // Purpose: // Input : index - // Output : mstudioseqdesc_t //----------------------------------------------------------------------------- mstudioseqdesc_t *AnimationBrowser::GetSeqDesc( int index ) { CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); if ( !hdr ) return NULL; index = TranslateSequenceNumber( index ); if ( index < 0 || index >= hdr->GetNumSeq() ) return NULL; return &hdr->pSeqdesc( index ); } int AnimationBrowser::TranslateSequenceNumber( int index ) { if ( index < 0 || index >= m_Filtered.Count() ) return NULL; // Lookup the true index index = m_Filtered[ index ]; return index; } void AnimationBrowser::FindCustomFiles( char const *subdir, CUtlVector< FileNameHandle_t >& files ) { char search[ 512 ]; Q_snprintf( search, sizeof( search ), "%s/*.txt", subdir ); FileFindHandle_t findHandle; const char *pFileName = filesystem->FindFirst( search, &findHandle ); while( pFileName ) { if( !filesystem->FindIsDirectory( findHandle ) ) { // Strip off the 'sound/' part of the name. char fn[ 512 ]; Q_snprintf( fn, sizeof( fn ), "%s/%s", subdir, pFileName ); FileNameHandle_t fh; fh = filesystem->FindOrAddFileName( fn ); files.AddToTail( fh ); } pFileName = filesystem->FindNext( findHandle ); } filesystem->FindClose( findHandle ); } void AnimationBrowser::PurgeCustom() { for ( int i = 0; i < m_CustomAnimationTabs.Count(); ++i ) { if ( m_CustomAnimationTabs[ i ]->m_bDirty ) { m_CustomAnimationTabs[ i ]->SaveToFile(); } delete m_CustomAnimationTabs[ i ]; } m_CustomAnimationTabs.Purge(); } void AnimationBrowser::BuildCustomFromFiles( CUtlVector< FileNameHandle_t >& files ) { PurgeCustom(); for ( int i = 0; i < files.Count(); ++i ) { char fn[ 512 ]; if ( !filesystem->String( files[ i ], fn, sizeof( fn ) ) ) continue; Q_FixSlashes( fn ); Q_strlower( fn ); char basename[ 128 ]; Q_FileBase( fn, basename, sizeof( basename ) ); CCustomAnim *anim = new CCustomAnim( files[ i ] ); anim->m_ShortName = basename; anim->LoadFromFile(); m_CustomAnimationTabs.AddToTail( anim ); } UpdateCustomTabs(); } void AnimationBrowser::RenameCustomFile( int index ) { if ( index < 0 || index >= m_CustomAnimationTabs.Count() ) return; CCustomAnim *anim = m_CustomAnimationTabs[ index ]; CInputParams params; memset( ¶ms, 0, sizeof( params ) ); Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "Custom Animation Group" ); Q_strcpy( params.m_szPrompt, "Group Name:" ); Q_strcpy( params.m_szInputText, anim->m_ShortName.String() ); if ( !InputProperties( ¶ms ) ) return; if ( !params.m_szInputText[ 0 ] ) return; // No change if ( !Q_stricmp( anim->m_ShortName.String(), params.m_szInputText ) ) return; char fn[ 512 ]; if ( !filesystem->String( anim->m_Handle, fn, sizeof( fn ) ) ) { Assert( 0 ); return; } StudioModel *model = models->GetActiveStudioModel(); if ( !model ) { return; } CStudioHdr *hdr = model->GetStudioHdr(); if ( !hdr ) { return; } // Delete the old file filesystem->RemoveFile( fn, "MOD" ); anim->m_ShortName = params.m_szInputText; char basename[ 128 ]; Q_StripExtension( hdr->name(), basename, sizeof( basename ) ); Q_snprintf( fn, sizeof( fn ), "expressions/%s/animation/%s.txt", basename, params.m_szInputText ); Q_FixSlashes( fn ); Q_strlower( fn ); CreatePath( fn ); anim->m_Handle = filesystem->FindOrAddFileName( fn ); anim->m_bDirty = true; UpdateCustomTabs(); } void AnimationBrowser::AddCustomFile( const FileNameHandle_t& handle ) { char fn[ 512 ]; if ( !filesystem->String( handle, fn, sizeof( fn ) ) ) return; Q_FixSlashes( fn ); Q_strlower( fn ); char basename[ 128 ]; Q_FileBase( fn, basename, sizeof( basename ) ); CCustomAnim *anim = new CCustomAnim( handle ); anim->m_ShortName = basename; anim->LoadFromFile(); anim->m_bDirty = true; if ( m_nCurCell != -1 ) { StudioModel *model = models->GetActiveStudioModel(); if ( model ) { CStudioHdr *hdr = model->GetStudioHdr(); if ( hdr ) { mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( m_nCurCell ); CUtlSymbol sym; sym = seqdesc.pszLabel(); anim->m_Animations.AddToTail( sym ); } } } m_CustomAnimationTabs.AddToTail( anim ); UpdateCustomTabs(); } void AnimationBrowser::DeleteCustomFile( int index ) { if ( index < 0 || index >= m_CustomAnimationTabs.Count() ) return; CCustomAnim *anim = m_CustomAnimationTabs[ index ]; char fn[ 512 ]; if ( !filesystem->String( anim->m_Handle, fn, sizeof( fn ) ) ) return; m_CustomAnimationTabs.Remove( index ); filesystem->RemoveFile( fn ); delete anim; UpdateCustomTabs(); } void AnimationBrowser::UpdateCustomTabs() { m_pFilterTab->UpdateCustomTabs( m_CustomAnimationTabs ); } void AnimationBrowser::OnModelChanged() { CUtlVector< FileNameHandle_t > files; StudioModel *model = models->GetActiveStudioModel(); if ( model ) { CStudioHdr *hdr = model->GetStudioHdr(); if ( hdr ) { char subdir[ 512 ]; char basename[ 512 ]; Q_StripExtension( hdr->name(), basename, sizeof( basename ) ); Q_snprintf( subdir, sizeof( subdir ), "expressions/%s/animation", basename ); Q_FixSlashes( subdir ); Q_strlower( subdir ); FindCustomFiles( subdir, files ); } } BuildCustomFromFiles( files ); RestoreThumbnailSize(); // Just reapply filter OnFilter(); } void AnimationBrowser::OnAddCustomAnimationFilter() { StudioModel *model = models->GetActiveStudioModel(); if ( !model ) { return; } CStudioHdr *hdr = model->GetStudioHdr(); if ( !hdr ) { return; } CInputParams params; memset( ¶ms, 0, sizeof( params ) ); Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "Custom Animation Group" ); Q_strcpy( params.m_szPrompt, "Group Name:" ); Q_strcpy( params.m_szInputText, "" ); if ( !InputProperties( ¶ms ) ) return; if ( !params.m_szInputText[ 0 ] ) return; if ( FindCustomFile( params.m_szInputText ) != -1 ) { Warning( "Can't add duplicate tab '%s'\n", params.m_szInputText ); return; } // Create it char fn[ 512 ]; char basename[ 512 ]; Q_StripExtension( hdr->name(), basename, sizeof( basename ) ); Q_snprintf( fn, sizeof( fn ), "expressions/%s/animation/%s.txt", basename, params.m_szInputText ); Q_FixSlashes( fn ); Q_strlower( fn ); CreatePath( fn ); FileNameHandle_t fh = filesystem->FindOrAddFileName( fn ); AddCustomFile( fh ); } int AnimationBrowser::FindCustomFile( char const *shortName ) { CUtlSymbol search; search = shortName; for ( int i = 0; i < m_CustomAnimationTabs.Count(); ++i ) { CCustomAnim *anim = m_CustomAnimationTabs[ i ]; if ( anim->m_ShortName == search ) return i; } return -1; } void AnimationBrowser::AddAnimationToCustomFile( int index, char const *animationName ) { if ( index < 0 || index >= m_CustomAnimationTabs.Count() ) return; CCustomAnim *anim = m_CustomAnimationTabs[ index ]; CUtlSymbol search; search = animationName; if ( anim->m_Animations.Find( search ) == anim->m_Animations.InvalidIndex() ) { anim->m_Animations.AddToTail( search ); anim->m_bDirty = true; } OnFilter(); } void AnimationBrowser::RemoveAnimationFromCustomFile( int index, char const *animationName ) { if ( index < 0 || index >= m_CustomAnimationTabs.Count() ) return; CCustomAnim *anim = m_CustomAnimationTabs[ index ]; CUtlSymbol search; search = animationName; int slot = anim->m_Animations.Find( search ); if ( slot != anim->m_Animations.InvalidIndex() ) { anim->m_Animations.Remove( slot ); anim->m_bDirty = true; OnFilter(); } } void AnimationBrowser::RemoveAllAnimationsFromCustomFile( int index ) { if ( index < 0 || index >= m_CustomAnimationTabs.Count() ) return; CCustomAnim *anim = m_CustomAnimationTabs[ index ]; anim->m_Animations.Purge(); anim->m_bDirty = true; OnFilter(); }